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?
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();
}
}
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.
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.
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.
//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:
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.)
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:
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.
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.
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.
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;
}