In these notes you will learn how to:
In this note we’ll create a program that displays some grass and dandelions that grow over time. Each individual blade of grass will have its own corresponding object that controls how it is drawn and how it grows.
The graphics are quite simple but still effective, e.g. a blade of grass is a line segment with a random height, thickness, color, and orientation.
In graphics and animation we often want to set variables to have values within a given range of values. For example, we might want a blade of grass to have a thickness between 1 and 4 pixels, or that its angle be between -15 and +15 degrees.
It’s also common to randomly select a value from a range when creating random objects, e.g.:
float lowValue = -2.43;
float highValue = 8.04;
float r = random(lowValue, highValue);
The problem with this is that if you have many different ranges (as we will for the program below), then you have a lot of variables and values to keep straight.
So instead of storing the low value and high value of a range in separate variables, we will create a new kind of object that stores both at the same time:
class Range {
float lo, hi;
Range(float lo_init, float hi_init) {
lo = lo_init;
hi = hi_init;
}
float randValue() {
return random(lo, hi);
}
}
This class is simple but useful. It reduces the number of variables we need to represent a range. For instance, the example above can be re-written like this:
Range rng = new Range(-2.43, 8.04);
float r = rng.randValue();
We can also access the low and high values if we need them, e.g.:
Range rng = new Range(-2.43, 8.04);
println("rng = [" + rng.lo + ", " + rng.hi + "]");
We’re going to represent each blade of grass as its own GrassBlade object. The attributes of a blade of grass that we’ll include are its position, thickness, length, angle, color, and rate of growth:
Range thicknessRange = new Range(1, 3);
Range lengthRange = new Range(1, 3);
Range angleRange = new Range(-25, 25);
Range greenRange = new Range(200, 256);
// ...
class GrassBlade {
float x, y;
float thickness;
float length;
float angle;
color clr;
float growthRate = 0.01;
GrassBlade(float x_init, float y_init) {
x = x_init;
y = y_init;
thickness = thicknessRange.randValue();
length = lengthRange.randValue();
angle = angleRange.randValue();
clr = color(0, greenRange.randValue(), 0);
}
// a blade of grass is drawn as a line
void render() {
pushMatrix();
stroke(clr);
strokeWeight(thickness);
translate(x, y);
rotate(radians(angle));
line(0, 0, 0, -length);
popMatrix();
}
void grow() {
if (random(0.0, 1.0) < growthRate)
length += 1;
}
} // class GrassBlade
The grow() function is serves the same purpose as the update() function we’ve seem in previous examples; we use the name grow here because it is more suggestive of how the blade changes than the generic term update. It works by sometimes increasing the length of the blade.
For us, a dandelion will consist of a stem and a circular “flower” on one end:
Range greenRange = new Range(200, 256);
Range radiusRange = new Range(4, 7);
Range stemLengthRange = new Range(10, 15);
Range stemThicknessRange = new Range(3, 5);
class Dandelion {
float x, y;
float radius;
float stemLength;
float stemThickness;
float stemAngle;
color headColor;
color stemColor;
float growthRate = 0.02;
Dandelion(float x_init, float y_init) {
x = x_init;
y = y_init;
radius = radiusRange.randValue();
stemLength = stemLengthRange.randValue();
stemThickness = stemThicknessRange.randValue();
stemAngle = angleRange.randValue();
headColor = color(255, 255, 0);
stemColor = color(0, greenRange.randValue(), 0);
}
// dandelion is a line with a circle on top
void render() {
pushMatrix();
translate(x, y);
rotate(radians(stemAngle));
stroke(stemColor);
strokeWeight(stemThickness);
line(0, 0, 0, -stemLength);
noStroke();
fill(headColor);
ellipse(0, -stemLength, 2 * radius, 2 * radius);
popMatrix();
}
void grow() {
if (random(0.0, 1.0) < growthRate)
stemLength += 1;
}
} // class Dandelion
Finally, we need code to draw the lawn. For this program, a lawn is collection of grass blades and dandelions:
ArrayList<GrassBlade> lawn;
float bladeGap;
ArrayList<Dandelion> weeds;
int numBlades = 500;
int numWeeds = 5;
Range thicknessRange = new Range(1, 3);
Range lengthRange = new Range(1, 3);
Range angleRange = new Range(-25, 25);
Range greenRange = new Range(200, 256);
Range gapRange = new Range(1, 3);
Range radiusRange = new Range(4, 7);
Range stemLengthRange = new Range(10, 15);
Range stemThicknessRange = new Range(3, 5);
void setup() {
size(500, 500);
// initialize the grass blades
lawn = new ArrayList<GrassBlade>();
bladeGap = gapRange.randValue();
int i = 0;
while (i < numBlades) {
GrassBlade g = new GrassBlade(bladeGap * i, 250);
lawn.add(g);
++i;
}
// initialize the weeds
weeds = new ArrayList<Dandelion>();
i = 0; // no "int" because i was already defined above
while (i < numWeeds) {
Dandelion d = new Dandelion(random(10, width - 10), 250);
weeds.add(d);
++i;
}
} // setup
void draw() {
background(224, 255, 255);
// draw weeds
for(Dandelion d : weeds) {
d.render();
d.grow();
}
// draw blades of grass
for(GrassBlade g : lawn) {
g.render();
g.grow();
}
} // draw
It’s not hard to spot problems, e.g. the dandelions seem to grow too big too quickly. Plus, you can probably think of other features you might add to this simulation to make it more realistic or interesting. The simplest thing to try is to change the ranges for the various variables — the results can be quite intriguing.
int numBlades = 500;
int numWeeds = 5;
float bladeGap; // distance between each blade of grass
// ranges for blades of grass
Range thicknessRange = new Range(1, 3);
Range lengthRange = new Range(1, 3);
Range angleRange = new Range(-25, 25);
Range greenRange = new Range(200, 256);
float grassBladeGrowthRate = 0.01;
// distance between blades of grass
Range gapRange = new Range(1, 3);
// ranges for dandelions
Range headRadiusRange = new Range(4, 7); // size of dandelion head
Range stemLengthRange = new Range(10, 15);
Range stemThicknessRange = new Range(3, 5);
Range weedPositionRange = new Range(10, 490);
float weedGrowthRate = 0.01;
ArrayList<GrassBlade> lawn;
ArrayList<Dandelion> weeds;
void setup() {
size(500, 500);
// initialize the grass blades
lawn = new ArrayList<GrassBlade>();
bladeGap = gapRange.randValue();
int i = 0;
while (i < numBlades) {
GrassBlade g = new GrassBlade(bladeGap * i, 250);
lawn.add(g);
++i;
}
// initialize the weeds
weeds = new ArrayList<Dandelion>();
i = 0; // no "int" because i was already defined above
while (i < numWeeds) {
Dandelion d = new Dandelion(weedPositionRange.randValue(), 250);
weeds.add(d);
++i;
}
} // setup
void draw() {
background(224, 255, 255);
// draw weeds
for (Dandelion d : weeds) {
d.render();
d.grow();
}
// draw blades of grass
for (GrassBlade g : lawn) {
g.render();
g.grow();
}
} // draw
///////////////////////////////////////////////////////////////////
class Dandelion {
float x, y;
float radius;
float stemLength;
float stemThickness;
float stemAngle;
color headColor;
color stemColor;
Dandelion(float x_init, float y_init) {
x = x_init;
y = y_init;
radius = headRadiusRange.randValue();
stemLength = stemLengthRange.randValue();
stemThickness = stemThicknessRange.randValue();
stemAngle = angleRange.randValue();
headColor = color(255, 255, 0);
stemColor = color(0, greenRange.randValue(), 0);
}
void render() {
pushMatrix();
translate(x, y);
rotate(radians(stemAngle));
stroke(stemColor);
strokeWeight(stemThickness);
line(0, 0, 0, -stemLength);
noStroke();
fill(headColor);
ellipse(0, -stemLength, 2 * radius, 2 * radius);
popMatrix();
}
void grow() {
if (random(0.0, 1.0) < weedGrowthRate) {
stemLength += 1;
}
}
} // class Dandelion
///////////////////////////////////////////////////////////////////
class GrassBlade {
float x, y;
float thickness;
float length;
float angle;
color clr;
float growthRate = 0.01;
GrassBlade(float x_init, float y_init) {
x = x_init;
y = y_init;
thickness = thicknessRange.randValue();
length = lengthRange.randValue();
angle = angleRange.randValue();
clr = color(0, greenRange.randValue(), 0);
}
void render() {
pushMatrix();
stroke(clr);
strokeWeight(thickness);
translate(x, y);
rotate(radians(angle));
line(0, 0, 0, -length);
popMatrix();
}
void grow() {
if (random(0.0, 1.0) < grassBladeGrowthRate)
length += 1;
}
} // class GrassBlade
class Range {
float lo, hi;
Range(float lo_init, float hi_init) {
lo = lo_init;
hi = hi_init;
}
float randValue() {
return random(lo, hi);
}
} // class Range