Reset
Project Overview
This show opening was done for Vox’s Reset podcast, hosted by Arielle Duhaime-Ross, which covers why and how technology is changing everything. Inspired by Ordinary Folk’s Age of No Code animation, abstract forms and shapes were utilized as the tools for creating a simple yet compelling visual language.
Responsiblities
I was responsible for the creative direction, project management, complex animations, and technical development such as writing a custom JSX scriptUI to automate the brick tessellation of hundreds of icons in the opening shot.
- Motion Designer
- After Effects
- Javascript scripting
Technical Development
Workflow
The complexity of the opening shot was mostly the tediousness of arranging hundreds of blocks or sprites into rows. Instead of doing things manually, I developed an AE JSX scriptUI that allows users to tile selected layers into brick tessellation with user-specified horizontal and vertical spacing and not have them non-repeating from the previous layer, in a single click.
However, the script is only a component of production, what matters is how the script was used. To tackle the complexity of the opening shot and eliminate the menial task of tiling layers together, here is the workflow I developed
- Animate a series of icons of animations that spans 8 seconds.
- Generate 9 tile tessellation combinations using the custom written script.
- To sequential animate in the icons, a distanced-base Time Remapping expression was added to each icon layer.
- These blocks are then rendered with alpha, and used to assemble into a full-HD composition.
Brick tessellation script
Below are the demonstration of the aforementioned script being used, a visual diagram of how it works, and the main algorithm.
Algorithm
// Setup
var myComp = app.project.activeItem;
var myLayers = myComp.selectedLayers;
// ----------------------------------------
// Utilities import
function getRandNum(max) {
var x = Math.floor(Math.random() * max);
return x;
};
function arraySource(array) {
var arrSrc = [];
for (i = 0; i < array.length; i++) {
arrSrc.push(array[i].source.name);
}
return arrSrc;
}
function arrayCompress(array) {
// generate array of unique names
var str = array.sort().join('\r') + '\r';
str = str.replace(/([^\r]+\r)(\1)+/g, '$1');
str = str.replace(/\r$/, '');
return str.split('\r');
}
// ----------------------------------------
function sortUniqueArray(array) {
var seq = [];
var str = arraySource(array);
var i = array.length;
while (i > 0) {
var groups = arrayCompress(str).length;
var randIndex = getRandNum(array.length); // random element index
var randElem = array[randIndex]; // element selection
// check for non-repeating
if (seq.length > 0) {
while (groups != 1 && seq[seq.length - 1].source.name == randElem.source.name) {
var randIndex = getRandNum(array.length);
var randElem = array[randIndex];
}
}
// append & remove
seq.push(randElem);
array.splice(randIndex, 1);
i--;
}
return seq;
}
function tileByDistance(layers, hSpace, vSpace, threshold) {
app.beginUndoGroup("tileLayers");
// Set start position
var currentTime = app.project.activeItem.time;
// var startPos = [0, 0];
var startPos = [layers[0].sourceRectAtTime(currentTime, false).width * layers[0].transform.scale.value[0], layers[0].sourceRectAtTime(currentTime, false).height * layers[0].transform.scale.value[1]] / 100 / 2;
layers[0].transform.position.setValue(startPos);
// Tile layers
for (i = 1; i < layers.length; i++) {
myLayer = layers[i];
previousLayer = layers[i - 1];
s1 = myLayer.sourceRectAtTime(currentTime, false).width * myLayer.transform.scale.value[0] / 100 / 2;
s2 = previousLayer.sourceRectAtTime(currentTime, false).width * previousLayer.transform.scale.value[0] / 100 / 2;
xPos = previousLayer.transform.position.value[0] + s1 + hSpace + s2;
yPos = previousLayer.transform.position.value[1];
if (xPos > threshold) {
xPos = getRandNum(s1);
yPos += vSpace;
}
myLayer.transform.position.setValue([xPos, yPos]);
}
}
// ----------------------------------------
// Main
if (myLayers != 0) {
var mySeq = sortUniqueArray(myLayers);
tileByDistance(mySeq, 0, 0, 1920);
} else {
alert("No layers are selected")
}
Animation
Art-directing the sequential animation
Also, for the opening shot, instead of having the icon animation appearing in linear fashion from left to right (left image), the next challenge was to stylize the sequence in which the blocks appeared as if they were being typed row by row (right image).
Using a simple distanced-based Time Remapping expression technique with a control null layer is insufficient because it behaves as an omnidirectional effector and we wanted the blocks to appear row by row. Using the index number of layers to stagger the animation will be menial if we wanted to change the design quickly with the script I developed, hence the solution is still a distanced-based one.
To solve this, all I had to do is to measure the length of X and Y coordinates separately, and compound the time remap offset using the individual distance. Below is a demonstration of how the layers appear row by row and also the opacity expression that perform this function:
//user variables
tgt = thisComp.layer("control");
delayX = 1;
delayY = 2;
distanceX = 1920;
distanceY = 400;
// main
d1 = length(tgt.toWorld(tgt.transform.anchorPoint)[0],transform.position[0])
d2 = length(tgt.toWorld(tgt.transform.anchorPoint)[1],transform.position[1])
offsetx = linear(d1,0,distanceX,0,delayX);
offsety = linear(d2,0,distanceY,0,delayY);
//main
time-offsetx-offsety
Abstract code type-on
For this shot, it was done by making animation rig that generate unique combinations of layers automatically using expressions and Master Properties. How this works is that:
- A unique number combination is generated using a text layer and expression (as shown below).
- Random shape layers in a pre-composition are then accessed through the Time Remap using the values from number combination as frame number.
- The setup is then rigged with Master Properties to create more variants by modifying the seed value.
Generate unique number combination
var nums = [1,2,3,4,5,6,7,8,9,10],//all numbers to be randomized
ranNums = [],
i = nums.length,
j = 0;
while (i--) {
j = Math.floor(Math.random() * (i+1));
ranNums.push(nums[j]);
nums.splice(j,1);
}
ranNums
Credits
Creative Direction
Desmond Du
Art Director
Anna Yang
Technical Direction
Desmond Du
Animation
Desmond Du
Sabrina Guyton