Appendix AIntroduction to p5.js
To quote from p5js.org, p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else!
By developing a library and tools, including a Web editor, the Processing community provides a way to produce projects for the Web. The p5.js effort is currently led by Qianqian Ye and evelyn masso and was created by Lauren Lee McCarthy.
The p5.js library and tools provide for JavaScript a similar entry that Processing provides for Java. In fact, if you go to https://processing.org/, you can find links to tools for using Processing functionality to Python and other programming languages.
In this Appendix, we will show you how to use the Web editor and ideas that you have learned in this book to make sketches in JavaScript, the programming language for websites. After this introduction, you may want to go further with p5.js. The next steps are to download the tools in order to create your sketches on your own computer, acquire webspace or turn your computer into a local server, and upload what you have produced. The p5js site has instructions, a large reference section, and videos. Note: The material on the p5js.org website does not presume prior knowledge of Processing or programming, but what you have learned about Processing and programming in general in this book will help you advance to building projects as complicated as your imagination and inspiration carry you.
As was my practice in the chapters in this book, I work using examples: the Daddy logo, a head/tail sketch I named Fearless Girls vs. the Bull, and a 3D construction I call Rainbow Helix.
Getting Started Using p5.js
The p5js project provides a Web editor and an environment that lets us create, run, and store sketches (programs). To put it another way, you can get started at programming without doing any downloads! Go to https://p5js.org/ and click on Editor on the left or you can go directly to https://editor.p5js.org/
Figure
A-1 shows the opening screen. You can start using it immediately. However, to save your work, you do need to sign up, so I encourage you to do that. The panel on the left is for your code. The coding does resemble what you have been reading about in this book, namely, there is something called
setup and something called
draw. However, there are differences. Instead of specifying that each of these functions does not return a value by writing
void, the definition of functions in p5.js requires us to write
function. You next see what appears to be and is an invocation (function call) to
createCanvas. This is what p5.js uses in place of
size. In the definition of
draw, you will see something familiar: a call to the function
background. I encourage you to explore the rest of the Editor, including running the sketch by pressing on the arrow. The
Web editor gives a name to sketches. This one is Abundant hip. You can modify what is here and add more code. Because I assume (I hope) you are familiar with programming in Processing, I will go on with the examples, but take as long as you want here. This template will appear when you click on File to get a pulldown menu and then click on New.
Overview of Examples
The
Daddy Logo example is essentially the same as the Processing example featured in Chapter
1. It is a static drawing of two images. Figure
A-2 is a screenshot of the
Web editor, with the start of the code showing on the left in the panel labeled sketch.js and the result on the right in the panel labeled Preview. Notice the name daddyLogo appears next to a pencil. I changed what was there. It wasn’t Abundant hip, but sometimes equally silly.
After showing similar screenshots of the other two examples, I will describe the changes you need to make from Processing as presented in this book to p5js.
The
Fearless Girls vs. the Bull is a coin-toss type of program. The Bull is a well-known statue near the New York Stock Exchange. The Fearless girl statue was nearby but has been moved. My granddaughter, also a fearless girl, is standing next to the statue. The sketch features the use of the functions
random and
mouseReleased. Just as in the example in Chapter
2, pressing and then releasing the mouse button causes one of two pictures to be shown. Figure
A-3 shows an expanded screenshot of the editor. One aspect of this sketch is that it makes use of two image files. On the left you see a list of files, two of which refer to the images. The description of the implementation will describe how to upload the image files for use with the code.
The last example is new and is included here to show the use of classes and 3D. It also features the use of
mouseDragged for moving the helix. Figure
A-4 is a screenshot of the helix.
The helix is constructed of flat shapes positioned in 3D. The definition of a class, used for the sections of the helix, is similar but with small differences from Processing code. I will make some general comments and then include tables for the coding for each example, with line-by-line explanations. My prediction is that you will not need to read the explanation for each line.
Implementing Daddy Logo
The p5.js language has functions and variables like other programming languages. Processing and many other programming languages are what is termed strongly typed. We, the programmers, need to specify the data type of each variable, each parameter to each function, and the return value for each function. What we get for our efforts is error messages that tell us if we are using something in a way that does not match how we defined it. JavaScript and p5.js (which is JavaScript with the addition of a library of Processing functions) are each NOT strongly typed. This does mean we don’t get certain error messages. The benefit is that the initial programming is quicker. It is necessary to let (no pun intended) the editor know that something is a variable, and for that, p5.js requires us to use the word let. These statements are called declarations. Declaration statements can be used to initialize variables. However, some expressions must wait until the setup function. The daddyLogo sketch starts with declarations of global variables. The term global means that the variables maintain the last value set even after a function has ended. In contrast, local variables are declared and set within functions. When the function ends, the variables are not accessible, and any changes made within the function are not maintained if and when it is invoked again.
The sketch has definitions for setup and draw. As was the case in Processing, the Processing language uses setup as the name suggests: it generally is invoked first (I need to hedge here as you will see in the second example). The draw function is invoked over and over unless we include a call to noLoop to make this stop. (I could have done that for this sketch.)
As is the case with Processing, opening and closing brackets ({ and }) are necessary to define functions, classes, and clauses within functions such as for-loops. Many, perhaps most, errors are because of mistakes in bracketing. Processing and p5js do not pay attention to indenting, but proper indenting does help us, so try to use it.
As was the case in the daddyLogo in Processing, the definition of the daddy function allows the function to be used to draw different figures. I think of one as regular and the other as skinny. My purpose back in Chapter 1 and here is to demonstrate the advantages of variables and functions. The face is drawn using two ellipses with color the skintone value. The two ellipses overlap to form the peanut shape. The positioning and the sizes of the eyes, the mouth, and the two arcs representing the single hair are calculated from the parameters to daddy. Reminder: In p5js as well as Processing, horizontal values, referencing the x axis, increase moving from left to right. Vertical values, y axis, increase moving down the screen. This last is typical of most computer applications, but not what we are used to when creating graphs, so it does require an adjustment in our thinking.
Look over the code in the table. Use it to create your own drawings.
let ctrx = 100; | Horizontal value for 1st figure |
let ctry = 160; | Vertical value for 1st figure |
let faceWidth = 80; | Width for 1st figure |
let faceHeight = 100; | Height for 1st figure |
let skinnyFaceWidth = 60; | Width for 2nd figure |
let skinnyFaceHeight = 130; | Height for 2nd figure |
let eyeSize = 10; | Eye size |
let skintone; | Color for skin to be set in setup |
function setup() | Header for setup function |
{ | |
createCanvas(800,600); | Set size of window |
ellipseMode(CENTER); | Parameters for ellipse will start in the center |
skintone = color(255,224,189); | Set color for faces. Please feel free to set the color |
} | |
function draw() | Header for draw function |
{ | |
daddy(ctrx,ctry,faceWidth, faceHeight); | Invoke daddy function with one set of parameters |
daddy(3*ctrx, 2*ctry,skinnyFaceWidth, skinnyFaceHeight ); | Invoke daddy function with another set of parameters. Notice the modified parameters |
} | |
function daddy(x,y, w, h) | Header for daddy function. Parameters for position and dimensions |
{ | |
noStroke(); | Turn off outlines |
fill(skintone); | Set color for fill |
let eyeXoffset = int((15.0/80.0)*w); | Calculate eye X offset as expression using w parameter |
let eyeYoffset = int(.35*h); | Calculate eye Y offset as expression using h parameter |
let mouthYoffset = int(.10*h); | Calculate mouth Y offset as expression using h parameter |
let mouthWidth = int(.5*w); | Calculate mouth width as expression using w parameter |
let mouthHeight = int(.3*h); | Calculate mouth height as expression using h parameter |
let hairOffsetY = eyeYoffset*3; | Calculate hair Y offset in terms of already-calculated eye offset |
let hairRadius = 3*eyeSize; | Calculate hair arc radius in expression of eye size |
ellipse(x,y,1.2*w,h); | Draw part of face |
ellipse(x,y-h/2,w,h); | Draw part of face |
stroke(0); | Now set stroke to black |
fill(0); | Set fill to black |
ellipse(x-eyeXoffset,y-eyeYoffset,eyeSize,eyeSize); | Draw left eye |
ellipse(x+eyeXoffset,y-eyeYoffset,eyeSize,eyeSize); | Draw right eye |
noFill(); | Turn off fill |
arc(x,y-hairOffsetY,hairRadius,hairRadius,-PI/2,PI/2); | Draw the lower part of single hair |
arc(x,y-hairOffsetY-hairRadius,hairRadius,hairRadius,PI/2,PI*3/2); | Draw the upper part of single hair |
stroke(240,0,0); | Set stroke to reddish |
arc(x,y+mouthYoffset,mouthWidth,mouthHeight,QUARTER_PI,3*QUARTER_PI); | Draw the mouth |
} | |
Your next step is to modify my sketch by putting in more calls to daddy, with different sets of parameters. The next step after that would be to modify the daddy function. Then make it totally your own by defining your own function for a simple cartoon-type drawing. Put in calls to your function using different sets of parameters.
Implementing Fearless Girls vs. the Bull
The
Fearless Girls vs. the Bull is what I call a coin-toss program. I make use of the
random function to make one or the other picture appear with the location in the window based on the position of the mouse. The action is triggered using the
mouseReleased function, and the figure appears where the mouse was on the window when the button was released (after being pressed down). Figure
A-5 shows the Bull option. I made the screenshot smaller so I could point out the Hello, Jeanine on the upper right. I signed up when I started using p5js and logged on, giving me the capability to save my work.
You will notice from the screenshot that the draw function is empty. This is because it is necessary to have a draw function, even if the action of the sketch is not specified in draw but in another function, in this case, mouseReleased.
The sketch makes use of the two images. Neither one is very big, but each takes up some amount of time to be loaded into the computer working memory. In order that this be accomplished before each is used, I make use of a function named
preload. This is like
setup and
draw and
mouseReleased. The underlying program “knows” to call
preload when any and all media files are loaded. As you can see from the code in the table, the action in the
preload function I have written is to load each image file and assign it to one of the variables. Remember: Names of variables and functions (and classes) are strictly up to us, the programmer. Do make them something meaningful for you. Do not assume the p5js system will interpret your names. I think of one as being like the head of a coin and the other like the tail.
let imgH; | Variable to hold one image |
let imgT; | Variable to hold the other image |
function preload(){ | Header preload |
imgH = loadImage(“annikaFearless.jpg”); | Load and set imgH |
imgT = loadImage(“bull.jpg”); | Load and set imgT |
} | |
function setup() { | Header for setup |
createCanvas(600,400); | Create a canvas 600 by 400 |
textSize(32); | Set the size of the text |
text(“click on the screen”,10,50); | Display the text “click on the screen” at the indicated location |
} | |
function draw() { | Header for draw function |
} | Draw function is empty |
function mouseReleased() { | Header for mouseReleased |
background(255); | Set the background. This has the effect of erasing anything drawn previously in the window |
if (.5<random(1)) { | The random function is invoked. It returns a number (fraction) between 0 and 1. The mechanics of the if statement checks if the value is greater than .5 |
image(imgH,mouseX,mouseY,200,200); | In this case, the imgH is drawn at the location of the mouse action |
} | |
else { | else |
image(imgT,mouseX,mouseY,200,200); | Draw the imgT at the location of the mouse action |
} | |
} | |
Okay, you have seen the code, but I need to tell you how to get image files when the code can access them. Look back to Figure
A-1. Notice the > at the left. If you click on this, you see Figure
A-6. Now click on the downward pointing arrow next to Sketch Files.
A small window appears giving some options as shown in Figure
A-7.
Click on Upload file and follow the instructions for all the image files needed for the sketch. I am showing this starting from the initial template. It can be done during or after writing the code. After I did this, the screen looked like Figure A-3. The Web editor maintains a folder holding the files. This includes any media files, such as the two image files, and sketch.js, which holds the code created with the Web editor. The index.html file is standard for all the p5js sketches. You can examine it. It does reference the Processing Library, mainly a collection of JavaScript functions. The style.css is another standard file for all the p5js sketches.
With this introduction and using what you learned for Processing, you can try to make other p5.js examples. The last example features 3D and definition of a class.
Implementing Rainbow Helix
The rainbow helix in my sketch is made up of a sequence of flat (2-D) pieces—I call them wedges—in 3D. I wanted to incorporate some user/player interactions, so I use mouseDragged to set two variables: rotx and roty. These are used for positioning each wedge.
The coding differs from Processing in two aspects. The createCanvas function has a third parameter, and it is WEBGL. This refers to a 3D library. You will recall that size also used a 3rd parameter when doing 3D. I refer you back to Chapter 10.
The second aspect is the definition of a class. You can go back to Chapter 4 for the definition of a class using Processing, which is the same as the Java programming language. I must admit that I like the terminology of p5.js better than Processing and Java. Broadly speaking, a class defines code and data (variables). There can be exceptions, but standard practice is to use, that is, reference, and set (read and write) the variables only with the code defined in the class definition. A class is used to create objects. The term constructor is used for the code that constructs an object. Other code is defined like functions. These functions are called methods.
Let’s leave the theory and get down to explaining this example. One piece of the helix is an object in the Wedge class. It is defined by its four vertices and its color. The four vertices are specified by three values each. These are the x, y, and z positions in space (3D). The x and the y are horizontal and vertical, and the z is perpendicular to the x and y. Think of it as coming toward you out of the screen. This sketch does make use of a general, mathematical technique called parameterized curves as well as standard trigonometry. In a parameterized curve, a single value, the parameter, is used to calculate the positions in space. The value often can be viewed as time, but in this situation, it is an angle.
The fact that the Wedge objects fit together and take on the sequence of rainbow colors over and over is accomplished by how the constructor is invoked. It is an important programming skill to divide the work. The constructor function in the Wedge class definition creates a Wedge object based on two angles, the start and stop positions, and one color. The invocation of the constructor calculates the parameters so as to position the object along two helical spirals: one outer and one inner. The invocation also cycles through the array of colors, rainbowColors. The construction of what I have named strip, an array of Wedge objects, is accomplished by nested for-loops.
The Wedge class only has two methods: constructor and display. The display code uses beginShape() and endShape(CLOSE) to form the Wedge object, a flat shape in space. The new Wedge object is assigned to the variable newWedge. It is displayed by invoking display, and it is added to the array strip using an expression with the push function adding an item to the strip array.
With these general ideas in mind, please look at the table for the commented code.
let multiples = 28; | Multiples of rainbow segments |
let rainbowColors = []; | Initialize rainbowColors as an array |
let strip = []; | Initialize strip as an array |
let rotx = 2.85; | Used to support mouse dragging changing orientation. I experimented to get these values |
let roty = 0.55; | Used to support mouse dragging |
let a = 190; | Radius of inner helix |
let b = -6; | Step up vertically for 1 turn |
let WedgeWidth; | Set in setup. It could be set now |
let tDelta; | Will be set to hold difference in radians between start and end of a Wedge |
let orad; | Outer radius. That is, radius of outer helix |
let irad; | Inner radius |
function setup() { | |
createCanvas(700,600,WEBGL); | |
WedgeWidth = a*PI/(3*7); | Three complete rainbows per half turn. This looked good to me |
tDelta = PI/21; | Set so three complete rainbows fit in a half turn |
irad = a; | Radius of inner helix |
orad = a+WedgeWidth; | Calculated radius of outer helix |
rainbowColors[0] = color(148,0,211); | Rainbow colors |
rainbowColors[1] = color(75,0,130); | “ |
rainbowColors[2] = color(0,0,255); | “ |
rainbowColors[3] = color(0,255,0); | “ |
rainbowColors[4] = color(255,255,0); | “ |
rainbowColors[5] = color(255,127,0); | “ |
rainbowColors[6] = color(255,0,0); | “ |
let t= 0; | Initial value of t |
| t used for parameterized curves representing the two helix coordinates |
background(200); | Set background |
| Nested for loops |
for(let i = 0;i<multiples;i++){ | Go through multiples of rainbows |
for(let j=0;j<rainbowColors.length;j++) { | In each case, go through seven Wedges |
let t1 = t; | Set t1 to the current value of t |
let t2 = t1+tDelta; | Calculate t2 |
newWedge = new Wedge(t1,t2,rainbowColors[j]); | Create a Wedge going from t1 to t2 and using the jth color |
newWedge.display(); | Display that Wedge |
strip.push(newWedge); | Add to the strip array |
t = t2; | Set new t value by advancing t to now be t2 |
} | |
} | |
} | |
function draw() { | Header for draw |
background(180); | Set background (erase everything) |
rotateX(rotx); | Use current value of rotx (set in mouseDragged) |
rotateY(roty); | Use current value of roty (set in mouseDragged) |
for(let i=0;i<strip.length;i++) { | Display each element of strip |
strip[i].display(); | |
} | |
} | |
class Wedge { | Header for Wedge class definition |
constructor(t1a, t2a, c) { | The constructor method computes and stores the eight vertex points for a new wedge. These values are used in the display method |
this.t1 = t1a; | Set object variables, starting with t1. This is the t1 value for this object |
this.t2 = t2a; | Set the t2 value |
this.h1x = irad*cos(this.t1); | Calculate and set h1x. The first of three values setting the position of one corner |
this.h1y = b*this.t1; | Calculate and set h1y |
this.h1z = irad*sin(this.t1); | Calculate and set h1z |
this.h2x = irad*cos(this.t2); | Now move on the next corner. Calculate and set h2x |
this.h2y = b*this.t2; | Calculate and set h2y |
this.h2z = irad*sin(this.t2); | Calculate and set h2z |
this.h3x = orad*cos(this.t2); | Now move on to the next corner, the outer helix. Calculate and set h3x |
this.h3y = b*this.t2; | Calculate and set h3y |
this.h3z = orad*sin(this.t2); | Calculate and set h3z |
this.h4x = orad*cos(this.t1); | Now move on to the 4th and last corner of the wedge. Calculate and set h4x |
this.h4y = b*this.t1; | Calculate and set h4y |
this.h4z = orad*sin(this.t1); | Calculate and set h4z |
this.colorR = c; | Set the color of this wedge |
} | |
display() { | Header for display method |
fill(this.colorR); | Set the color |
beginShape(); | beginShape |
vertex(this.h1x,this.h1y,this.h1z); | Vertex h1 |
vertex(this.h2x,this.h2y,this.h2z); | Vertex h2 |
vertex(this.h3x,this.h3y,this.h3z); | Vertex h3 |
vertex(this.h4x,this.h4y,this.h4z); | Vertex h4 |
endShape(CLOSE); | endShape, using the CLOSE option |
} | |
} | |
function mouseDragged() { | Header for mouseDragged |
let rate = 0.01; | Set value used in the calculation. I experimented and this seemed good |
rotx += (pmouseY-mouseY)* rate; | Increment the rotx value |
roty += (mouseX-pmouseX)* rate; | Increment the roty value. The difference between the statement, concerning mouseX, and the one above, concerning mouseY, makes the results correspond to user intuition. Remember the upside-down coordinate system |
} | |
I encourage you to play with this and make changes. You can use the underlying concepts to produce your own sketches.