Andrew Russell

21 Jan 2014

A while ago, I came upon a well thought out blog that detailed an introduction to the Fourier transform. Now, I first learned about the Fourier transform years ago myself, and have used it for years, so the introduction was repetition for me (I highly recommend this post for those who want to learn about it). However, there was one link at the bottom which was really interesting. Fourier Toy. An interactive guide to the Fourier transform by lucasvb. With this tool, you can control the amplitude and phase of eight sin/cos waves, and see the direct result to the Fourier transform through a nice visualization.

Remembering how I was able to get mesmerized by this interactive tool, I decided this was the perfect place for me to start my lenticular animation. I started by adjusting the waves in the tool to get a feel for what designs could be made with only eight waves (theoretically, any complex drawing can be made with infinite waves). After, I started to sketch designs that I thought would work with only 10 frames of animation. The results can be seen below.

IMG_20140121_091644[1]

The page of sketches show a failed design (top), the actual design (left), the design for each circle (right), and the amplitude and phases for each wave (bottom).

I plugged the amplitudes and phases into Fourier Toy to make sure that I had gotten them right. I then started working on the Processing code. I started with the idea that I will iteratively draw circles with each new iteration’s circle being based on the last iteration’s location. The code for drawing the circle was similar to Golan’s sample code to draw the spokes rotating around a square.

Here is the smooth GIF. Processing actually computed 1000 frames, but I only saved every 10th frame for a total of 100 frames. I did this since I needed more frames to make the curves between frames smoother, but 1000 frames was way too many (megabytes large GIF?? No thanks).

long

Here is the 10 frame version. The same concept as above was applied, except that every 100th frame was selected for just 10 frames. I believe that this will work even better on the lenticular animation since each frame will blur into the next one.

short

Here is the code.

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
final int NUMBER_FRAMES = 1000;
final int TRAIL_SIZE = NUMBER_FRAMES / 5;
final boolean IS_GIF = true;
final int SIZE = 1500;
 
final float[] AMPLITUDES = {0.75, 0, 0.25, 0.947, 0.15, 0, 0.107, 0};
final float[] PHASES = {0, 0, 0, -3.3, 0, 0, 0, 0, 0};
 
int current_frame = 0;
boolean recording = false;
 
ArrayList last_px = new ArrayList();
ArrayList last_py = new ArrayList();
 
void setup() {
  size(SIZE, SIZE); 
  frameRate(NUMBER_FRAMES);
}
 
void keyPressed() {
  recording = true;
  current_frame = 0;
  last_px.clear();
  last_py.clear();
}
 
void draw() {
  float percentComplete = 0; 
  if (recording) {
    percentComplete = (float) current_frame / (float)NUMBER_FRAMES;
  } else {
    float modFrame = (float) (frameCount % NUMBER_FRAMES);
    percentComplete = modFrame / (float)NUMBER_FRAMES;
  }
 
  renderMyDesign(percentComplete);
 
  if (recording) {
    if (IS_GIF && current_frame % (NUMBER_FRAMES / 10) == 0) {
      saveFrame("output/short-" + nf(current_frame, 4) + ".png");
    } else if(!IS_GIF && current_frame % (NUMBER_FRAMES / 100) == 0) {
      saveFrame("output/frame-" + nf(current_frame, 4) + ".png");
    }
 
    current_frame++;
    if (current_frame == NUMBER_FRAMES + 201) {
      recording = false;
    }
  }
}
 
void renderMyDesign (float percent) {
  background(240);
  smooth();
  strokeWeight(2);
 
  fill(0, 0, 0, 0);
 
  float start_angle = -percent * TWO_PI;
  float px = SIZE / 2.0;
  float py = SIZE / 2.0;
 
  for (int i = 0; i < 8; i++) {
    float angle = start_angle * (i + 1) - PHASES[i];
    float radius = AMPLITUDES[i] * (SIZE / 5.0);
    float cx = px;
    float cy = py;
 
    px = cx + radius * cos(angle);
    py = cy + radius * sin(angle);
 
    stroke(0, 0, 0, 45);
    ellipse(cx, cy, radius * 2, radius * 2);
    ellipse(cx, cy, 4, 4);
 
    stroke(200, 150, 150, 150);
    line(cx,cy, px, py);
  }
 
  last_px.add(px);
  last_py.add(py);
 
  for (int i = 1; i < last_px.size(); i++) {     float opacity = i * 255.0 / TRAIL_SIZE;     stroke(0, 0, 200, opacity);     line(last_px.get(i - 1), last_py.get(i - 1), last_px.get(i), last_py.get(i));   }      while (last_px.size() > TRAIL_SIZE) {
    last_px.remove(0);
    last_py.remove(0);
  }
}