How to fill in a checker pattern with PVectors?

Hi, I am having a hard time changing the fill(); on the following terrain code. How can I fill(); in a black and white pattern?

import processing.svg.*;

boolean record;

// 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(980, 980, P3D); //OPENGL?
  noLoop();
  frameRate(1);
  smooth(8);
  halfWidth  = width/2.;
  halfHeight = height/2.;
  initGrid(7.);
  // 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("gradients88_2.jpg");
  img.loadPixels();
  img.resize(980,980);
  // 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.9));
    }
  }
}

void draw() 
{
    background(0);
   if (record) {
    beginRaw(SVG, "output_September19_2023.svg");
  }
 
  

  // set the origin to the center of the screen, and move it 400px far away on z-axis
  translate(halfWidth, halfHeight, 0.05);  // zoom in (lower numbers) zoom out (higher numbers around 200)
  

  // 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(2);
    smooth(8);
    
  }

  // 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 (QUAD_STRIP); // or (TRIANGLE_STRIP),(QUAD_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();
    }
      if (record) {
       endRaw();
       record = false;
  }
}
// Hit 'p' to record a single frame
void keyPressed(){
  if (key == 'p') 
    {
    record = true;
    }
  redraw();
  }

You probably can’t do it easily using fill(). Triangle or quad strips interpolate the color from one vertex to the next, so if you made one vertex white and the next black, you’d get shades of gray between them.

You could, however, use a checkerboard texture.

2 Likes

You can also draw rectangles instead of using vertex

Or box(20,5,20);

3 Likes

Hi @asymmetric,

just another quick one …

Cheers
— mnse

PVector[][] grid;
void setup() {
  size(500, 500);
  int size = 50;
  int scl  = floor(width/size);
  grid = new PVector[scl+1][scl+1];
  for (int y=0; y <= scl; y++) {
    for (int x=0; x <= scl; x++) {
      grid[y][x] = new PVector(x*size, y*size);
    }
  }
}

void draw() {
  background(0);
  noStroke();
  for (int y = 0; y < grid.length-1; y++) {
    beginShape(QUAD_STRIP); 
    for (int x = 0; x < grid[0].length; x++) {
      PVector p1 = grid[y][x];
      PVector p2 = grid[y+1][x];
      fill((y*grid[0].length+x) % 2 == 0 ? 255 : 0);
      vertex(p1.x, p1.y);
      vertex(p2.x, p2.y);
    }
    endShape();
  }
}

Addon:
if you need to use opengl renderer like P2D or P3D the above method would fail as mentioned by @scudly. For this you need another approach to assign the correct corner colors, like ie…

PVector[][] grid;

void setup() {
  size(500, 500, P3D);  
  int size = 50;
  int scl  = floor(width/size);
  grid = new PVector[scl+1][scl+1];
  for (int y=0; y <= scl; y++) {
    for (int x=0; x <= scl; x++) {
      grid[y][x] = new PVector(x*size, y*size);
    }
  }
}

void draw() {
  background(255,0,0);
  noStroke();
  for (int y = 0; y < grid.length-1; y++) {    
    for (int x = 0; x < grid[0].length-1; x++) {
      beginShape();
      PVector p1 = grid[y][x];
      PVector p2 = grid[y+1][x];
      PVector p3 = grid[y+1][x+1];
      PVector p4 = grid[y][x+1];      
      fill((y*grid[0].length+x) % 2 == 0 ? 255 : 0);
      vertex(p1.x, p1.y);
      vertex(p2.x, p2.y);
      vertex(p3.x, p3.y);
      vertex(p4.x, p4.y);
      endShape();
    }    
  }
}

3 Likes

Using QUAD_STRIP will be slightly more efficient.

This just collapses every other square to zero-width. Nearly the same code as @mnse, I just had to swap the vertex order for p3 and p4.

PVector[][] grid;

void setup() {
  size(500, 500, P3D);  
  int size = 50;
  int scl  = floor(width/size);
  grid = new PVector[scl+1][scl+1];
  for (int y=0; y <= scl; y++) {
    for (int x=0; x <= scl; x++) {
      grid[y][x] = new PVector(x*size, y*size);
    }
  }
}

void draw() {
  background(255,0,0);
  noStroke();
  for (int y = 0; y < grid.length-1; y++) {    
    beginShape( QUAD_STRIP );
    for (int x = 0; x < grid[0].length-1; x++) {
      PVector p1 = grid[y][x];
      PVector p2 = grid[y+1][x];
      PVector p3 = grid[y][x+1];      
      PVector p4 = grid[y+1][x+1];
      fill((y*grid[0].length+x) % 2 == 0 ? 255 : 0);
      vertex(p1.x, p1.y);
      vertex(p2.x, p2.y);
      vertex(p3.x, p3.y);
      vertex(p4.x, p4.y);
    }    
    endShape();
  }
}

or you can make it with a single QUAD_STRIP for even better efficiency if use a zero-area triangle to connect the ends of the strips. Then, of course, store it all in a PShape so you’re not re-creating it every frame.

PVector[][] grid;

void setup() {
  size(500, 500, P3D);  
  int size = 50;
  int scl  = floor(width/size);
  grid = new PVector[scl+1][scl+1];
  for (int y=0; y <= scl; y++) {
    for (int x=0; x <= scl; x++) {
      grid[y][x] = new PVector(x*size, y*size);
    }
  }
}

void draw() {
  background(255,0,0);
  noStroke();
  beginShape( QUAD_STRIP );
  PVector p1, p2, p3, p4 = grid[0][0];
  for (int y = 0; y < grid.length-1; y++) {    
    for (int x = 0; x < grid[0].length-1; x++) {
      p1 = grid[y][x];
      p2 = grid[y+1][x];
      p3 = grid[y][x+1];      
      p4 = grid[y+1][x+1];
      fill((y*grid[0].length+x) % 2 == 0 ? 255 : 0);
      vertex(p1.x, p1.y);
      vertex(p2.x, p2.y);
      vertex(p3.x, p3.y);
      vertex(p4.x, p4.y);
    }   
    vertex(p4.x, p4.y);
    vertex(p4.x, p4.y);
  }
  endShape();
}
1 Like

@scudly and @mnse I am having a hard time connecting the checkerboard fill to the terrain code provided at the top…

Thank you so much for your help everyone!

Within your innermost loop in draw() (across x) you are calling vertex() twice, once each for the top and bottom edges of the strip. Instead, first set the fill to either white or black and then call vertex() four times: x top and bottom and x+1 top and bottom. Each top/bottom pair of vertexes will end up getting set twice at the same location, but with different fill() colors. Run your x loop only to x < cols-1 since it’s now including the x+1 vertexes.

1 Like

I am still having a hard time connecting my code with the black and white checkerboard code provided… Please help!

Is this what it’s supposed to look like?

In your draw(), for your loops over y and x change your loop bodies to the code below. Change your scale from 7 to 32 so you can see better what it’s doing. I’m calling each pair of vertexes twice, once as white and once as black (or the reverse). (x+y)&1 tells us if we’re on an even or odd square so I use that to set the fill.

      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y+1][x];
      fill( 255*((x+y)&1) );
      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);
        }
      fill( 255-255*((x+y)&1) );
      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);
        }

1 Like

Thank you so much, @scudly !!!