Oscillation

Using Processing's trigonomic functions to create interesting motion in our sketches

Dexter Shephrd

Creative Coder working with web technologies

Adjunct Faculty @ CalArts Music Technology program

@dexterwritescode

dexterjshepherd@gmail.com

Key concepts

  • Sine - sin()
  • Cosine - cos()
  • Polar vs Cartesian Coordinates - degrees() & radians()
  • Translation - translate()
  • Rotation - rotate()
  • Interpolation - map()

Sine & Cosine

In math terms...

Sine:

the trigonometric function that is equal to the ratio of the side opposite a given angle (in a right triangle) to the hypotenuse.

Cosine:

the trigonometric function that is equal to the ratio of the side adjacent to a given angle (in a right triangle) to the hypotenuse.

Red wave = sin(time)

Blue wave = cos(time)

In creative coding terms

Sine and Cosine are smoothly oscillating values between -1 and 1

Some examples

ellipse(sin(frameCount * 0.1) * 100, height / 4, 10, 10);

Sine function

sin(frameCount * 0.1) * 100

Frequency ( speed of oscillation )

sin(frameCount * 0.1) * 100

Amplitute ( range of oscillation )

sin(frameCount * 0.1) * 100

Proxy for time

sin(frameCount * 0.1) * 100

frameCount is a global variable in p5js


              for(let i = 0; i < 20; i ++) {
                ellipse(i * 20, sin(i) * 20, 10, 10);
              }
            

Distance between points on the wave

( sample rate )


              for(int i = 0; i < 20; i ++) {
                ellipse(i * 20, sin(i) * 20, 10, 10);
              }
            

Proxy for time


              for(int i = 0; i < 20; i ++) {
                ellipse(i * 20, sin(i) * 20, 10, 10);
              }
            

Amplitude


              for(int i = 0; i < 20; i ++) {
                ellipse(i * 20, sin(i) * 20, 10, 10);
              }
            

Animation


            for(let i = 0; i < 20; i ++) {
              ellipse(i * 20, sin(i + frameCount * 0.1) * 20, 10, 10);
            }
          
Use frameCount or some other changing variable ( another sine or cosine function?!?! ) to animate the wave phase over time. If your frequency is to high, multiply by some small constant

Degrees vs Radians

Question


            // rotate() will rotate the canvas clockwise
            rotate(???)
            for(let i = 0; i < 5; i ++ ) {
              rect(i * 10, height / 2, 10, 10);
             }
            

What input to the rotate function will change the left image to the right image?

Answer


            rotate(PI/4)
            for(let i = 0; i < 5; i ++ ) {
              rect(i * 10, height / 2, 10, 10);
            }
            

A circle has 360 degrees

A circle has Pi * 2 ( Tau ) radians

All processing functions dealing with angles will expect radians

You can convert between degrees and radians with the degrees() and radians() functions or change the expected arguments globally with the angleMode() function.

But you probably won't want to.

Some more examples

Draw points equidistant on a circle


            const radius = 100;
            const numPoints = 20;
            for(let i = 0; i < numPoints; i ++) {
              let angle = map(i, 0, numPoints, 0, TWO_PI);
              let x = sin(angle) * radius;
              let y = cos(angle) * radius;
              ellipse(x, y, 5, 5);
            }
            

            const radius = 100;
            const numPoints = 10;
            for(let i = 0; i < numPoints; i ++)  {
              let angle = map(i, 0, numPoints, 0, TWO_PI);
              let x = sin(angle) * radius;
              let y = cos(angle) * radius;
              ellipse(x, y, 5, 5);
            }
            

use the map function to convert the range 0 - numPoints to 0 - Tau


            const radius = 100;
            const numPoints = 10;
            for(let i = 0; i < numPoints; i ++)  {
              let angle = map(i, 0, numPoints, 0, TWO_PI);
              let x = sin(angle) * radius;
              let y = cos(angle) * radius;
              ellipse(x, y, 5, 5);
            }
            

sin and cos will return values between -1 and 1 so multiply by radius and use as x and y coords for the circle.

Plot points along one cycle of a wave


            const amplitude = 50;
            const numPoints = 20;
            for(let i = 0; i < numPoints; i ++)  {
              let angle = map(i, 0, numPoints, 0, TWO_PI);
              let pos = sin(angle) * amplitude;
              ellipse(i * 10, pos, 5, 5);
            }
            

Rotate the canvas according to the mouse


            let angle = map(mouseX, 0, width, 0, TWO_PI);
            rotate(angle);
            

            let angle = map(mouseX, 0, width, 0, TWO_PI);
            rotate(angle);
            

Use the map function to change any arbitrary stream of values into a useable angle.

Translate, Rotate, Push, and Pop

Last steps ...

What does rotate() actually do?

Let's make a spinning rectangle!


            rotate(frameCount * 0.1);
            rect(width / 2, height / 2, 100, 100);
            

Instead of spinning rectangle, we see the rectangle spin off screen and reappear on the other side some time later?

The rotate function rotates the canvas around the point of origin, in our case, (0, 0) - the top left corner of the screen.

in order to rotate the rectange around its center point, we need to change the origin point. To do this we need ...

translate() !


              translate(width / 2, height / 2);
              rotate(frameCount * 0.1);
              rect(0, 0, 100, 100);
            

              translate(width / 2, height / 2);
              rotate(frameCount * 0.1);
              rect(0, 0, 100, 100);
            

we use the translate function to change the origin from (0, 0) to ( width / 2, height /2 ).


              translate(width / 2, height / 2);
              rotate(frameCount * 0.1);
              rect(0, 0, 100, 100);
            

By rotating after the translation we rotate around the new origin instead of (0, 0).


              translate(width / 2, height / 2);
              rotate(frameCount * 0.1);
              rect(0, 0, 100, 100);
            

Becuase we translated the origin to ( width / 2, height / 2 ) we can draw the rect at ( 0, 0 ) - the new origin.

Stacked translations and rotations


            translate(width * 0.25, height * 0.25);
            rect(0, 0, 20, 20 );
            translate(width * 0.25, height * 0.25);
            rect(0, 0, 20, 20 );
            translate(width * 0.25, height * 0.25);
            rect(0, 0, 20, 20 );
          

We can stack rotations and translations together by calling the functions multiple times.

All translation and rotations are reset at the end of the draw loop


            translate(width * 0.5, 0);
            for(let i = 0; i < 20; i ++) {
              let angle = map(i, 0, 20, 0, TWO_PI);
              for(let j = 0; j < 10; j++ ) {
                translate(10, 15);
                rotate(angle / 10);
                rect(0, 0, j + 2 * 0.5, j + 2 * 0.5);
              }
            }
          

Using for and while loops, we can create complex patterns by repeating translate and rotate manipulations

But sometimes we don't want our translations to stack


            rectMode(CENTER);
            translate(width * 0.25, 0);
            for(int i = 0; i < 10; i++) {
              pushMatrix();
              float x = map(i, 0, 10, 0, width * 0.5);
              translate(x, height * 0.5);
              if ( i % 2 == 0 ) {
                rotate(frameCount * 0.01);
              } else {
                rotate(frameCount * -0.01);
              }
              rect(0, 0, 50, 50);
              popMatrix();
            }
            

so we use pushMatrix() and popMatrix() to reset the translation and rotation matrices

Demo ...

p5 sketches