Jupyter at Bryn Mawr College |
|||
Public notebooks: /services/public/dblank / CS110 Intro to Computing / 2017-Spring / Lectures |
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.
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:
void
with something like int
or float
).return
keyword in the function definitionExample:
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.
x1 - x2
and y1 - y2
(x1 - x2) * (x1 - x2)
and (y1 - y2) * (y1 - y2)
sqrt
functionPutting 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)));
}
or:
float distance(float x1, float y1, float x2, float y2) {
return sqrt(sq(x1 - x2) + sq(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:
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);
}
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++;
}
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$
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.
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 * 0.75;
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
}
Let's move the cos and sin operations into functions called rotateAroundX
and rotateAroundY
, respectively.
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
}
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$:
<img src="http://upload.wikimedia.org/wikipedia/commons/3/38/Spirograph.png"/ width="100%">
We will have two rotating lines:
We will also draw a black line that connects (ex, ey) through time:
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
}
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:
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
}
Let's try the same thing, but make the second line rotate 6 times faster:
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
}
Finally, let's make it so that we get rid of the for-loop. In order to do this we:
Now you can restart the drawing interactively.
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 * 11.2);
ey = rotateAroundY(x, y, 20, angle * 11.2);
line(pex, pey, ex, ey);
pex = ex; // previous ex
pey = ey; // previous ey
angle += PI/50;
//delay(100);
}
void delay(int delay) {
int start_time = millis();
while(millis() - start_time <= delay);
}
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: