Generative terrain customization

Hi, after following along some tutorials I created a terrain generator using perlin noise and triangle_stripes. It is working and it runs.
For further iterations I would like to slow down the speed of the generated terrain and make the triangles stripes less visible/ less bold at the horizon of the terrain (the end of it) and have the triangle_stripes more visible in the font of the screen (the newly generated terrain)! Any ideas/ recommendations on how to achieve that?

Like in this example:

Here is the code I extracted from Daniel Schiffermans tutorial code:

  int cols, rows;
// variable scl for scale 
int scl = 20;
int w = 2000;
int h = 1600;

float flying = 0;

//2dimensional array to store for every single point a z value generated by an algorythm
//in java thats how you define a 2dimensional array
float[][] terrain;

void setup() {
  size(600,600, P3D);
  // columnes is the width of the grid devided by that scale 
  cols = w / scl;
  rows = h / scl;
  terrain = new float[cols][rows];
  float yoff = 0;
  for (int y = 0; y < rows; y++) {
    float xoff = 0;
    for (int x = 0; x < cols; x++) {
      terrain[x][y] = map(noise(xoff,yoff), 0, 1, -100, 100);
      xoff += 0.2;
    }
    yoff += 0.2;
  }  
}

void draw() {
  
  flying -= 0.1;
  
  float yoff = flying;
  for (int y = 0; y < rows; y++) {
    float xoff = 0;
    for (int x = 0; x < cols; x++) {
      terrain[x][y] = map(noise(xoff,yoff), 0, 1, -100, 100);
      xoff += 0.2;
    }
    yoff += 0.2;
  }  
  

  
  background(0);
  stroke(255);
  noFill();
  
  translate(width/2, height/2+50);
  rotateX(PI/3);
  translate(-w/2,-h/2);
  //nested loop for every single point on the grid
  for (int y = 0; y < rows-1; y++) {
    beginShape(TRIANGLE_STRIP);
    for (int x = 0; x < cols; x++) {
      vertex(x*scl, y*scl, terrain[x][y]);
      vertex(x*scl, (y+1)*scl, terrain[x][y+1]);
      //rect(x*scl, y*scl,scl,scl);
    }
    endShape();
  }
}

Hi @fijthecreator,

This will change the stroke color as step increments:

`stroke(step*255/78.0); // step 0 to 78 becomes 0 to 255 ( for color)’

There is also the map() function to re-map a number from one range to another:
https://processing.org/reference/map_.html

Scrutinize the above carefully and understand what it is doing and where it may be useful.

A search in the forum for terrain will yield many results.

I encourage you to do some exploration of the Processing resources and experimenting with the code.

Resources here:
https://processing.org/

Make use of println() statements to see what the variables are doing in your code and consider what to do to change them in a loop or over time.

:)

2 Likes

This is Daniel Shiffman’s code: p5.js Web Editor

If you really understood the code, slowing it down should not be an issue.
My bet is that this is an assignement

1 Like

Hello @fijthecreator

Once you figure out how this is working:

You can then start to play with multiple colors:

Via dovetailing with this tutorial on Happy Coding:

BTW, kindly tag this as homework. :slightly_smiling_face:
:nerd_face:

4 Likes

I thought this looked familiar.

You should be aware that posting another person’s code and claiming it as your own is frowned upon.

This appears to be the exact same code in Dan Shiffman’s tutorial on 3D Terrain Generation as seen here:

Not cool.
:face_with_raised_eyebrow:

As described in my post, this is code I extracted from a tutorial. I just changed the false label of my code and specified where I got this code from. In no way I intended to pretend this would be my self-written code. I will definitely pay more attention to that in further posts.

1 Like

A question that came up while working on this project: Is it possible to create a similar terrain generator in p5js?

I experimented a little and got to this far:

The issues I encountered are the following, the TRIANGLE_STRIPES aren’t all connected, as u can see, resulting in more kind of terrain stripes/ lines instead of a terrain mesh. The other issue I couldn’t find a workaround for is the flying effect from the processing sketch.

1 Like

Hi

It is possible!

There are already versions of this out there and I will leave that search to you.

If you are converting the code from Processing to p5.js be aware of this:

https://p5js.org/examples/arrays-array-2d.html

I remember the above because I did this a while back and learned this.

:)

Thanks a lot. Instead of just copying one of the examples of a good-looking terrain generator in p5js, I tried to apply my learnings from different tutorials, such as daniel Schiffman’s, to my own terrain generator.

My current results look like this:

And the code like this:

//global variables:
//random array to preload the noise levels which will serve as the z coordinates 
var terrainValues = []; 

//tells us how high the terrain will be 
var multiP = 140; 

var xoff = 0;
var yoff = 0;

//tells us how fast the illusion of movement will be & how smooth the terrain will be  
var inc = 0.1; 

var marchingStep = 0;


function setup() {
  createCanvas(900, 600, WEBGL);
  angleMode(DEGREES);
  
//nested for loop to create, get noise values and store them in the 2 dimensional array
  for(var y = 0; y < 60; y++){
    
//create new empty array while we previously just declared a random on dimensional array and later referred to a 2 dimensional array
    terrainValues.push([]); 
    
    xoff = 0;
    for(var x = 0; x <60; x++){
      terrainValues[y][x] = map(noise(xoff, yoff), 0, 1, -multiP, multiP);
      xoff = xoff + inc;
    }
    yoff = yoff + inc;
  }
}


function draw() {
  background(0);
  
// set initial stroke to transparent
  stroke(255,5); 
  
  noFill();
  
// reset the y offset
  yoff = 0; 
  
// nested for loop to replace x array value with added marchingStep -> illusion of endlessness
//compute new value for landscape and replace the values in our terrain array with our marchingStep added in
  for(var y = 0; y < 60; y++){
    xoff = 0;
    for(var x = 0; x < 60; x++){
      terrainValues[y][x] = map(noise(xoff  + marchingStep, yoff), 0, 1, -multiP, multiP);
      xoff = xoff + inc;
    }
    yoff = yoff + inc;
  }
  
//rotate around x-axis by 70 degrees to get the horizontal view 
  rotateX(70); 
  
//translate to poisition stripe mesh in center of the screen - negative because we want to transalte the mesh in the negativ direction
  translate(-width/3, -height/3)
  
//outter loop needs to be y while I want the grid to start first with y, then do all the x's. Go to the next y and do all the x's again, and so on.
//nested for loop to create a 2 dimensional triangle stripe mesh
  for(var y = 0; y < 60; y++) {
    
//for every single row begin Shape
    beginShape(TRIANGLE_STRIP);
    for(var x = 0; x < 60; x++){
      vertex(x *10, y *10, terrainValues[x][y]);
      vertex(x *10, (y +1) * 10, terrainValues[x][y+1]);
    }
    endShape();
    
//decreases the alpha value of the stroke each time we increment the y loop
    stroke(255,y*2);
  }
//increment the small offset we add to the perlin noise calculation for terrain values in    each draw loop
  marchingStep -= 0.1; 
}

Things I would like to change, but fail to do so:

  1. I would like the terrain mesh to fill the viewport width without leaving the “empty” spaces to the left and the right of the mesh. (I first tried to scale up the mesh but I couldn’t figure out how to eliminate the “empty” space on both sides.)
  2. I would like to rotate the mesh even further to 80 degrees, but while I do so the first line of my mesh is visible and disturbs the look of the terrain generation like this:

Does anybody have a recommendation on how to conquer these challenges?

Move the stroke() call to before the beginShape() in your drawing loop. That will fix your bright leading edge.

If you want the terrain to fill the screen, then you either have to generate a wider grid or just zoom in more.

I increased the size of the mesh, but this unfortunately lowers the framerate and lets the terrain appear laggy (while I think all the points are drawn constantly in the draw function). Is there a way to optimize the performance (framerate) while increasing the size of the mesh?

An alternative is to spread out the back sample points so that your are sampling in a fan shape rather than a strict rectangular grid. Whether that would be acceptable or not depends on your actual goal for this project.

Another choice would be to store the noise values in an array and only generate new back rows as they are needed. Your current code calls the noise() function 3600 times per draw() (or more if you’re trying to cover more of the screen) which has a cost.

1 Like

The second option seems very interesting and probably best fitting to my project. While it would not only solve the width issue, it would also enhance the performance of my terrain generator (which I’d like to embed into a webpage).

Unfortunately, I’m still learning and don’t know how to go about storing the noise values in an array that only generates new back rows as needed. Any chance to show me/ give me a starting point?

It turns out that it doesn’t matter how fast or slow noise() is, it’s the rendering that slows things down. So the only way to get speed is to pack each row into a p5.Geometry mesh. I spent way too long on this, but here’s your terrain fly-over:

//global variables:
//random array to preload the noise levels which will serve as the z coordinates 
let terrainValues = []; 

//tells us how high the terrain will be 
let vScale = 10;

let nCols = 90;
let nRows = 60;
let steps = 10;     // how many steps (frames) per row, fewer is faster

let ns = 0.1;  // horizontal noise() scale

function makeStrip( y, r1, r2 ) {
  let s = new p5.Geometry( 1, 1 );
  s.gid = "land"+str(y);
  for( let x=0; x<nCols-1; x++ ) {
    s.vertices.push( new p5.Vector( x, 0, r1[x] ), new p5.Vector( x, 1, r2[x] ) );
  }
  for( let x=0; x<nCols-2; x++ ) {
    s.faces.push( [ 2*x, 2*x+1, 2*x+2 ] );
  }
  s.computeNormals();
  return s;
}

let mesh = [];

function setup() {
  createCanvas(900, 600, WEBGL);
  angleMode(DEGREES);
  
//nested for loop to create, get noise values and store them in the 2 dimensional array
  for(let y = 0; y < nRows; y++){
    
//create new empty array while we previously just declared a random on dimensional array and later referred to a 2 dimensional array
    terrainValues.push([]); 
    
    for(let x = 0; x <nCols; x++){
      terrainValues[y][x] = map(noise(x*ns, (-y-1)*ns), 0, 1, -vScale, vScale);
    }
  }
  
  for( let y=0; y<nRows-2; y++ ) {
    mesh.push( makeStrip( -y-1, terrainValues[y], terrainValues[y+1] ) );
  }
}

function draw() {
  background(0);
  
  let yPos = frameCount/steps;
  let yFrac = yPos % 1;
  
  noFill();
  
  if( (frameCount % steps) == 0 ) {
    for( let y=nRows-1; y>0; y-- ) {
      terrainValues[y] = terrainValues[y-1];
      mesh[y] = mesh[y-1];
    }
    
    terrainValues[0] = [];
    for(let x = 0; x < nCols; x++){
      terrainValues[0][x] = map(noise(x*ns, yPos*ns), 0, 1, -vScale, vScale);
    }
    mesh[0] = makeStrip( yPos, terrainValues[0], terrainValues[1] );
  }
  
  //rotate around x-axis by 70 degrees to get the horizontal view 
  rotateX(70);
  
  scale( width/nCols*1.8 );
  translate( -(nCols-1)*0.5, -(nRows-1)*0.6 );

  for( y=0; y<nRows-2; y++ ) {
    push();
    translate( 0, y+yFrac, 0 );
    fill( 255, y*2 );
    model( mesh[y] );
    pop();
  }
}

I just saw that I left it with filled triangles. Change the fill to stroke near the bottom to go back to full wireframe.

1 Like

Thank you so much for your effort! Amazing how smooth the fly-over is compared to my previous sketches. Unfortunately, there are a lot of new concepts that I’m not too familiar with yet. I will definitely take the time to understand the code, its methods and functions.
Once again, thanks. :slight_smile:

After further experimentations, I got very close to my initial goal.
The challenges that I currently face, are shown here:

While I included a windowResize() function, I would like the terrain to always stick to the bottom of the windowHeight, so the first line of the mesh is not visible. Is there any way to accomplish that?
Furthermore, I do still struggle to resize the mesh without its performance being significantly decreased. I would like the mesh to appear endless, so it fills the whole windowWitdh.
Anyone able to help?

Here is my code:

//global variables:
//random array to preload the noise levels which will serve as the z coordinates 
var terrainValues = []; 

//tells us how high the mountains will be 
var multiP = 150; 

var xoff = 0;
var yoff = 0;

//tells us how smooth the terrain will be  
var inc = 0.08; 

var marchingStep = 0;


function setup() {
  var canvas = createCanvas(windowWidth, windowHeight, WEBGL);
  canvas.style('display', 'block');
  angleMode(DEGREES);
  
//nested for loop to create, get noise values and store them in the 2 dimensional array
  for(var y = 0; y < 60; y++){
    
//create new empty array while we previously just declared a random on dimensional array and later referred to a 2 dimensional array
    terrainValues.push([]); 
    
    xoff = 0;
    for(var x = 0; x < 60; x++){
      terrainValues[y][x] = map(noise(xoff, yoff), 0, 1, -multiP, multiP);
      xoff = xoff + inc;
    }
    yoff = yoff + inc;
  }
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}


function draw() {
  background(0);
  
// set initial stroke to transparent
  stroke(255, 5); 
  
  noFill();
  
// reset the y offset
  yoff = 0; 
  
// nested for loop to replace x array value with added marchingStep -> illusion of endlessness
//compute new value for landscape and replace the values in our terrain array with our marchingStep added in
  for(var y = 0; y < 60; y++){
    xoff = 0;
    for(var x = 0; x < 60; x++){
      terrainValues[y][x] = map(noise(xoff  + marchingStep, yoff), 0, 1, -multiP, multiP);
      xoff = xoff + inc;
    }
    yoff = yoff + inc;
  }
  
//rotate around x-axis by 80 degrees to get the horizontal view 
  rotateX(80); 
  
//translate to poisition stripe mesh in center of the screen - negative because we want to transalte the mesh in the negativ direction
  translate(-width/2, -height/2, -250)
  scale(width/600,1)
  
//outter loop needs to be y while I want the grid to start first with y, then do all the x's. Go to the next y and do all the x's again, and so on.
//nested for loop to create a 2 dimensional triangle stripe mesh
  for(var y = 0; y < 60; y++) {
    
//for every single row begin Shape
    beginShape(TRIANGLE_STRIP);
    for(var x = 0; x < 60; x++){
      vertex(x *10, y *10, terrainValues[x][y]);
      vertex(x *10, (y +1) * 10, terrainValues[x][y+1]);
    }
    endShape();
    
//decreases the alpha value of the stroke each time we increment the y loop
    stroke(255,y*2);
  }
  
//increment the small offset we add to the perlin noise calculation for terrain values in each draw loop
  marchingStep -= 0.08; 
}