8. Parallel Animations

Accompanying Video

8.1 Sticky

DEFINITION An animation is said to be sticky if it is run in parallel with the previous animation in the list.

In particular, sticky is a unary operator that takes in an animation and tells it to run in parallel with the previous animation in the parent vector. If an animation is the first in the list, then there is no change.

Example: fading and writing two objects at the same time.

tree square = Square:
    center: LEFT
    width: 1
    tag: {}
    color: default

tree circ = Circle:
    center: RIGHT
    radius: 0.5
    tag: {}
    color: default

p += Fade:
    meshes&: square
    time: 1

/* in this example we can reorder the Fade and Write */
/* in some advanced scenarios, you cannot */
p += sticky Write:
    meshes&: circ
    time: 1

8.2 Parallel Animations on the same Variable

In general, two animations cannot run on the same variable (you will get an error of concurrent modification). Heres an example causing error. Notice how it doesn't make sense to do two different actions on the same object at once.

tree square = Square:
    center: LEFT
    width: 1
    tag: {}
    color: default

tree circ = Circle:
    center: RIGHT
    radius: 0.5
    tag: {}
    color: default

p += Fade:
    meshes&: square
    time: 1

/* how can we write and fade the same object at once? */
p += sticky Write:
    meshes&: square
    time: 1

However, there are cases where we want two animations to run on separate subsets of the same variable. This is perfectly valid, but we need to be a bit smart. The strategy is to split the contents into two separate variables, perform the animation on the auxiliary variables, then hide the auxiliary variables and show the main one. This also highights the important of auxiliary variables.

tree shapes = {} + Square:
    center: LEFT
    width: 1
    tag: {0}
    color: default
shapes += Circle:
    center: RIGHT
    radius: 0.5
    tag: {1}
    color: default

tree aux1 = mesh_select:
    root: shapes
    tag_predicate(tag): 0 in tag
tree aux2 = mesh_select:
    root: shapes
    tag_predicate(tag): 1 in tag

/* now we can do a normal animation on aux1 and aux2 */
p += Fade:
    meshes&: aux1
    time: 1
p += sticky Write:
    meshes&: aux2
    time: 1
/* at the end, we to hide aux1 and aux2, and make shapes visible */
aux1 = aux2 = {}
p += Set:
    vars&: {aux1, aux2, shapes}

That was somewhat of a toy example, heres a more complicated example where we show a column of a matrix one by one.

func matrix(n) = identity:
    /* notice the tags */
    var ret = {}
    for i in 0 :< n
        for j in 0 :< n
            ret += Circle:
                center: {i / 4, j / 4, 0}
                radius: 0.05
                tag: {i, j}
                stroke: CLEAR
                fill: WHITE

    element: ret

let mat = matrix:
    n: 8

/* the final dump variable */
tree main = {}

/* every iteration of the for loop, a new aux variable is created */
var anims = {}
for i in 0 :< mat.n 
    tree aux = mesh_select:
        root: mat
        tag_predicate(tag): tag[0] == i

    var sub = {}
    /* first fade it in, then transfer contents to main */
    sub += Fade:
        meshes&: aux
        time: 1
    sub += Transfer:
        from&: aux
        into&: main

    anims += sub

/* lagged map is like parallel, but with some delay */
p += LaggedMap:
    anims: anims
    average_offset: 0.05

/* bring back in type information */
main = mat
p += Set:
    vars&: main

REMARK An auxiliary variable is created in each iteration of the for loop. Thus in this example there are 8 auxiliary variables.

That might be a bit hard to follow, but the beauty of Monocurl is we can abstract the logic into a function and only have to worry about it once. Notice that while ColumnFade has some complex logic (as you write more Monocurl, this will make more sense as the paradigm is somewhat common), the actual interface for ColumnFade is remarkably simple and can be reused when necessary.

func matrix(n) = identity:
    /* same as before */

func ColumnFade(mesh&) = identity:
    let org = mesh

    var ret = {}
    /* clear mesh as auxiliaries take ownership */
    mesh = {}
    ret += Set:
        vars&: mesh

    var lagged_anims = {}
    for i in 0 :< org.n
        tree aux = mesh_select:
            root: org
            tag_predicate(tag): tag[0] == i

        var sub = {}
        /* first fade it in, then transfer contents to main once done */
        sub += Fade:
            meshes&: aux
            time: 1
        sub += Transfer:
            from&: aux
            into&: mesh

        lagged_anims += sub

    ret += LaggedMap:
        anims: lagged_anims
        average_offset: 0.05

    /* restore type information */
    mesh = org
    ret += Set:
        vars&: mesh

    element: ret

tree main = matrix:
    n: 8
p += ColumnFade:
    mesh&: main

Sections

8.1 Sticky8.2 Parallel Animations on the same Variable