3D Perlin noise in Processing

Hi again,

Well, as the day is slowly coming to an end and I’ve had some thoughts about whether to post or not, I’ve come to the decision that I will post it after all.
There are many pros and cons that could be listed, but none of them weigh so heavily that there would be a right or wrong.
Reasons are …
My code is just one of many examples and is far from an all-encompassing implementation. There are many things that can be worked out in addition to this on the topic.
I’ve now added lots of comments to the code, to not offer this as a simple copy/paste solution, but rather that one can get an idea of what concrete was done here and most importantly why.
(took longer than to code it. :grin:)

If you disagree with my decision, you are free to flag the post. :wink:

Code+Image
// a class to representing a point on our grid.
// This should/would/could be done better by defining it as faces (of a triangle mesh).
// but would it keep as simple as possible 
class GridPoint {
  float x, y, v;
  public GridPoint(float px, float py, float pv) {
    x = px;  // x-pos of the grid point
    y = py;  // y-pos of the grid point
    v = pv;  // height of the grid point
  }
}

//helper variables
float halfWidth, halfHeight;
boolean mode = false;
// storage of out grid
int rows, cols;
GridPoint[][] grid;
// image used for texture and heightmap
PImage img;

void setup() {
  size(800, 800, P3D);
  halfWidth  = width/2.;
  halfHeight = height/2.;
  initGrid(10.);
  // use textureMode IMAGE as the grid is not normalized
  textureMode(IMAGE);
}

void initGrid(float scl) {
  // load the image, used for texture and heightmap.
  // usually more than one picture will be used. One for heightmap, one for normalmap and one with the texture.
  // for simplicity it has the same size than our grid to not make the code more math intensive.
  // to get this work our image is somehow a bit blurred (like noise is), otherwise the mesh isn't smooth enough
  img = loadImage("image.png");
  img.loadPixels();
  // initialize our gridpoints
  rows = floor(height/scl);
  cols = floor(width/scl);
  grid= new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      int sx = floor(x*scl);
      int sy = floor(y*scl);
      // set x and y coordinates (center origin) and the height of the current point.
      // height is the normalized brightness value [0..1] of the pixel color from the image.
      // Subtract 0.5 to align it to center [-0.5..0.5]
      // scaling it by 50, so the range is [50.0*(-0.5)..50.0*(0.5)]
      grid[y][x] = new GridPoint(-halfWidth+sx+scl/2, -halfHeight+sy+scl/2, 50.*(brightness(img.get(sx, sy))/255-0.5));
    }
  }
}

void draw() {
  background(0);
  // set the origin to the center of the screen, and move it 400px far away on z-axis
  translate(halfWidth, halfHeight, -400);

  // switch between mesh and texured mode
  if (frameCount % int(TAU*100.) == 0) {
    mode = !mode;
  }

  if (mode) {
    // if textured mode no strokes and a light from viewpoint to object
    noStroke();
    directionalLight(255, 255, 255, 0, 0, -1);
  } else {
    // if mesh mode only show strokes
    noFill();
    stroke(128);
    strokeWeight(0.5);
  }

  // apply the rotation after lighting, otherwhile the light gets also rotated, which we not want.
  // tipping 30degree back on X-axis
  rotateX(radians(30));
  // tipping left/right a bit per frame from -PI/4 - +PI/4 on Y-axis
  rotateY(sin(frameCount/100.)*QUARTER_PI);

  // build the surface/terrain by triangle strips to display it
  for (int y = 0; y < rows-1; y ++) {
    beginShape(TRIANGLE_STRIP);
    if (mode) {
      // on textured mode set the texture
      texture(img);
    }
    // common triangle strips
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y+1][x];
      vertex(ca.x, ca.y, ca.v, ca.x+halfWidth, ca.y+halfHeight);
      if (mode) {
        // on textured mode we need to set the normal vector, so the light knows how to behave on hittng the surface
        // usually there are better calculation than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector na = new PVector(ca.x-cb.x, ca.y-cb.y, 0).sub(new PVector(ca.x-cb.x, ca.y-cb.y, 1)).normalize();
        normal(na.x, na.y, na.z);
      }
      vertex(cb.x, cb.y, cb.v, cb.x+halfWidth, cb.y+halfHeight);
      if (mode) {
        // on textured mode we need to set the normal vector, so the light knows how to behave on hittng the surface
        // usually there are better calculation than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector nb = new PVector(cb.x-ca.x, cb.y-ca.y, 0).sub(new PVector(cb.x-ca.x, cb.y-ca.y, 1)).normalize();
        normal(nb.x, nb.y, nb.z);
      }
    }
    endShape();
  }
}

Image used for code above [800x800]px

Result:
output

Cheers
— mnse

5 Likes