Processing Live tutorial
The code that was produced in this workshop can be found here.
- Processing Live tutorial
Processing
Download the Processing IDE (3.x is stable, 4.x is still in beta)
Processing is Java (17). Wrapped in simple libraries.
Drawing a line in plain Java:
public static void main(String[] args) {
JFrame f = new JFrame();
JPanel p = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(0, 0, 100, 100);
}
};
f.add(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
Drawing a line in Processing (immediate mode):
line(0,0,100,100);
The basic anatomy of a processing program (sketch):
// Called once at the start:
void setup(){
// Specify the size of the window
size(800, 600, P2D); // P2D makes it faster by using the GPU
// Initialization code here
}
// called 60 times per second until the program stops:
void loop(){
background(0, 0, 0); // Actually clears the screen with a color
// Use built in functions to draw things:
}
Very important:
the Reference
Drawing things
ellipse(100, 100, 100, 300);
circle(400, 300, 40);
rect(500, 400, 200, 150);
line(30, 50, 400, 500);
Appearance:
// everything drawn after this function will be color (255,200, 10)
fill(255, 200, 10);
ellipse(100, 100, 100, 300);
circle(400, 300, 40);
// outline everything drawn after this function will be color (40,200, 100)
stroke(40, 200, 100);
rect(500, 400, 200, 150);
// Everything is drawn with thick outlines
strokeWeight(3);
line(30, 50, 400, 500);
// Reset appearance for the next frame
fill(255);
stroke(0);
strokeWeight(1);
Variables
Java is typed
. You need type specifications to allocate pieces of memory (variables):
int xPosition = 10; // integer, no fractions
float radius = 13.5; // fractions
String yourName = "Junior the 3rd"; // text
Local variables
Variables created in functions will be destroyed when the function is over:
// variables xPosition and yPosition will be created every time draw() is called, 60 times per second
void draw(){
float xPosition = 20;
float yPosition = 45;
circle(xPosition, yPosition, 200);
}
// this won't work. The variables don't exist outside of the draw() function
void updatePosition(){
xPosition+=10;
yPosition-=40;
}
Global variables
Frowned upon by many but quite handy for quick hacks. Variables exist and can be shared between functions.
// variables xPosition and yPosition can be called in each function
float xPosition = 30;
float yPosition = 60;
void draw(){
circle(xPosition, yPosition, 200);
}
// this will now work.
void updatePosition(){
xPosition+=10;
yPosition-=40;
}
Processing defined variables
Variable | Purpose |
---|---|
width |
The width of the window |
height |
The height of the window |
mouseX |
The x-coordinate of the mouse |
mouseY |
The y-coordinate of the mouse |
pmouseX |
The previous x-coordinate of the mouse |
pmouseY |
The previous y-coordinate of the mouse |
mousePressed |
Wether a mouse button is pressed |
mouseButton |
The last mouse button pressed |
key |
the last key pressed |
keyCode |
the last key pressed, also for non-character keys |
Movement
Animation is just changing images really fast.
Draw a circle at a different location for every frame:
float xPosition = 0;
float xVelocity = 1;
void draw(){
circle(xPosition, height/2, 20);
xPosition += xVelocity;
}
In y and y plane and bounce:
float xPosition;
float yPosition;
float xVelocity = 2;
float yVelocity = 3;
void setup(){
size(800, 600, P2D);
xPosition = width/2;
yPosition = height/2;
}
void draw(){
circle(xPosition, yPosition, 20);
xPosition += xVelocity;
yPosition += yVelocity;
// bounce
if (xPosition > width || xPosition < 0) xVelocity = -xVelocity;
if (yPosition > height || yPosition < 0) yVelocity = -yVelocity;
}
Multiple objects
Everything that is drawn in Processing is committed to the screen as pixels. You can’t change the location of the pixels afterwards, like in InkScape. It’s rather like Photoshop. To have multiple objects, means you have to draw each object from scratch for each frame. Multiple objects means multiple variables:
float xPositionBall1;
float yPositionBall1;
float xVelocityBall1 = 2;
float yVelocityBall1 = 3;
float xPositionBall2;
float yPositionBall2;
float xVelocityBall2 = 2;
float yVelocityBall2 = 3;
//etc...
This gets really messy. We can use classes to “bundle” variables together. A class
is like a recipe for an object, in this case a ball.
// The class is like a recipe
class Ball {
float xPosition;
float yPosition;
float xVelocity=2;
float yVelocity=3;
}
// The class is used like any datatype (such as int, float, String, etc.)
Ball ball = new Ball();
Ball ball2 = new Ball();
void draw(){
circle(ball.xPosition, ball.yPosition, 10);
circle(ball2.xPosition, ball2.yPosition, 10);
// Update the variables in the objects
ball.xPosition+=ball.xVelocity;
ball.yPosition+=ball.yVelocity;
ball2.xPosition+=ball2.xVelocity;
ball2.yPosition+=ball2.yVelocity;
}
Still messy. Lot’s of copied code. Lets make the ball update and draw itself:
class Ball {
float xPosition;
float yPosition;
float xVelocity;
float yVelocity;
// A function with the name of the class is called when a
// variable is made with this class
Ball() {
// This puts this ball in a random position with a random velocity
xPosition = random(0, 800);
yPosition = random(0, 600);
xVelocity = random(-5, 5);
yVelocity = random(-5, 5);
}
// The ball knows how to draw itself
void draw() {
circle(xPosition, yPosition, 100);
}
// The ball knows how to updates it's position
void update() {
xPosition += xVelocity;
yPosition += yVelocity;
// bounce
if (xPosition > width || xPosition < 0) xVelocity = -xVelocity;
if (yPosition > height || yPosition < 0) yVelocity = -yVelocity;
}
}
// Create two variables using the class
Ball ball1 = new Ball();
Ball ball2 = new Ball();
void setup() {
size(800, 600, P2D);
}
// Only call functions from both balls
void draw() {
ball1.update();
ball2.update();
ball1.draw();
ball2.draw();
}
Lots of balls
Using an ArrayList it’s possible to store and draw lots of balls.
// with the previously described "ball" class included:
// Make a lost to put balls in:
ArrayList<Ball> balls = new ArrayList<Ball>();
void setup(){
size(800, 600, P2D);
// Add 100 balls to the list
for (int iBall=0; iBall<100; iBall++)
balls.add(new Ball());
}
void draw(){
// Traverse the list and update each ball
for (int iBall=0; iBall<100; iBall++){
balls.get(iBall).update();
}
// Use a for-each loop to draw each ball
for (Ball ball : balls){
ball.draw();
}
}
My favorite tricks
Friction
// Make the velocity for an object slightly smaller for each frame:
xVelocity *= 0.99;
yVelocity *= 0.99;
Gravity
// Add a small value to the velocity for each frame, in the ball update
yVelocity += 1;
Follow the mouse
// Make the velocity of the object point towards the mouse
// And scale it down to slowly move towards the mouse
xVelocity = (mouseX - xPosition)*0.05;
yVelocity = (mouseY - yPosition)*0.05;
Random walk
// Add a random number to the velocity for each frame
xVelocity += random(-2, 2);
yVelocity += random(-2,2);
Springs(!!)
Like “follow the mouse” but with acceleration
// By adding a force/acceleration
float springiness = 2.0;
float energyLoss = 2;
xAcceleration = springiness * (mouseX - xPosition) - energyLoss * xVelocity;
xVelocity += xAcceleration/60.0;
xPosition += xVelocity/60;
yAcceleration = springiness * (mouseY - yPosition) - energyLoss * yVelocity;
yVelocity += yAcceleration/60.0;
yPosition += yVelocity/60;
Cheap motion blur
// Add to setup
noStroke(); // turns off stroke
// at start of draw, instead of background:
fill(0,0,0,5); // set the fill-color to black, almost fully transparent
rect(0,0,width, height); // draw a black, almost transparent rectangle on the entire window
// Don't forget to set the fill color back to the ball colors.
fill(255);
Smoke
// In the setup, set the blend-mode to additive
blendMode(ADD);
//In draw() make the balls grey transparent.
fill(200, 200, 200, 10);
// In the ball.update, make the balls go up and re-appear at
// the bottom when it hist the top
if (yPosition < 0) yPosition = height;
Determine start position and start velocity
Using the constructor
, the function that is called when a ball is created, you can give new balls a starting position.
class Ball {
Ball(float x, float y, float vX, float vY){
xPosition = x;
yPosition = y;
xVelocity = vX;
yVelocity = vY
}
// etc...
}
Add new balls
Add a new ball to the list when the mouse is pressed.
void draw(){
// Somewhere in the frame:
if (mousePressed){
balls.add(new Ball(mouseX, mouseY, random(-4,4), random(-4, 2)));
}
// etc.
}
Serial Communication
In Arduino:
- open the example
01.Basics->AnalogReadSerial
- Upload to any device
- Check the serial output
In Processing:
- open the example
Serial->SimpleRead
- fill in your serial port at line 22, for instance:
String portName = "/dev/cu.usbmodem142401";
- Add the following line at line 30:
println(val);
What happens? What values do you see? Why?
- Replace
Serial.println(sensorValue);
withSerial.write(sensorValue);
- Check the output:
- In the serial terminal
- In processing
- Still not right? Why?
ASCII vs Binary
In Arduino, Serial.println()
converts values to ASCII:
Serial.println(1234);
actually outputs a ‘1’, a ‘2’, a ‘3’ and a ‘4’. Four bytes. Not one number.
Serial.write(1234);
actually outputs one byte which can only go from 0 to 255. So 1234 is too large. To properly write a number as bytes:
uint16_t bigNumber = 1234;
// Three ways to send a 16 bit integer:
// With arithmetic
Serial.write(bigNumber%256); // Send least significant byte
Serial.write(bigNumber/256); // Send most significant byte
// with binary operations and shifting:
Serial.write(bigNumber | 0x00FF); // Send least significant byte
Serial.write((bigNumber | 0xFF00)>>8); // Send least significant byte
// By sending the bytes as an array
Serial.write((uint8_t*)&bigNumber, 2);
In Processing:
Serial.read()
only reads one byte. To read multiple bytes and make an integer:
byte leastSignificantByte = Serial.read();
byte mostSignificantByte = Serial.read();
int reassembledInteger = mostSignificantByte*256 + leastSignificantByte;
Arduino datatypes
Using specified datatypes helps you understand how many bytes are used for each variable:
Datatype | description | number of bytes | range |
---|---|---|---|
uint8_t |
unsigned 8-bit integer | 1 byte | From 0 to 255 |
uint16_t |
unsigned 16-bit integer | 2 bytes | from 0 to 2^16 |
uint16_t |
unsigned 32-bit integer | 4 bytes | from 0 to 2^32 |
int8_t |
signed 8-bit integer | 1 byte | From -128 to 127 |
int16_t |
signed 16-bit integer | 2 bytes | from -2^15 to 2^15 |
int16_t |
signed 32-bit integer | 4 bytes | from -2^31 to 2^31 |
These don’t exist in processing.
Simple command structure
- Send a character byte first
- Send parameters after that
- Know exactly how many bytes you’re going to receive
Example, Processing:
///Set the color of an RGB LED
Serial.write('C'); // command 'C' for color
Serial.write(255);
Serial.write(10);
Serial.write(100);
Arduino:
uint8_t command = Serial.read();
switch (command) {
case 'C':
r = Serial.read();
g = Serial.read();
b = Serial.read();
break;
// parse more commands if needed
}