Animating Out of Order

In Algan, when you change an animatable attribute, or run an animated function, Algan does not actually perform that animation immediately. Instead, Algan makes a record of the fact that this animation took place, and the times at which the animation begins and ends. Algan stores this information inside of the Mob s data attribute, which is an AnimatableData object. The time at which the animation takes place is controlled by the AnimationContexts. For example, in a Seq context, once an animation is done, the context will write the animation to the current time, then increment the current time by 1. So the next animation will be written to one second later on the timeline, and so on.

Once a command to render is given, as in render_to_file(), Algan reads through all of the Mob’s animation timelines and actually performs the interpolations to compute animated states.

Most of the time, you do not need to worry about this and you can just let the animation contexts handle the writing of animations to the timeline. But if you want to, you can take manual control of the animation writing, to write animations anywhere in the timeline, at any point in the code. And in some situations, this makes animation code much simpler.

Animating a wave effect

Suppose that we have a bunch of mobs

n = 10
mobs = Group([Square(color=BLUE) for _ in range(n*n)]).arrange_in_grid(n).scale(0.25).spawn()

and we want to animate the effect of a wave passing through them, from the top-left of the screen to the bottom-right. When the wave hits a Mob, we will change its color to RED briefly. This is quite a difficult animation to orchestrate normally. You need to sort mobs in the order in which the wave hits them, and then calculate how much time there is between the wave hitting one mob and the next. Instead, it is much simpler to specify for each Mob when its animation should start.

wave_direction = F.normalize(RIGHT + DOWN, p=2, dim=-1)
mob_dots = [(mob.location * wave_direction).sum().item() for mob in mobs]
min_dot = min(mob_dots)
max_dot = max(mob_dots)

We now have a list of the times at which each mob should start playing its animation. And we can use out of order animation to implement the animations.

Example: AOOWave1

from algan import *

n = 10
mobs = Group([Square(color=BLUE) for _ in range(n*n)]).arrange_in_grid(n).scale(0.25).spawn()

wave_direction = F.normalize(RIGHT + DOWN, p=2, dim=-1)
mob_dots = [(mob.location * wave_direction).sum().item() for mob in mobs]
min_dot = min(mob_dots)
max_dot = max(mob_dots)

with Seq() as context:
    # Get the current point in the timeline which this context is writing to.
    animation_start_time = context.current_time
    for i in range(len(mobs)):
        # rescale to [0, 5], so the wave takes 5 seconds to propagate.
        mob_start_time = 5 * (mob_dots[i] - min_dot) / (max_dot - min_dot)

        # Set the current time we write animations to,
        # to the point in time when this mob should start
        context.current_time = animation_start_time+mob_start_time

        # Write the animation to this point on the timeline.
        with Seq(run_time=2):
            original_color = mobs[i].color
            mobs[i].color = RED
            mobs[i].color = original_color

    # Now that we are done writing the animations, jump to the end of the context to
    # continue animating in order.
    context.current_time = context.end_time

render_to_file()
from algan import *

n = 10
mobs = Group([Square(color=BLUE) for _ in range(n*n)]).arrange_in_grid(n).scale(0.25).spawn()

wave_direction = F.normalize(RIGHT + DOWN, p=2, dim=-1)
mob_dots = [(mob.location * wave_direction).sum().item() for mob in mobs]
min_dot = min(mob_dots)
max_dot = max(mob_dots)

with Seq() as context:
    # Get the current point in the timeline which this context is writing to.
    animation_start_time = context.current_time
    for i in range(len(mobs)):
        # rescale to [0, 5], so the wave takes 5 seconds to propagate.
        mob_start_time = 5 * (mob_dots[i] - min_dot) / (max_dot - min_dot)

        # Set the current time we write animations to,
        # to the point in time when this mob should start
        context.current_time = animation_start_time+mob_start_time

        # Write the animation to this point on the timeline.
        with Seq(run_time=2):
            original_color = mobs[i].color
            mobs[i].color = RED
            mobs[i].color = original_color

    # Now that we are done writing the animations, jump to the end of the context to
    # continue animating in order.
    context.current_time = context.end_time

render_to_file()