8. Parallel Animations
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