joxin-AnimatedLoop

Masks

Emotions are a curious thing. They come and go in waves, often unexpected. Recently I read two very beautiful plays, Madea and Matsukaze. I learned about the masks used in ancient Greek theater and Japanese Noh theater to embody emotions and characters. They are abstract, dramatic representations of joy and suffering. I enjoy the simple and mysterious look, as well as the symbolic power.

Inspired by the masks, I decided to create a loop that signifies the cycle of emotions. Points vaguely define the visage of happiness and sadness. The smiling mask flips to the crying mask, and flips back. The points diffuse during the the flip, and then re-construct.

On one hand I want to strive for simplicity, but I also feel this overall graphics looks a bit “thin.” The concept is straight-forward, but there isn’t much to savor. I considered creating a grid of masks flipping together, but then I realized it wouldn’t really mean anything. There are other visual effects that I could add on top, but nothing feels like a plus with conceptual significance.

I thought this could be a fun screensaver, like this. Just playing with scale.

The implementation goes like this: I scan the pixels of two high-contrast images of masks, and generate a point cloud that looks like a mask based on the brightness values. With the rotation of camera, I create the illusion of face flipping.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// This is a template for creating a looping animation in Processing/Java. 
// When you press the 'F' key, this program will export a series of images
// into a "frames" directory located in its sketch folder. 
// These can then be combined into an animated gif. 
// Known to work with Processing 3.3.6
// Prof. Golan Levin, January 2018
 
// Thanks to help and feedback from Cameron Burgess, Faith Kim and Aman Tiwari <3
 
//===================================================
// Global variables. 
String  myNickname = "joxin"; 
int     nFramesInLoop = 45;
int     nElapsedFrames;
boolean bRecording; 
 
PImage smile, cry;
ArrayList dots;
float scale = 1.8;
float randomFactor = 3.5;
float gaussianFactor = 13.0;
int step = 16;
float colorThreshold = 160;
 
//===================================================
void setup() {
  bRecording = false;
  nElapsedFrames = 0;
  size(600, 600, P3D);
  pixelDensity(2);
  background(0);
  smile = loadImage("smile.jpg");
  cry = loadImage("cry.jpg");
  smile.loadPixels();
  cry.loadPixels();
  dots = new ArrayList();
  for (int x = 0; x < cry.width; x += step) {
    for (int y = 0; y < cry.height; y += step) { color c_cry = cry.get(x, y); color c_smile = smile.get(x, y); float x1 = 0; float y1 = 0; float x2 = 0; float y2 = 0; //float z = randomGaussian() * gaussianFactor - gaussianFactor/2; float z = randomGaussian() * gaussianFactor - gaussianFactor/2; if (red(c_cry) > colorThreshold) {
        x1 = x/scale ;//+ random(-randomFactor, randomFactor);
        y1 = y/scale ;//+ random(-randomFactor, randomFactor);
      }
      if (red(c_smile) > colorThreshold) {
        x2 = x/scale ;//+ random(-randomFactor, randomFactor);
        y2 = y/scale ;//+ random(-randomFactor, randomFactor);
      }
 
      dots.add(new Dot(x1, y1, x2, y2, z, c_cry, c_smile));
    }
  }
 
}
 
//===================================================
void keyPressed() {
  if ((key == 'f') || (key == 'F')) {
    bRecording = true;
    nElapsedFrames = 0;
  }
}
 
//===================================================
void draw() {
 
  // Compute a percentage (0...1) representing where we are in the loop.
  float percentCompleteFraction = 0; 
  if (bRecording) {
    percentCompleteFraction = (float) nElapsedFrames / (float)nFramesInLoop;
  } else {
    percentCompleteFraction = (float) (frameCount % nFramesInLoop) / (float)nFramesInLoop;
  }
 
  // Render the design, based on that percentage. 
  renderMyDesign (percentCompleteFraction);
 
  // If we're recording the output, save the frame to a file. 
  if (bRecording) {
    saveFrame("frames/" + myNickname + "_frame_" + nf(nElapsedFrames, 4) + ".png");
    nElapsedFrames++; 
    if (nElapsedFrames >= nFramesInLoop) {
      bRecording = false;
    }
  }
}
 
 
void renderMyDesign (float percent) {
  background(0);
  lights();
 
  float angle = 0;
  if (percent <= 0.5) {
    float eased = function_DoubleExponentialSigmoid (percent*2, 0.7);
    angle = map(eased, 0, 1, -PI/2, PI/2);
 
  } else {
    float eased = function_DoubleExponentialSigmoid (percent*2-1, 0.7);
    angle = map(eased, 0, 1, PI/2, 3*PI/2);
  }
 
  camera(width/2+cos(angle)*width, 
         height/2, 
         sin(angle)*width, // eyeX, eyeY, eyeZ
         width/2, height/2, 0.0, // centerX, centerY, centerZ
         0.0, 1.2, 0.0); // upX, upY, upZ}
 
  for (int i = 0; i < dots.size(); i++) {
    Dot d = dots.get(i);
    if (d != null) {
      d.show(percent);
    }
  }
 
}
 
class Dot { 
  float x1, y1, x2, y2, z, c1, c2; 
  Dot (float _x1, float _y1, float _x2, float _y2, float _z, float c_cry, float c_smile) {  
    x1 = _x1;
    y1 = _y1;
    x2 = _x2;
    y2 = _y2;
    z = _z;
    c1 = c_cry;
    c2 = c_smile;
  } 
 
  void show(float percent) { 
    float angleX, angleY, angleZ;
    if (percent <= 0.5) {
      float easedX = function_DoubleExponentialSigmoid (percent*2, 0.7);
      float easedY = function_DoubleExponentialSigmoid (percent*2, 0.6);
      float easedZ = function_DoubleExponentialSigmoid (percent*2, 0.5);
      angleX = map(easedX, 0, 1, -PI/2, PI/2);
      angleY = map(easedY, 0, 1, -PI/2, PI/2);
      angleZ = map(easedZ, 0, 1, -PI/2, PI/2);
 
    } else {
      float easedX = function_DoubleExponentialSigmoid (percent*2-1, 0.7);
      float easedY = function_DoubleExponentialSigmoid (percent*2-1, 0.6);
      float easedZ = function_DoubleExponentialSigmoid (percent*2-1, 0.5);
      angleX = map(easedX, 0, 1, PI/2, 3*PI/2);
      angleY = map(easedY, 0, 1, PI/2, 3*PI/2);
      angleZ = map(easedZ, 0, 1, PI/2, 3*PI/2);
    }
 
    float maxScaleZ = 1.2;
    float maxScaleXY = 1;
    if (angleX < 0 || angleX > PI) {
      float expandFactor = 1;
      if (angleX < 0) { expandFactor = map(abs(angleX - 0), 0, PI/2, maxScaleZ, 0); } else { expandFactor = map(abs(angleX - PI), 0, PI/2, maxScaleZ, 0); } float otherExpandFactor = map(expandFactor, maxScaleZ, 1, maxScaleXY, 1); float curZ = map(z, 0, 1, 0, expandFactor); if (x1 > 0) {
        pushMatrix();
        translate(width/2 - cry.width/2.0/scale*otherExpandFactor + x1*otherExpandFactor, 
                  height/2 - cry.height/2.0/scale*otherExpandFactor + y1*otherExpandFactor, 
                  curZ);
        noStroke();
        sphere(1.8);
        popMatrix();
      }
    } else {
      if (x2 > 0) {
      float expandFactor = 1;
        if (angleX < PI/2) {
          expandFactor = map(abs(angleX - 0), 0, PI/2, maxScaleZ, 0);
        } else {
          expandFactor = map(abs(angleX - PI), 0, PI/2, maxScaleZ, 0);
        }
        float otherExpandFactor = map(expandFactor, maxScaleZ, 1, maxScaleXY, 1);
        float curZ = map(z, 0, 1, 0, expandFactor);
        pushMatrix();
        translate(width/2 - smile.width/2.0/scale*otherExpandFactor + x2*otherExpandFactor, 
                  height/2 - smile.height/2.0/scale*otherExpandFactor + y2*otherExpandFactor, 
                  curZ);
        noStroke();
        sphere(1.8);
        popMatrix();
      }
    }
  } 
 
  void setZ(float zz) {
    z = zz;
  }
}
 
//===================================================
// Taken from https://github.com/golanlevin/Pattern_Master
float function_DoubleExponentialSigmoid (float x, float a) {
  // functionName = "Double-Exponential Sigmoid";
 
  float min_param_a = 0.0 + EPSILON;
  float max_param_a = 1.0 - EPSILON;
  a = constrain(a, min_param_a, max_param_a); 
  a = 1-a;
 
  float y = 0;
  if (x<=0.5) {
    y = (pow(2.0*x, 1.0/a))/2.0;
  } else {
    y = 1.0 - (pow(2.0*(1.0-x), 1.0/a))/2.0;
  }
  return y;
}