// this program displays a landscape of a stone path along a stream in the woods.
// empty arrays for objects:
var bkgrndtrees = [];
var path = [];
var stream = [];
var trees = [];
var allArrays = [bkgrndtrees, path, stream, trees];
// speed of landscape shift, per frame
// adjusted for background+foreground so 'closer' objects move faster than 'far away' ones
var shift = -1;
function setup() {
createCanvas(480, 300);
background(80, 125, 60);
frameRate(200);
// create initial collections of objects:
fillBkgrndtrees();
fillPath();
fillStream();
fillTrees();
// sort objects in arrays to be drawn properly:
for (a=0; a<allArrays.length; a++) {
if (allArrays[a]!=stream){ // stream should not be ordered
orderObjects(allArrays[a]);
}
}
}
// fills backgroundTrees array with initial trees:
function fillBkgrndtrees() {
for (var i = 0; i < 320; i++) {
var rx = random(width+75);
var ry = random(-height/25, 2*height/5); // top of canvas = background
bkgrndtrees[i] = makeTree(rx, ry);
}
}
// fills foreground tree array with initial trees:
function fillTrees() {
for (var i = 0; i < 5; i++) {
var rx = random(width+75);
var ry = random(4*height/5, 3*height/2); // bottom of canvas = foreground
trees[i] = makeTree(rx, ry);
}
}
// fills path array with initial stones:
function fillPath() {
for (var i = 0; i < 15; i++) {
var size = random(width/25, width/10);
var col = color(random(90, 120));
var x = i*50;
var ry = random(height/2, 3*height/5);
path[i] = makeStone(x, ry, size, col);
}
}
// fills stream array with objects based on stone path:
function fillStream() {
for (var i = 0; i < path.length; i++) {
var x = i*50;
var y = path[i].y + path[i].size;
stream[i] = makePoint(x, y);
}
}
// creates a tree object
function makeTree(tx, ty) {
// background trees:
if (ty < height/2) {
var tree = {x:tx,
y:ty,
age:random(2, 11),
trunkc: color(random(70, 90), random(55, 75), random(30, 55)),
leavesc: color(random(0, 75), random(65, 175), random(50)),
show: showTree,
move: moveObject,
speed: shift*.85 }
}
// foreground trees:
else {
var tree = {x:tx,
y:ty,
age:random(4, 7),
trunkc: color(random(50, 80), random(20, 50), random(10, 20)),
leavesc: color(random(0, 100), random(100, 200), random(30)),
show: showTree,
move: moveObject,
speed: shift*1.15 }
}
return tree;
}
// creates a stone object
function makeStone(sx, sy, ssize, scol) {
var stone = {x:sx,
y:sy,
size:ssize,
c: scol,
show: showStone,
move: moveObject,
speed: shift}
return stone;
}
// creates a point object (for stream curve);
function makePoint(px, py) {
var pnt = {x:px,
y:py,
size:width/50,
c: color(50, 125, 175),
show: showStone,
move: moveObject,
speed: shift}
return pnt;
}
function draw() {
background(80, 125, 60);
// draw all objects in all arrays:
for (j=0; j<allArrays.length; j++) {
thisArray = allArrays[j];
drawObjects(thisArray);
if (thisArray!=stream) { // stream is updated with path
// order and updates arrays:
orderObjects(thisArray);
updateArray(thisArray);
}
}
}
// draws and moves objects in an array
function drawObjects(array) {
for (i=0; i<array.length; i++) {
thisObj = array[i];
thisObj.show();
thisObj.move();
}
if (array==stream) {
drawStream();
}
}
// uses curve shape and array points ot draw stream
function drawStream() {
push();
noFill();
stroke(50, 125, 175);
strokeWeight(15);
curveTightness(-.2);
beginShape();
curveVertex(stream[0].x, stream[0].y);
curveVertex(stream[0].x, stream[0].y);
for (var m=1; m<stream.length-1; m++){
curveVertex(stream[m].x, stream[m].y);
}
curveVertex(stream[stream.length-1].x, stream[stream.length-1].y);
endShape();
pop();
}
// sorts objects in an array according to the y feild
// this ordering ensures accurate depth on canvas
function orderObjects(array) {
for (var j=0; j<array.length-1; j++) {
var counter = 0;
for (var i=0; i<array.length-1; i++) {
if (array[i].y > array[i+1].y) {
var tmp = array[i];
array[i] = array[i+1];
array[i+1] = tmp;
counter += 1;
}
}
if (counter==0) {
break;
}
}
}
// updates all allArrays
// adds new objects to end and deletes unused objects (off canvas)
function updateArray(array) {
if (array==trees || array==bkgrndtrees) {
// add new trees to array off-canvas:
// background trees:
var treeLikelihoodB = 320/555;
if (random(1) < treeLikelihoodB) {
var ry = random(-height/25, 2*height/5); // top of canvas = background
bkgrndtrees.push(makeTree(width+75, ry));
}
// foreground trees:
var treeLikelihoodF = 5/555;
if (random(1) < treeLikelihoodF) {
var ry = random(3*height/4, 3*height/2); // bottom of canvas = foreground
trees.push(makeTree(width+75, ry));
}
}
else {
// path and stream use fixed x values:
if (frameCount%50==0) {
var size = random(width/25, width/10);
var col = color(random(90, 120));
var ry = random(height/2, 3*height/5);
path.push(makeStone(700, ry, size, col));
col = color(0, 50, 150);
var y = ry + size;
stream.push(makePoint(700, y));
}
}
// remove any object no longer on canvas:
var keep = [];
for (var k=0; k<array.length; k++) {
if (array[k].x > 0) {
keep.push(array[k]);
}
}
array = keep;
}
// draws tree based on age
function showTree() {
if (this.y < height/2) { // top of canvas = background
var h = this.age*10;
var w = this.age*2;
}
else { // bottom of canvas = foreground
var h = this.age*12;
var w = this.age*2.5;
}
var cx = this.x + w/2;
var cy = this.y - h*1.5;
if (this.age > 5) { // older trees
stroke(50, 40, 30);
fill(this.trunkc);
rect(this.x, this.y-h, w, h);
fill(this.leavesc);
for (var i=0; i<3; i++) {
stroke(0, 75, 0);
circle(cx - (this.age*4*i) + (this.age*4),
cy - (this.age*1.5) + (this.age*3*i),
this.age*5);
circle(cx + (this.age*4*i) - (this.age*4),
cy - (this.age*1.5) + (this.age*3*i),
this.age*5);
ellipse(cx - (this.age*6*i) + (this.age*6),
cy + this.age,
this.age*5,
this.age*6);
ellipse(cx,
cy - (this.age*3) + (this.age*4*i),
this.age*7,
this.age*5);
}
noStroke();
ellipse(cx, cy + this.age, this.age*12, this.age*9);
}
else { // younger trees
stroke(50, 40, 30);
fill(this.trunkc); // tree trunk lighter brown
rect(this.x, this.y-h, w/2, h);
fill(this.leavesc);
for (var i=0; i<3; i++) {
stroke(0, 75, 0);
circle(cx - (this.age*3*i) + (this.age*3),
cy - (this.age*1.5) + (this.age*2*i),
this.age*4);
circle(cx + (this.age*3*i) - (this.age*3),
cy - (this.age*1.5) + (this.age*2*i),
this.age*4);
ellipse(cx - (this.age*4*i) + (this.age*4),
cy + this.age,
this.age*4,
this.age*5);
ellipse(cx,
cy - (this.age*4) + (this.age*4*i),
this.age*6,
this.age*4);
}
noStroke();
ellipse(cx, cy + this.age/2, this.age*11, this.age*9);
}
}
// draws stone for path
function showStone() {
stroke(75);
fill(this.c);
ellipse(this.x, this.y, this.size, this.size/2);
}
// updates x values to move the landscape along
function moveObject() {
this.x += this.speed;
}
For this project, I was inspired by a trail I used to walk a lot growing up. I struggled a bit to order the objects in a way that looks nice and is recognizable as a landscape, but I think the orderObjects() function that I created helped a lot with that. I also found it difficult to create the stream using a curve shape, but I am again really pleased with how well it came out. I wanted the path and stream to flow along together, and they do. To give more of a landscape effect, I adjusted the shift values for moving the objects so that things in the foreground move faster than things in the background.