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 will be quite simple, but still effective. We’ll think of a blade of grass as a line segent with a random height, thickness, colour, 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, for example:
float lowValue = -2.43; float highValue = 8.04; float r = random(lowValue, highValue);
The problem with this 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 and high values 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 initLo, float initHi) { lo = initLo; hi = initHi; } float randValue() { return random(lo, hi); } }
This class is simple but useful. It reduces the number variables we need to represent a range. For instance, the example above may be re-written to use Range:
Range rng = new Range(-2.43, 8.04); float r = rng.randValue();
We can also access the low and high values if we need then, 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, colour, and rage of growth:
// ... Range class here Range thicknessRange = new Range(1, 3); Range lengthRange = new Range(1, 3); Range angleRange = new Range(-25, 25); Range greenRange = new Range(200, 255); class GrassBlade { float x, y; float thickness; float length; float angle; color grassColor; float growthRate; GrassBlade(float initX, float initY) { x = initX; y = initY; thickness = thicknessRange.randValue(); length = lengthRange.randValue(); angle = angleRange.randValue(); grassColor = color(0, greenRange.randValue(), 0); } // a blade of grass is drawn as a line void render() { pushMatrix(); stroke(grassColor); strokeWeight(thickness); translate(x, y); rotate(radians(angle)); line(0, 0, 0, -length); popMatrix(); } void grow() { if (random(0.0, 1.0) < growthRate) { length++; } } }
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.
We will think of a dandelion as a stem and a circular “flower” on one end:
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 initX, float initY) { x = initX; y = initY; radius = radiusRange.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; } } }
Finally, we need code to draw the lawn. For this program, a lawn is a collection of grass blades and dandelions:
ArrayList<GrassBlade> lawn; float bladeGap; ArrayList<Dandelion> weeds; int numBlades = 500; int numWeeds = 5; Range gapRange = new Range(1, 3); 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(); } }
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