Tutorial 10: Organic Motion
Organic Motion
Examples of organic movement include a leaf falling, an insect walking, a bird flying, a person breathing, a river flowing, and smoke rising. This type of motion is often considered idiosyncratic and stochastic. It is often non-periodic or "almost" periodic.
A Random Walk
The simplest form of organic motion is called a "random walk". The following code shows how we can program this by declaring just two variables to hold the x and y coordinates of a point and then adding a small random amount to the x and y coordinates on every call of the draw()
function:
- Show Sketch
/** @peep sketchcode */
float x; // X-coordinate
float y; // Y-coordinate
void setup() {
size(200, 200);
background(0);
x = random(width);
y = random(height);
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
// Update the position of the point
x += random(-3, 3); // Add a small amount to x-coordinate
y += random(-3, 3); // Add a small amount to y-coordinate
x = constrain(x, 0, width); // Constrain x to width of display
y = constrain(y, 0, height); // Constrain y to height of display
// Draw the point
strokeWeight(4);
stroke(255);
point(x, y);
}
Using the above code as a base, use arrays for the x and y coordinates of multiple points moving by random walks. Your sketch should look something like this:
Try adding colour by creating an array of
color
values to hold a different colour for each moving point, so that it looks something like this:
You might also try practicing write a class, perhaps called
RandomWalker
orBug
that has variables for its position and colour and updates itself every time a function is called. You would then create an array of your class like we did last week in the lecture, see my notes in my Process portfolio post.
Position and Direction
To produce slightly smoother movement, we can apply small changes to the direction that something is moving in, rather than changing its position directly. First let's start with some code to simulate a single moving point with a direction:
- Show Sketch
/** @peep sketchcode */
float x; // X-coordinate
float y; // Y-coordinate
float angle; // Direction of motion
float speed; // Speed of motion
void setup() {
size(200, 200);
background(0);
x = width/2;
y = height/2;
angle = random(TWO_PI);
speed = 0.5;
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
// Calculate distance to move in x and y
float dx = cos(angle) * speed;
float dy = sin(angle) * speed;
// Update coordinate, constrained to display window
x = constrain(x + dx, 0, width);
y = constrain(y + dy, 0, height);
// Draw the point
stroke(255);
strokeWeight(4);
point(x, y);
}
Now we just need to add a small random amount to the angle each time the draw()
function is called:
- Show Sketch
/** @peep sketchcode */
float x; // X-coordinate
float y; // Y-coordinate
float angle; // Direction of motion
float speed; // Speed of motion
void setup() {
size(200, 200);
background(0);
x = width/2;
y = height/2;
angle = random(TWO_PI);
speed = 0.5;
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
// Calculate distance to move in x and y
float dx = cos(angle) * speed;
float dy = sin(angle) * speed;
// Update coordinate, constrained to display window
x = constrain(x + dx, 0, width);
y = constrain(y + dy, 0, height);
// Update direction that the point is going to move in
angle += random(-0.3, 0.3);
// Draw the point
stroke(255);
strokeWeight(4);
point(x, y);
}
As with the random walk example from before, extend this code to support multiple trails.
Try creating a multi-coloured version too:
Of course, we're not limited to drawing simple dots, we can draw whatever we want in it's place. The following simple example shows how we can achieve an interesting effect by just drawing a line across the direction of motion. Notice that in this sketch we use the transformation functions to do the drawing because it is easier that calculating the ends of the lines "manually".
- Show Sketch
/** @peep sketchcode */
float x; // X-coordinate
float y; // Y-coordinate
float angle; // Direction of motion
float speed; // Speed of motion
void setup() {
size(200, 200);
background(0);
x = width/2;
y = height/2;
angle = random(TWO_PI);
speed = 0.5;
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
// Calculate distance to move in x and y
float dx = cos(angle) * speed;
float dy = sin(angle) * speed;
// Update coordinate, constrained to display window
x = constrain(x + dx, 0, width);
y = constrain(y + dy, 0, height);
// Update direction that the point is going to move in
angle += random(-0.3, 0.3);
// Draw the point
translate(x, y);
rotate(angle);
stroke(255, 130);
line(0, -10, 0, 10);
}
Design your own organic motion based on this sketch. You might try extending one of your earlier sketches involving multiple moving things and draw something between things that are close together, like we did in the Process example before.
Noise-Based Motion
Another way to achieve apparently organic motion is to use the noise()
function. The simplest way to do this is simply to use two calls to the noise()
function, one for the x and one for the y coordinates of a point we want to move about the display.
- Show Sketch
/** @peep sketchcode */
void setup() {
size(200, 200);
background(0);
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
// Calculate position based on the noise function
float x = width * noise(frameCount * 0.017);
float y = height * noise(frameCount * 0.013);
// Draw the point
stroke(255);
strokeWeight(4);
point(x, y);
}
We can easily extend this approach to draw several trails at the same time, creating a teaming ball of noise:
- Show Sketch
/** @peep sketchcode */
void setup() {
size(200, 200);
background(0);
}
void draw() {
// Fade the background
if (frameCount % 10 == 0) {
noStroke();
fill(0, 2);
rect(0, 0, width, height);
}
for (int i = 0; i < 10; i++) {
// Calculate position based on the noise function
float x = width * noise((frameCount + i * 1000) * 0.017);
float y = height * noise((frameCount + i * 1000) * 0.013);
// Draw the point
stroke(255);
strokeWeight(4);
point(x, y);
}
}
Flocking
Dan Shiffman provides a great introduction to steering behaviours and how to use them in Processing to produce flocking. (These and many other great examples of using Processing can be found on his Nature of Code web site that is a great resource for anyone interested in exploring how to code with Processing and soon to be released in an expanded form as a book.) Steering behaviours are a way of controlling organic motion that can be used to simulate the movement of birds, fish and other animals that move in groups. This powerful technique was first introduce by Craig Reynolds and is used extensively in games and movies.
I'm not going to repeat Dan Shiffman's introduction here, you should definitely go and read it. I've provided a version of his flocking code that has been converted to run on Processing.js and has been simplified somewhat.
- Show Code
/** @peep sketch */
// Flocking based on example code by Dan Shiffman
// See http://www.shiffman.net/teaching/nature/steering/
Flock flock;
void setup() {
size(400,400);
colorMode(RGB,255,255,255,100);
flock = new Flock(10, 50);
for (int i = 0; i < 10; i++) {
flock.addBoid(width/2, height/2);
}
}
void draw() {
background(100);
flock.run();
}
// Add a new boid into the System
void mousePressed() {
flock.addBoid(mouseX, mouseY);
}
class Flock {
ArrayList boids; // An arraylist for all the boids
float maxspeed = 2.0;
float maxforce = 0.05;
float desiredseparation = 10.0;
float neighbordist = 50.0;
Flock(float _desiredseparation, float _neighbordist) {
desiredseparation = _desiredseparation;
neighbordist = _neighbordist;
boids = new ArrayList(); // Initialize the arraylist
}
void run() {
for (int i = 0; i < boids.size(); i++) {
Boid b = (Boid) boids.get(i);
b.update();
b.render();
}
}
void addBoid(float x, float y) {
Boid b = new Boid(new PVector(x, y), this);
boids.add(b);
}
}
class Boid {
PVector loc;
PVector vel;
PVector acc;
float radius;
Flock flock;
Boid(PVector _location, Flock _flock) {
acc = new PVector(0,0);
vel = new PVector(random(-1,1),random(-1,1));
loc = new PVector(_location.x, _location.y);
radius = 2.0f;
flock = _flock;
}
void update() {
acc.set(0,0,0); // Reset accelertion to 0 each cycle
// We accumulate a new acceleration each time based on three rules
PVector sep = separate(); // Separation
PVector ali = align(); // Alignment
PVector coh = cohesion(); // Cohesion
// Arbitrarily weight these forces
sep.mult(2.0f);
ali.mult(1.0f);
coh.mult(1.0f);
// Add the force vectors to acceleration
acc.add(sep);
acc.add(ali);
acc.add(coh);
vel.add(acc); // Update velocity
vel.limit(flock.maxspeed); // Limit speed
loc.add(vel); // Update location
// Wraparound borders
if (loc.x < -radius) loc.x = width+radius;
if (loc.y < -radius) loc.y = height+radius;
if (loc.x > width+radius) loc.x = -radius;
if (loc.y > height+radius) loc.y = -radius;
}
void render() {
// Draw a triangle rotated in the direction of velocity
float theta = atan2(vel.y, vel.x);
fill(200);
stroke(255);
pushMatrix();
translate(loc.x,loc.y);
rotate(theta);
triangle(radius*2, 0, -radius*2, radius, -radius*2, -radius);
popMatrix();
}
// Separation
// Method checks for nearby boids and steers away
PVector separate() {
PVector sum = new PVector(0, 0, 0);
PVector diff = new PVector(0, 0, 0);
int count = 0;
// For every boid in the system, check if it's too close
for (int i = 0 ; i < flock.boids.size(); i++) {
Boid other = (Boid) flock.boids.get(i);
if (this != other) {
float d = PVector.dist(loc, other.loc);
// If the distance is greater than 0 and less than an arbitrary amount
if ((d > 0) && (d < flock.desiredseparation)) {
// Calculate vector pointing away from neighbor
diff.set(loc.x, loc.y, loc.z);
diff.sub(other.loc);
diff.normalize();
diff.div(d); // Weight by distance
sum.add(diff);
count++; // Keep track of how many
}
}
}
// Average -- divide by how many
if (count > 0) {
sum.div(count);
}
return sum;
}
// Alignment
// For every nearby boid in the system, calculate the average velocity
PVector align() {
PVector sum = new PVector(0,0,0);
int count = 0;
for (int i = 0 ; i < flock.boids.size(); i++) {
Boid other = (Boid) flock.boids.get(i);
if (this != other) {
float d = PVector.dist(loc, other.loc);
if ((d > 0) && (d < flock.neighbordist)) {
sum.add(other.vel);
count++;
}
}
}
if (count > 0) {
sum.div(count);
sum.limit(flock.maxforce);
}
return sum;
}
// Cohesion
// For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
PVector cohesion() {
PVector sum = new PVector(0,0,0); // Start with empty vector to accumulate all locations
int count = 0;
for (int i = 0 ; i < flock.boids.size(); i++) {
Boid other = (Boid) flock.boids.get(i);
float d = PVector.dist(loc, other.loc);
if ((d > 0) && (d < flock.neighbordist)) {
sum.add(other.loc); // Add location
count++;
}
}
if (count > 0) {
sum.div((float)count);
return steer(sum,false); // Steer towards the location
}
return sum;
}
// A method that calculates a steering vector towards a target
// Takes a second argument, if true, it slows down as it approaches the target
PVector steer(PVector target, boolean slowdown) {
PVector steer = PVector.sub(target,loc); // A vector pointing from the location to the target
float d = steer.mag(); // Distance from the target is the magnitude of the vector
// If the distance is greater than 0, calc steering (otherwise return zero vector)
if (d > 0) {
// Normalize desired
steer.normalize();
// Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
if ((slowdown) && (d < 100.0f)) steer.mult(maxspeed*(d/100.0f)); // This damping is somewhat arbitrary
else steer.mult(flock.maxspeed);
// Steering = Desired minus Velocity
steer.sub(vel);
steer.limit(flock.maxforce); // Limit to maximum steering force
} else {
steer.set(0,0);
}
return steer;
}
}
Note: To add new members to the flock click on the sketch with the mouse.
Experiment with the effect of changing the values for
desiredseparation
andneighbordist
.Can you adapt the flocking code to produce multiple flocks with different behaviours, i.e., different values for
desiredseparation
andneighbordist
?Can you change the code for rendering a
Boid
to draw them differently, or to support different colours, either for theFlock
or for individualBoid
s?
Comments
Nobody has said anything yet.