Introducing Control Structure and Visualisation
-
Control Structures and Visualisation
Introducing the mental model to approach assignments in class
-
Randomness and Noise
Applying randomness and noise in our work
-
Easing
Applying easing and using it with randomness
-
Utility Functions
P5.js functions you may find helpful
Control Structures and Visualisation
Generative design primarily hinges on creating algorithmic structures that can support unknown numbers. This is a difficult task as we need to create an algorithmic structure flexible enough to work with known unknowns to communicate a consistent visual style.
In this course, we separate these two factors into a control structure and visualization. Control structures are the mechanisms that formulate coordinates, color, and other values. Algorithmic structures such as for loops and if statements with unknown inputs coming from random and/or noise would make up your control structure.
Creating Randomness
Randomization of values is used to seed a design system to create generative results. Randomness applied to a structured design system may be difficult to predict but exhibit common themes. This is different than a purely random result which results from the overuse of randomization. The
random
function returns to us random numbers between a defined range, this can be helpful when we need randomized numbers for a particular purpose such as for coordinates or color. Check the
reference to see the uses
and syntax of this function.
// We can use randomness to create x and y coordinates
// Lets assign a random value to the variable 'randomX' we can use the two
// parameters of the random function to set the range.
let randomX = random(100, width-100);
let randomY = random(100, height-100);
Visualising Randomness
Here we have applied a basic visualisation to the random coorindates generated by the 'control structure' shown earlier. This control strucutre is be visualised using ellipses with random fills to form a drawing over time.
The
random
function in this case has been applied to generate numbers between 0 and 255 for our fill values.
// Our control structure (randomness)
let randomX = random(100, width-100);
let randomY = random(100, height-100);
// Here we set a random fill colour!
// Notice how we can call random whenever we need a number
fill(random(0, 255), random(0,255), random(0,255), 200);
// Remove stroke
noStroke();
// Draw an ellipse using the random numbers we created earlier
ellipse(randomX, randomY, 100);
Random Walker
Randomness can be applied to seed decisions made by a design system, the random walker uses the result of a random number to decide which way to travel every frame.
let x, y;
function setup() {
background(230);
createCanvas(windowWidth, windowHeight)
x = width/2;
y = height/2;
}
function draw() {
// Use the random values from r to pick the direction to remove
// Each if statement sends the x or y position of our ellipse in
// a direction of 4 pixels
let direction = floor(random(4));
if(direction == 0) x = x + 4;
if(direction == 1) x = x - 4;
if(direction == 2) y = y + 4;
if(direction == 3) y = y - 4;
// Here we check if the ellipse has touched the side of the canvas and
// Put it back on canvas if it has left
if (x == width) x = width - 25;
if (x < 0) x = 25;
if (y == height) y = height - 25;
if (y < 0) y = 25;
ellipse(x, y, 50);
}
Using Noise For Randomness
Here we can see a basic example of the
noise
function being applied. Over time the x value
of the ellipse is being updated with values generated from
noise
functions. Notice how unlike
random
the values that
noise
creates are related. Each number after another is somewhat related so it creates a less chaotic result.
Check the
reference to see the uses
and syntax of this function.
Using Noise For Randomness
The basic example here is extended to visualise the random coordinates being generated.
// Create a varaible to provide fresh input for our noise function
let counter = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
background(247, 249, 251);
}
function draw() {
// noise function is given a counter value which will return a number
// 0 and 1, this is then multiplied by the width so we can see it change
// over time on our ellipse
let noiseValue = noise(counter) * width;
ellipse(noiseValue, height/2, 50);
// counter increases by 0.005 each time draw funciton loops giving the
// noise funciton a fresh number each loop
counter = counter + 0.005;
}
Noise and Random Output Compared
On the left the stroke values are being generated by
noise
and on the right the stroke values
being generated by
random
. We can see how both functions
behave over time. Using the slider in the top left we can see how
increasing the step size creates greater variaiton between previous
values created by the
noise
function.
From this demonstration we can see that
noise
is a more orderly method for creating randomness over time. There is a transiton between each new number created unlike using
random
.
Noise Walker
Using the related numbers that the
noise
function creates, it can be applied to the x and y coordinates of shape to create unpredictable "organic-like" movement patterns.
let countX = 0;
let countY = 0;
let x, y;
let size = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
background(230);
}
function draw() {
// This is where we get our nice numbers from noise
// remember that noise needs a value changing over
// time to give us a number
// Noise gives us a number between 0 and 1 if we multiply by
// width it will lead to smooth numbers moving across the screen
x = noise(countX) * width;
y = noise(countY) * height;
// draw our ellipse using these nice values from noise
ellipse(x, y, size);
//increase our counter every time the draw loop loops
countX = countX + 0.004;
countY = countY + 0.003;
}
Easing Control Structure
Easing is the smoothing of a transition between two values and can provide structure to your randomly generated values. In this example, we have a visualization of the logic behind easing. Each small red dot represents 5% of the distance between where the ellipse is and where it is going. Using an easing of 5% the ellipse will travel that fraction of the distance it wants to cover every frame. This is why the ellipse slows down when it gets close to its destination.
We use
lerp
to do the easing calculation for us. Check the
reference to see the uses
and syntax of this function.
Easing
let x = 0;
let y = 0;
function setup() {
createCanvas(720, 400);
noStroke();
}
function draw() {
background(51);
// lerp() calculates a number between two numbers at a specific increment.
// The amt parameter is the amount to interpolate between the two values
// where 0.0 equal to the first point, 0.1 is very near the first point, 0.5
// is half-way in between, etc.
// Here we are moving 5% of the way to the mouse location each frame
x = lerp(x, mouseX, 0.05);
y = lerp(y, mouseY, 0.05);
fill(255);
stroke(255);
ellipse(x, y, 66, 66);
}
Easing Walker
let x = 0;
let y = 0;
let targetX, targetY;
function setup() {
createCanvas(windowWidth, windowHeight);
background(230);
targetX = width / 2;
targetY = height / 2;
}
function draw() {
fill(20);
stroke(170);
// We are lerping to our random coordinates now
x = lerp(x, targetX, 0.05);
y = lerp(y, targetY, 0.05);
ellipse(x, y, 50);
// Every 2 seconds or 120 frames update the random coordinates
if (frameCount % 120 == 0) {
targetX = random(100, width - 100);
targetY = random(100, height - 100);
}
}
Using Sin and Cos Structure
let count = 0;
let x = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(DEGREES); // Change to degrees for easy math
background(247, 249, 251);
}
function draw() {
// Here we are creating varaibles to remap the values we get from sin and cos
let sinY = map(sin(count), -1, 1, 0.5,1.5) * height/2;
let cosY = map(cos(count), -1, 1, 0.5,1.5) * height/2;
// The speed which these variables update changes the pattern
count += 1;
x += 5;
// This test sends our ellipse to the far left once off the screen
if(x > width+ 50) x = -50;
fill(255);
ellipse(x, sinY, 50);
fill(255);
ellipse(x, cosY, 50);
}
Helpful Utility Functions
angleMode()
By default when using with any function relating to angles or trigonometry such as
sin
,
cos
or
rotate
(there are many others),
p5.js generally refers to angles via radians, in short representing angles as fractions of PI rather than
using degrees. For example, an angle of 90 degrees is represented as PI/2 in radians.
This can be a fine for many applications however if you are more comfortable working with degrees then we
can change the way angles are represented using
angleMode()
function setup() {
createCanvas(windowWidth,windowHeight);
}
function draw() {
background(230);
angleMode(DEGREES); // Change the mode to DEGREES
// In this mode a full oscillation needs the mouse to move 360 degrees or 360 pixels
let a = map(sin(mouseX),-1,1,height/4,(height/4*3));
ellipse(width/2- 100, a, 50, 50);
angleMode(RADIANS); // Change the mode to RADIANS
// In this mode a full oscillation needs the mouse to move 2PI or 6.28 pixels
let b = map(sin(mouseX),-1, 1,height/4,(height/4*3));
ellipse(width/2+ 100, b, 50, 50);
}
floor()
The
floor
function turns any of of the numbers you give it into whole numbers.
It will round down so if you were to give it 3.8 it would return 3. This is quite helpful when
working with functions that give us decimal points such as
random
which may not work
nicely when trying to perform certain tests.
function setup() {
createCanvas(windowWidth,windowHeight);
frameRate(1);
}
function draw() {
background(230);
let random1 = random(2);
// the chances of this being true are very low as random gives
// us decimal values which mean random1 will rarely be equal to
// exactly 1
if(random1 == 1) {
ellipse(width/2 - 100, height/2, 50,50);
}
let random2 = floor(random(2));
// This test has an appromiate 50% chance to be true as the decimals are being
// chopped off the end so we can only get a result of 0 or 1
if(random2 == 1) {
ellipse(width/2 + 100, height/2, 50,50);
}
}
abs()
The
abs
funciton takes in numbers and always returns them positive. So if you were
to put -3 in abs it would return 3.
function setup() {
createCanvas(windowWidth,windowHeight);
}
function draw() {
background(230);
let sinMapped = sin(frameCount/100)*height/3;
ellipse(width/2 - 100, sinMapped, 50);
// Sin usually goes negative however abs always returns the positive values
// So this ellipse never leaves the canavs in negative y space
let sinMappedAbs = abs(sin(frameCount/100))*height/3;
ellipse(width/2 + 100, sinMappedAbs, 50);
}
colorMode()
The
colorMode
function changes the way that we work with color in P5.js.
By default we describe color with RGB however there are times when working with HSB
color ranges might be more helpful. HSB = Hue, Sautration, Brightness
let x = 0;
function setup() {
createCanvas(windowWidth,windowHeight);
background(230);
noStroke();
}
function draw() {
// Uses rgb to describe color
colorMode(HSB);
let hsbColor = map(x, 0, width, 0, 360);
fill(hsbColor,100,100);
ellipse(x,height/2 - 100, 50);
// Uses rgb to describe color
colorMode(RGB);
let rgbColor = map(x, 0, width, 0, 255);
fill(rgbColor);
ellipse(x,height/2 + 100, 50);
// Move the ellipse to the right
x++;
// once at the right edge put back on the left
if(x > width) x = 0;
}