Skip to content

Commit

Permalink
Custom Animation Docs (#1739)
Browse files Browse the repository at this point in the history
* Add custom animation section to docs building blocks page

* Add CountingScene Example for Custom Animation Docs

* Fix "upto" typo

* Scale DecimalNumber

* Small tweaks to Custom Animation example

* Add updater to decimal number to remain always in center in CountingScene

* Rename mob to number in CountingScene

* Add reference classes, methods and functions to Custom Animation Example

* Make the quality medium for Custom Animation Example

* Add the explaination for Custom Animation Example

* Remove rate functions reference

* Minor tweaks to Custom Animation Example

* Incorporate review changes

* Add more explaination to the logic behind calculating value

* Incorporate review changes

* Fix references

* Remove quality medium

* Add docstring to interpolate_mobject method of Animation class

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

Co-authored-by: Benjamin Hackl <devel@benjamin-hackl.at>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 1, 2021
1 parent 5061357 commit ba23bc5
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 6 deletions.
71 changes: 71 additions & 0 deletions docs/source/tutorials/building_blocks.rst
Expand Up @@ -294,6 +294,77 @@ Use the :code:`run_time` argument to control the duration.
self.play(ApplyMethod(square.shift, UP), run_time=3)
self.wait(1)

Creating a custom animation
===========================

Even though Manim has many built-in animations, you will find times when you need to smoothly animate from one state of a :class:`~.Mobject` to another.
If you find yourself in that situation, then you can define your own custom animation.
You start by extending the :class:`~.Animation` class and overriding its :meth:`~.Animation.interpolate_mobject`.
The :meth:`~.Animation.interpolate_mobject` method receives alpha as a parameter that starts at 0 and changes throughout the animation.
So, you just have to manipulate self.mobject inside Animation according to the alpha value in its interpolate_mobject method.
Then you get all the benefits of :class:`~.Animation` such as playing it for different run times or using different rate functions.

Let's say you start with a number and want to create a :class:`~.Transform` animation that transforms it to a target number.
You can do it using :class:`~.FadeTransform`, which will fade out the starting number and fade in the target number.
But when we think about transforming a number from one to another, an intuitive way of doing it is by incrementing or decrementing it smoothly.
Manim has a feature that allows you to customize this behavior by defining your own custom animation.

You can start by creating your own ``Count`` class that extends :class:`~.Animation`.
The class can have a constructor with three arguments, a :class:`~.DecimalNumber` Mobject, start, and end.
The constructor will pass the :class:`~.DecimalNumber` Mobject to the super constructor (in this case, the :class:`~.Animation` constructor) and will set start and end.

The only thing that you need to do is to define how you want it to look at every step of the animation.
Manim provides you with the alpha value in the :meth:`~.Animation.interpolate_mobject` method based on frame rate of video, rate function, and run time of animation played.
The alpha parameter holds a value between 0 and 1 representing the step of the currently playing animation.
For example, 0 means the beginning of the animation, 0.5 means halfway through the animation, and 1 means the end of the animation.

In the case of the ``Count`` animation, you just have to figure out a way to determine the number to display at the given alpha value and then set that value in the :meth:`~.Animation.interpolate_mobject` method of the ``Count`` animation.
Suppose you are starting at 50 and incrementing until the :class:`~.DecimalNumber` reaches 100 at the end of the animation.

* If alpha is 0, you want the value to be 50.
* If alpha is 0.5, you want the value to be 75.
* If alpha is 1, you want the value to be 100.

Generally, you start with the starting number and add only some part of the value to be increment according to the alpha value.
So, the logic of calculating the number to display at each step will be - 50 + alpha * (100 - 50).
Once you set the calculated value for the :class:`~.DecimalNumber`, you are done.

Once you have defined your ``Count`` animation, you can play it in your :class:`~.Scene` for any duration you want for any :class:`~.DecimalNumber` with any rate function.

.. manim:: CountingScene
:ref_classes: Animation DecimalNumber
:ref_methods: Animation.interpolate_mobject Scene.play

class Count(Animation):
def __init__(self, number: DecimalNumber, start: float, end: float, **kwargs) -> None:
# Pass number as the mobject of the animation
super().__init__(number, **kwargs)
# Set start and end
self.start = start
self.end = end
def interpolate_mobject(self, alpha: float) -> None:
# Set value of DecimalNumber according to alpha
value = self.start + (alpha * (self.end - self.start))
self.mobject.set_value(value)


class CountingScene(Scene):
def construct(self):
# Create Decimal Number and add it to scene
number = DecimalNumber().set_color(WHITE).scale(5)
# Add an updater to keep the DecimalNumber centered as its value changes
number.add_updater(lambda number: number.move_to(ORIGIN))

self.add(number)

self.wait()

# Play the Count Animation to count from 0 to 100 in 4 seconds
self.play(Count(number, 0, 100), run_time=4, rate_func=linear)

self.wait()

Using coordinates of a mobject
==============================

Expand Down
22 changes: 16 additions & 6 deletions manim/animation/animation.py
@@ -1,6 +1,13 @@
"""Animate mobjects."""


from .. import logger
from ..mobject import mobject, opengl_mobject
from ..mobject.mobject import Mobject
from ..mobject.opengl_mobject import OpenGLMobject
from ..utils.deprecation import deprecated
from ..utils.rate_functions import smooth

__all__ = ["Animation", "Wait", "override_animation"]


Expand All @@ -20,12 +27,6 @@
if TYPE_CHECKING:
from manim.scene.scene import Scene

from .. import logger
from ..mobject import mobject, opengl_mobject
from ..mobject.mobject import Mobject
from ..mobject.opengl_mobject import OpenGLMobject
from ..utils.deprecation import deprecated
from ..utils.rate_functions import smooth

DEFAULT_ANIMATION_RUN_TIME: float = 1.0
DEFAULT_ANIMATION_LAG_RATIO: float = 0.0
Expand Down Expand Up @@ -278,6 +279,15 @@ def interpolate(self, alpha: float) -> None:
self.interpolate_mobject(alpha)

def interpolate_mobject(self, alpha: float) -> None:
"""Interpolates the mobject of the :class:`Animation` based on alpha value.
Parameters
----------
alpha
A float between 0 and 1 expressing the ratio to which the animation
is completed. For example, alpha-values of 0, 0.5, and 1 correspond
to the animation being completed 0%, 50%, and 100%, respectively.
"""
families = list(self.get_all_families_zipped())
for i, mobs in enumerate(families):
sub_alpha = self.get_sub_alpha(alpha, i, len(families))
Expand Down

0 comments on commit ba23bc5

Please sign in to comment.