TRIANGLE_STRIP lighting problem

@scudly After much trial and error and testing of different lighting algorithms, I have determined that yours is one of the best. Here’s my code, it features 4 different types of lighting modes (cycle through them with ‘c’):

import peasy.*;
PeasyCam cam;

final int points = 200;
final float camMin = .1;
final float camMax = 50;
final float tileScale = 10;
final float noiseScale = 2;
final float noiseFalloff = 0.5;
final float zScale = 1. / noiseScale;
final int noiseOctave = 8;

boolean showNormals = false;
boolean trueLights = true;
boolean showMesh = false;
boolean flatten = false;
int lightingStyle = 0;

float[][] hmap = new float[points][points];
PVector[][] P = new PVector[points][points];
PVector[][][] N = new PVector[points - 1][points - 1][4];

void setup() {
  size(800, 800, P3D);
  cam = new PeasyCam(this, tileScale * sqrt(3) * height / 2);
  cam.setMinimumDistance(camMin * height);
  cam.setMaximumDistance(camMax * height);
  perspective(TAU / 6, (float)width / height, camMin * height, camMax * height);
  noStroke();
  noiseDetail(noiseOctave, noiseFalloff);

  for (int i = 0; i < points; i++)
    for (int j = 0; j < points; j++)
      P[i][j] = new PVector();
  for (int i = 0; i < points - 1; i++)
    for (int j = 0; j < points - 1; j++)
      for (int k = 0; k < 4; k++)
        N[i][j][k] = new PVector();
  updateMap();
  setLights();
}

void updateMap() {
  noiseSeed((int)random(MAX_INT));
  for (int i = 0; i < points; i++) {
    for (int j = 0; j < points; j++) {
      hmap[i][j] = noise(i * noiseScale / points, j * noiseScale / points);
      P[i][j].set(j - 0.5 * (points - 1), i - 0.5 * (points - 1), (hmap[i][j] - 0.5) * points * zScale);
      P[i][j].mult(tileScale * height / (points - 1));
    }
  }
}

void setLights() {
  int $ = points - 1;
  switch (lightingStyle) {
  case 0: // normals of two triangles (processing default)
    for (int i = 0; i < points - 1; i++) {
      for (int j = 0; j < points - 1; j++) {
        PVector s1 = PVector.sub(P[i][j], P[i + 0][j + 1]);
        PVector s2 = PVector.sub(P[i][j], P[i + 1][j + 1]);
        PVector s3 = PVector.sub(P[i][j], P[i + 1][j + 0]);
        PVector n1 = PVector.cross(s1, s2, null).normalize();
        PVector n2 = PVector.cross(s2, s3, null).normalize();
        N[i][j][0].set(n2);
        N[i][j][1].set(n1);
        N[i][j][2].set(n2);
        N[i][j][3].set(n2);
      }
    }
    break;
  case 1: // cross product of tile diagonals (no gradient)
    for (int i = 0; i < points - 1; i++) {
      for (int j = 0; j < points - 1; j++) {
        PVector n = PVector.cross(PVector.sub(P[i][j], P[i + 1][j + 1]), PVector.sub(P[i][j + 1], P[i + 1][j]), null).normalize();
        for (int k = 0; k < 4; k++)
          N[i][j][k].set(n);
      }
    }
    break;
  case 2: // 4-neighbor normal (smooth gradient)
    for (int i = 0; i < points; i++) {
      for (int j = 0; j < points; j++) {
        PVector n = PVector.cross(PVector.sub(P[i][max(j - 1, 0)], P[i][min(j + 1, points - 1)]), PVector.sub(P[max(i - 1, 0)][j], P[min(i + 1, points - 1)][j]), null).normalize();
        if (i != $ && j != $) N[i - 0][j - 0][0].set(n);
        if (i != $ && j != 0) N[i - 0][j - 1][1].set(n);
        if (i != 0 && j != 0) N[i - 1][j - 1][2].set(n);
        if (i != 0 && j != $) N[i - 1][j - 0][3].set(n);
      }
    }
    break;
  case 3: // 6-neighbor normal (even smoother gradient)
    for (int i = 0; i < points; i++) {
      for (int j = 0; j < points; j++) {
        int iL = max(i - 1, 0), iH = min(i + 1, $), jL = max(j - 1, 0), jH = min(j + 1, $);
        PVector A = P[iL][jL], U = P[iL][j], R = P[i][jH], B = P[iH][jH], D = P[iH][j] ,L = P[i][jL];
        PVector nXY = PVector.cross(PVector.sub(R, L), PVector.sub(D, U), null).normalize();
        PVector nXZ = PVector.cross(PVector.sub(R, L), PVector.sub(B, A), null).normalize();
        PVector nYZ = PVector.cross(PVector.sub(B, A), PVector.sub(D, U), null).normalize();
        PVector sum = new PVector().add(nXY).add(nXZ).add(nYZ);
        if (i != $ && j != $) N[i - 0][j - 0][0].set(sum);
        if (i != $ && j != 0) N[i - 0][j - 1][1].set(sum);
        if (i != 0 && j != 0) N[i - 1][j - 1][2].set(sum);
        if (i != 0 && j != $) N[i - 1][j - 0][3].set(sum);
      }
    }
    break;
  }
}

void draw() {
  background(32);
  
  if (showMesh)
    stroke(0);
  else
    noStroke();
  if (trueLights) lights();
  
  beginShape(QUADS);
  for (int i = 0; i < points - 1; i++) {
    for (int j = 0; j < points - 1; j++) {
      for (int k = 0; k < 4; k++) {
        if (trueLights) {
          fill(255);
          normal(N[i][j][k].x, N[i][j][k].y, N[i][j][k].z);
        } else fill(255 * get(hmap, i, j, k));
        vertex(get(P, i, j, k).x, get(P, i, j, k).y, flatten ? 0 : get(P, i, j, k).z);
      }
    }
  }
  endShape();

  if (showNormals) {
    float tileSize = tileScale * (float)height / (points - 1);
    color[] colors = {color(255, 0, 0), color(255, 255, 0), color(0, 255, 0), color(0, 0, 255)};
    for (int i = 0; i < points - 1; i++) {
      for (int j = 0; j < points - 1; j++) {
        for (int k = 0; k < 4; k++) {
          stroke(colors[k]);
          PVector p = get(P, i, j, k), n = N[i][j][k];
          line(p.x, p.y, p.z, p.x + tileSize * n.x, p.y + tileSize * n.y, p.z + tileSize * n.z);
        }
      }
    }
  }
}

void keyPressed() {
  switch (key) {
  case ' ':
    updateMap();
    setLights();
    break;
  case 'm':
    showMesh = !showMesh;
    break;
  case 'l':
    trueLights = !trueLights;
    break;
  case 'c':
    lightingStyle = (lightingStyle + 1) % 4;
    setLights();
    break;
  case 'f':
    flatten = !flatten;
    break;
  case 'n':
    showNormals = !showNormals;
    break;
  }
}

final int[][] coords = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};

float get(float[][] v, int i, int j, int k) {
  return v[i + coords[k][0]][j + coords[k][1]];
}

PVector get(PVector[][] v, int i, int j, int k) {
  return v[i + coords[k][0]][j + coords[k][1]];
}
3 Likes