1. Study in Spirographics

1.1 Goal

First, checkout this animated experiment called "Orbits" by Alexey Lebedev:

http://explord.com/experiments/orbits/

How would you describe, in the abstract, how these animations are made?

Insert insight here.

This is very similar to a a toy many had named Spirograph:

Our goal for Assignment #3 is to create art that can be seen as tracing the trajectory of an object through space and time. We'll look at two methods for making such art: Spirographics and Physics Simulations. This notebook looks at the first.

1.2 Preliminaries

1.2.1 Defining a function that returns a value

Before we begin, we need to learn a new technique in Processing: how to write a function that returns something. There are two things you need:

  1. A return type on the function definition (replace void with something like int or float).
  2. Use the return keyword in the function definition

Example:

Consider the concept distance from geometry. Do you remember the formula?

It is simply stated as: the square root of the sum of the squared differences of each dimension (eg, x and y). It comes from the relationship:

$ c^2 = a^2 + b^2 $

where a, b, and c are the lengths of a triangle with 90 degree angle:

If we "solve for c" then we take the square root of each side, and get:

$ c = \sqrt{ a^2 + b^2 }$

The length $a$ is the distance between points B and C on the y axis. That is, you just take the difference of the y's of B and C. Likewise, the length $b$ is the distance between points $A$ and $C$ on the x axis.

Let's break this down:

First, consider that we have two points (x1, y1) and (x2, y2). We want to know how far apart they are.

  1. First, we find the differences of each dimension: x1 - x2 and y1 - y2
  2. Then we square each of those. That is, we raise those difference to the 2nd power. Or, more simply, we just multiply each times itself: (x1 - x2) * (x1 - x2) and (y1 - y2) * (y1 - y2)
  3. Finally, we take the square root of the sum of those, using the sqrt function

Putting those all together looks like:

sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)))

The last thing we need to do is put that in a function definition:

float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)))
}

Note that we have defined a new function, called distance that takes 4 numbers (x1, y1, x2, y2) and returns the distance between those two points. To call the function, we put parentheses after the function name, pass in arguments, and save the returned value in $d$:

d = distance(50, 50, 10, 10);

Let's try it:

In [1]:
float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}

void setup() {
    background(200);
}

void draw() {
    stroke(0);
    line(50, 50 - 10, 50, 50 + 10);
    line(50 - 10, 50, 50 + 10, 50);
}

void mousePressed() {
    background(200);
    println( distance(50, 50, mouseX, mouseY));
    stroke(255, 0, 0);
    line(50, 50, mouseX, mouseY);
}
Sketch #1:

Sketch #1 state: Loading...

1.2.2 Loops

For this assignment, it would be handy if we had a method of doing something a specific number of times. For example, say we want to rotate a line about the origin 360 times, once for each degree.

We could do this like:

drawLine(0);
drawLine(1);
drawLine(2);
drawLine(3);
drawLine(4);
drawLine(5);
...
drawLine(359);

But that is a lot to type! Rather, we can use the for-loop in Processing to do this for us:

for (int i=0; i < 360; i++) {
    drawLine(i);
}

The two methods are exactly the same. Except one is a lot easier! The for-loop is a bit of a weird one: it looks like a function that has three arguments separated by semicolons. The meanings of the three parts are:

for (START_STATEMENT; CONTINUE_CRITERIA; INCREMENT_STATEMENT) {
}

To understand, it might we easier to see this as a while-loop:

START_STATEMENT;
while (CONTINUE_CRITERIA) {
    INCREMENT_STATEMENT;
}
int i=0;
while (i < 360) {
    drawLine(i);
    i++;
}

START_STATEMENT - the Processing statement used to define and initialize the loop variable

CONTINUE_CRITERIA - the Processing boolean expression used to signal that we should stop looping. The for-loop will continue until this boolean expression is no longer true

INCREMENT_STATEMENT - the Processing statement used to increment the loop variable

Examples:

for (int i=0; i < 360; i++) {
    drawLine(i);
}

int i=0; 
while (i < 360) {
    drawLine(i);
    i++;
}

1.3 Trigonometry

In order to replicate this experiment, we first need to be able to rotate a point around another point. We need just a touch of trig. For this explanation, we refer to the book The Nature of Code Copyright © 2012 by Daniel Shiffman, licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License.

<img src="http://natureofcode.com/book/imgs/chapter03/ch03_08.png"/ width="100%">

We see that there are functions named sin (sine) and cos (cosine) that define the relationships y/r and x/r of a right triangle. But we are interested in a triangle where we know $\theta$ (theta) and $r$ but we want to find the $x$ and $y$. To find $x$ and $y$ we merely need to solve for each, by multiplying the given relationships by $r$:

$sin(\theta) = \dfrac{y}{r}$

$sin(\theta) \times r = \dfrac{y}{r} \times r$

$y = sin(\theta) \times r$

and likewise:

$x = cos(\theta) \times r$

1.4 Rotating a line around the center

To use this is in Processing, we will add the center position to the point, to make it rotate around the center. Notice to that we will add x + cos(angle) to go to the right, but we will subtract y - sin(angle) to go up.

In [8]:
int cwidth = 200;
int cheight = 200;

void setup() {
    size(cwidth, cheight);
}

void draw() {
    float cx = cwidth/2;
    float cy = cheight/2;
    
    float length = cwidth/2 * 3/4;
    float x = cx + length;
    float y = cy;
    for (float angle = 0; angle < 2 * PI; angle = angle + PI/8) {
        line(cx, cy, x, y);
        x = cx + length * cos(angle);
        y = cy - length * sin(angle);
    }
    noLoop(); // this will make it so that draw() is not called anymore
}
Sketch #8:

Sketch #8 state: Loading...

Let's move the cos and sin operations into functions called rotateAroundX and rotateAroundY, respectively.

In [10]:
int cwidth = 200;
int cheight = 200;

void setup() {
    size(cwidth, cheight);
}

float rotateAroundX(float x1, float y1, float length, float angle) {
    return x1 + length * cos(angle);
}

float rotateAroundY(float x1, float y1, float length, float angle) {
    return y1 - length * sin(angle);
}

float distance(float x1, float y1, float x2, float y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

void draw() {
    float cx = cwidth/2;
    float cy = cheight/2;
    
    float length = cwidth/2 * 3/4;
    float x = cx + length;
    float y = cy;
    for (float angle = 0; angle < 2 * PI; angle = angle + PI/50) {
        line(cx, cy, x, y);
        float ox = x; // original x
        float oy = y; // original x
        x = rotateAroundX(cx, cy, distance(cx, cy, ox, oy), angle);
        y = rotateAroundY(cx, cy, distance(cx, cy, ox, oy), angle);
    }
    noLoop(); // this will make it so that draw() is not called anymore
}
Sketch #3:

Sketch #3 state: Loading...

1.5 Rotating two lines

To replicate this experiment, we want to rotate one line around the origin, and a second line around the end of the first line. In the following diagram, we rotate $r$ around the center, and we rotate $\rho$ (rho) around the end point of $r$:

We will have two rotating lines:

  1. from the center (cx, cy) to (x, y) [blue lines]
  2. from (x, y) to (ex, ey) [red lines]

We will also draw a black line that connects (ex, ey) through time:

In [11]:
int cwidth = 200;
int cheight = 200;

void setup() {
    size(cwidth, cheight);
}

float rotateAroundX(float x1, float y1, float length, float angle) {
    return x1 + length * cos(angle);
}

float rotateAroundY(float x1, float y1, float length, float angle) {
    return y1 - length * sin(angle);
}

void draw() {
    float cx = cwidth/2;
    float cy = cheight/2;
    
    float length = 50;
    float x = cx + length;
    float y = cy;
    
    float ex = cx + length + 20;
    float ey = cy;

    float pex = ex;
    float pey = ey;
    
    for (float angle = 0; angle <= PI * 2; angle += PI/50) {
        stroke(0, 0, 255);
        line(cx, cy, x, y);
        stroke(255, 0, 0);
        line(x, y, ex, ey);
        stroke(0);
        x = rotateAroundX(cx, cy, length, angle);
        y = rotateAroundY(cx, cy, length, angle);
        ex = rotateAroundX(x, y, 20, angle);
        ey = rotateAroundY(x, y, 20, angle);
        line(pex, pey, ex, ey);
        pex = ex;
        pey = ey;
    }
    noLoop(); // this will make it so that draw() is not called anymore
}
Sketch #11:

Sketch #11 state: Loading...

That is weird... it looks just like the circle we drew earlier. Why? Oh, it is because we are rotating the second line at exactly the same amount at the same rate as the first line. If change lines 38 and 39:

ex = rotateAroundX(x, y, 20, angle);
ey = rotateAroundY(x, y, 20, angle);

to

ex = rotateAroundX(x, y, 20, angle * 2);
ey = rotateAroundY(x, y, 20, angle * 2);

then the second line will go around the first at twice the rate:

In [12]:
int cwidth = 200;
int cheight = 200;

void setup() {
    size(cwidth, cheight);
}

float rotateAroundX(float x1, float y1, float length, float angle) {
    return x1 + length * cos(angle);
}

float rotateAroundY(float x1, float y1, float length, float angle) {
    return y1 - length * sin(angle);
}

void draw() {
    float cx = cwidth/2;
    float cy = cheight/2;
    
    float length = 50;
    float x = cx + length;
    float y = cy;
    
    float ex = cx + length + 20;
    float ey = cy;

    float pex = ex;
    float pey = ey;
    
    for (float angle = 0; angle <= PI * 2; angle += PI/50) {
        stroke(0, 0, 255);
        line(cx, cy, x, y);
        stroke(255, 0, 0);
        line(x, y, ex, ey);
        stroke(0);
        x = rotateAroundX(cx, cy, length, angle);
        y = rotateAroundY(cx, cy, length, angle);
        ex = rotateAroundX(x, y, 20, angle * 2);
        ey = rotateAroundY(x, y, 20, angle * 2);
        line(pex, pey, ex, ey);
        pex = ex;
        pey = ey;
    }
    noLoop(); // this will make it so that draw() is not called anymore
}
Sketch #5:

Sketch #5 state: Loading...

Let's try the same thing, but make the second line rotate 6 times faster:

In [4]:
int cwidth = 200;
int cheight = 200;

void setup() {
    size(cwidth, cheight);
}

float rotateAroundX(float x1, float y1, float length, float angle) {
    return x1 + length * cos(angle);
}

float rotateAroundY(float x1, float y1, float length, float angle) {
    return y1 - length * sin(angle);
}

void draw() {
    float cx = cwidth/2;
    float cy = cheight/2;
    
    float length = 50;
    float x = cx + length;
    float y = cy;
    
    float ex = cx + length + 20;
    float ey = cy;

    float pex = ex;
    float pey = ey;
    
    for (float angle = 0; angle <= PI * 2; angle += PI/50) {
        stroke(0, 0, 255);
        line(cx, cy, x, y);
        stroke(255, 0, 0);
        line(x, y, ex, ey);
        stroke(0);
        x = rotateAroundX(cx, cy, length, angle);
        y = rotateAroundY(cx, cy, length, angle);
        ex = rotateAroundX(x, y, 20, angle * 6);
        ey = rotateAroundY(x, y, 20, angle * 6);
        line(pex, pey, ex, ey);
        pex = ex;
        pey = ey;
    }
    noLoop(); // this will make it so that draw() is not called anymore
}
Sketch #4:

Sketch #4 state: Loading...

Finally, let's make it so that we get rid of the for-loop. In order to do this we:

  • Move all variables in for() code block to global variables
  • Initialize them in the setup() function
  • Replace the for-loop with a draw() funtion

Now you can restart the drawing interactively.

In [13]:
int cwidth = 200;
int cheight = 200;
float cx; 
float cy; 
float length; 
float x; 
float y; 
float ex; 
float ey; 
float pex;
float pey;
float angle;

void setup() {
    size(cwidth, cheight);
    background(200);
    cx = cwidth/2;
    cy = cheight/2;
    length = 50;
    x = cx + length;
    y = cy;
    ex = cx + length + 20;
    ey = cy;
    pex = ex;
    pey = ey;   
    angle = 0;
}

float rotateAroundX(float x1, float y1, float length, float angle) {
    return x1 + length * cos(angle);
}

float rotateAroundY(float x1, float y1, float length, float angle) {
    return y1 - length * sin(angle);
}

void draw() {
    stroke(0, 0, 255);
    line(cx, cy, x, y);
    stroke(255, 0, 0);
    line(x, y, ex, ey);
    stroke(0);
    x = rotateAroundX(cx, cy, length, angle);
    y = rotateAroundY(cx, cy, length, angle);
    ex = rotateAroundX(x, y, 20, angle * 6);
    ey = rotateAroundY(x, y, 20, angle * 6);
    line(pex, pey, ex, ey);
    pex = ex;
    pey = ey;
    angle += PI/50;
    delay(100);
}

void delay(int delay) {
  int start_time = millis();
  while(millis() - start_time <= delay);
}
Sketch #13:

Sketch #13 state: Loading...

1.6 Conclusion

In this notebook we have seen how you can rotate a point about the origin, rotate a line about a point, and rotate a line around a moving point. There are many variations you can try:

  • don't draw line 1 and line 2
  • change the lengths of line 1 and line 2
  • change the rotation speeds (even go negative)
  • try different colors
  • more rotating lines
  • rotate planets around the sun; moon around planet
  • something completely different!