September 14-16

September 14

We reviewed material that’s already discussed in the September 11 notes, including:

  • Counting. Generally, computer scientists and computer programs count from zero, so to count 3 things, count 0, 1, 2. To count N things, count 0, 1, … N-1. See Sept. 11 notes on counting and templates for for loops.
  • Using declarations in for loops: Write “for (var i = 0; i
  • Random squares: see code in Sept. 11 notes.
  • Iteration – we made some examples do draw lots of random rectangles to illustrate for loops.
  • we talked about clearing the screen. The background() function clears the screen. You can call it in setup(), in draw(), or even inside an if statement depending on what you want to accomplish, and we saw examples of each of these cases.

We went over order of evaluation of sketch.js programs:

  1. Compile the program, evaluating all global variable declarations and initializations.
  2. Define all the p5.js library functions and variables such as random() and PI.
  3. Call setup().
  4. Call draw() repeatedly.

The point here is that random() and PI and other p5.js functions are not available for global variable initializations. Rather than writing “var myVar = random(10);” at the top of your program, write “var myVar;” at the top, and in setup (where random() will be defined), write “myVar = random(10);”.

We looked at rotations. A good tutorial is this one, although it is for Java/Processing rather than JavaScript. The principles are the same. See your textbook too: Chapter 6.

September 16

We started with a review of rotation: Make a square rotate in the center of the screen. The tutorial mentioned above is very nice (this one, although it is for Java/Processing rather than JavaScript). Here’s the example we did in class:

Rotation/Translate Example

rotation sketch

var gAngle = 0;

function setup() {
    createCanvas(100, 100);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    push();
    translate(width / 2, height / 2);
    rotate(radians(gAngle));
    rectMode(CENTER);
    rect(0, 0, 50, 50);
    pop();
    gAngle = gAngle + 1;
}

This example is slightly changed from class:

  • The canvas size is smaller — how do you control the canvas size?
  • Why is the box still in the middle of the canvas even though the canvas size changed?
  • The angle variable was changed to gAngle.

Global Variables Names

In future examples, I will be using the notation “gSomething” to mean global variables. The reason is that p5js defines a lot of global variables such as top, width, height, and scrollX — it’s just dangerous to pick a name for your own variable and hope there will not be a conflict. the “gSomething” notation (in the case, gAngle) safely generates non-p5js names and helpfully reminds the reader that the variables are global. You can use any naming scheme you want, but avoid simple names like top to stay out of trouble.

Iteration Examples

Next, we reviewed some iteration examples. Let’s draw 10 boxes:
sketch

function setup() {
    createCanvas(400, 100);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    var x = 50;
    for (var i = 0; i < 10; i = i + 1) {
        rect(x, 50, 10, 10);
        x = x + 20;
    }
}

Notice in this example that we completely separate the task of counting (10) boxes (with loop variable i) and deciding where to draw them (with local variable x).

Do we really need the extra variable x to compute location? I don’t think so! Try this:

pixel coordinates computed from counting variable

function setup() {
    createCanvas(400, 100);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    for (var i = 0; i < 10; i = i + 1) {
        rect(i * 20 + 50, 50, 10, 10);
    }
}

Here, we’ve used a little math (multiply to scale up to 20-pixel spacing, add to shift boxes by 50 pixels) to translate the counting variable i into a screen coordinate (formerly the x variable). Fun fact: Some programming languages like C have “optimizers” that actually convert the first program example into the second automatically to make them run faster. (Or in the old days, when multiplication hardware did not exist, “*” was really slow, so optimizers could convert the second form to the first, eliminating the multiplication!)

But do we really need to do the math to convert i from counting numbers to pixels? Why not just work with pixels directly? Here’s how that solution looks:

for loop working directly with pixel coordinates

function setup() {
    createCanvas(400, 100);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    for (var i = 50; i < 250; i = i + 20) {
        rect(i, 50, 10, 10);
    }
}

Notice here that we have to be very careful about the condition in the for loop. Quick, look at “for (var i = 50; i " and tell me how many boxes will this code draw. But it's still a good example of the flexibility of for loops.

Nested For Loops

Next, we look at nested loops. Remember that whatever is inside the for loop brackets is repeated, even another loop that draws an entire row. Here, the “outer” or first loop draws rows, and the “inner” or second embedded loop draws each box within the row.

nested loops

function setup() {
    createCanvas(400, 200);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    for (var row = 0; row < 5; row = row + 1) {
        // BEGIN ROW
        for (var i = 0; i < 10; i = i + 1) {
            rect(i * 20 + 50, row * 30 + 25, 10, 10);
        }
        // END ROW
    }
}

Notice that each rect location depends on what row it is in and on the value of i, which can be understood as the column number. What makes row a row and i a column? Try switching parameters in rect, i.e. write rect(row * 30 + 25, i * 20 + 50, 10, 10).

Here’s another way to approach the problem. Rather than changing the code to draw rows at different coordinates, let’s leave the row code the way it was, and instead use translate() to shift each row further down the screen.

using translate

function setup() {
    createCanvas(400, 200);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    for (var row = 0; row < 5; row = row + 1) {
        push();
        translate(0, row * 30);
        // BEGIN ROW
        for (var i = 0; i < 10; i = i + 1) {
            rect(i * 20 + 50, 25, 10, 10);
        }
        // END ROW
        pop();
    }
}

Notice here that the 2nd parameter to rect (the Y coordinate) is always the same (25), but the actual location on the canvas changes for each row due to the translate() call.

Sine = Sin, Cosine = Cos, Radius, and Angle

Next, we talked about expressing locations in terms of angles and radius rather than X and Y.

Introducing Sin and Cos

Cos and Sin functions tell you X and Y coordinates of a point on a circle of radius 1. The input parameter for Cos and Sin is the angle: How far to rotate around the circle. The output is where you land in terms of X (cos) and Y (sin):

ucdefp
(From “The Amazing Unit Circle”)

Here’s a graph showing how the functions sin() and cos() translate angle (here represented by the x coordinate) into a value shown as displacement in the Y axis.

sketch

function setup() {
    createCanvas(400, 120);
    frameRate(10);
}

function draw() {
    background(150, 200, 150);
    for (var x = 0; x < width; x = x + 1) {
        stroke(0);
        point(x, 60 - 50 * sin(radians(x)));
        stroke(255, 255, 0);
        point(x, 60 - 50 * cos(radians(x)));
    }
}

Rotating

Perhaps more interestingly, we can now write a program that creates circular motion by translating radius and angle into X and Y. You should understand how the following program works and why the circle moves in a circle. How would you make it go faster? How would you make the circle bigger? How would you make circle orbit bigger? What happens if the radius is zero?

sketch

function setup() {
    createCanvas(200, 200);
    frameRate(30);
}

var gAngle = 45;

function draw() {
    var centerx = width / 2;
    var centery = height / 2;
    var radius = 80;
    // line(centerx, centery, centerx + x, centery - y);
    background(150, 200, 150);
    var x = cos(radians(gAngle)) * radius;
    var y = sin(radians(gAngle)) * radius;
    line(centerx, centery, centerx + x, centery - y);
    noStroke();
    ellipseMode(CENTER);
    ellipse(centerx + x, centery - y, 10, 10);
    gAngle = gAngle + 2;
}

Spiral

Finally, change the previous example as follows:

  • Start the radius at 1, but increase the radius for each frame.
  • Move the background() call to setup() so it does not erase every frame.
  • Remove the line, just draw the circle.
  • For this web page version, call background and reset radius after a bunch of draw
    calls.

    Spiral Example

    function setup() {
        createCanvas(200, 200);
        frameRate(30);
        background(150, 200, 150);
    }
    
    var gAngle = 45;
    var gRadius = 1;
    var gFrameCount = 0;
    
    function draw() {
        var centerx = width / 2;
        var centery = height / 2;
        // line(centerx, centery, centerx + x, centery - y);
        var x = cos(radians(gAngle)) * gRadius;
        var y = sin(radians(gAngle)) * gRadius;
        noStroke();
        ellipseMode(CENTER);
        ellipse(centerx + x, centery - y, 10, 10);
        gAngle = gAngle + 2;
        gRadius = gRadius + 0.1;
        gFrameCount = gFrameCount + 1;
        if (gFrameCount > 1000) {
            gFrameCount = 0;
            gRadius = 1;
            background(150, 200, 150);
        }
    }
    

Summary

Now you know:

  • How to use translate/rotate to rotate objects (or whole drawings)
  • How to write nested for loops and draw grids and 2-D arrays.
  • How to use cos/sin to convert from angle and radius to X and Y