HexGrid with Processing's polygon function

Source code below will create a scalable grid of hexagons using Processing’s polygon function to create the hexagons:

void polygon(float x, float y, float radius, int npoints) {
  float angle = TWO_PI / npoints;
  beginShape();
  for (float a = 0; a < TWO_PI; a += angle) {
    float sx = x + cos(a) * radius;
    float sy = y + sin(a) * radius;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

void hexGrid(float x, float y, float radius, int cols, int rows) {
  float centerX;
  float centerY;

  for (int k = 0; k < rows; k++) {
    for (int j = 0; j < cols; j++) {
      centerX = x + j*(radius + 0.5*radius);
      centerY = y + k*(radius + 0.7*radius);
      if (j % 2 != 0) {
        centerY += 0.86*radius; // additional y offset for odd columns
      }
      stroke(0);
      fill(random(255), random(255), random(255)); 
      polygon(centerX, centerY, radius, 6);  // Hexagon
    }
  }
}

void setup() {
  size(600, 620);
  background(209);
  hexGrid(80, 80, 50, 7, 6);
}

Output:

4 Likes

Nice.

What about a version for Settlers of Catan ?

Here the polygons of the board are in a circle.
:grinning:

1 Like

Settlers of Catan version:

Thanks @Chrisir Had to think about that for a minute. I’m not familiar with the game, but according to images on the web the board has five rows and a variable number of columns (ranging from three to five) depending on the row number. The first challenge was to rotate the polygon 90 degrees so that there is a flat surface to align with succeeding polygons; this can be achieved by reversing ‘sin’ and ‘cos’ in the polygon function. Then it becomes a matter of altering the nested ‘for’ loops to handle the variable number of columns depending on row number. The demo below is not scalable, and is hard coded uniquely for the Settlers of Catan board.

void polygon(float x, float y, float radius, int npoints) {
  float angle = TWO_PI / npoints;
  beginShape();
  for (float a = 0; a < TWO_PI; a += angle) {
    float sx = x + sin(a) * radius;
    float sy = y - cos(a) * radius;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

void settlersOfCatanGrid(float x, float y, float radius) {
  float centerX;
  float centerY;
  int rows = 5;
  int cols = 0; //init value will change depending on row num

  for (int k = 0; k < rows; k++) {
    if ((k == 0)||(k == 4)) {
      cols = 3;
    } else if ((k == 1)||(k == 3)) {
      cols = 4;
    } else if (k == 2) {
      cols = 5;
    }
    for (int j = 0; j < cols; j++) {
      centerX = x + j*(radius + 0.7*radius);
      centerY = y + k*(radius + 0.5*radius);
      if (k % 2 != 0) { // -xOffset for rows 1,3
        centerX -= 0.86*radius;
      }
      if (k == 2) { // -xOffset for row 2
        centerX -= 2*0.86*radius;
      }
      stroke(0);
      fill(random(255), random(255), random(255));
      polygon(centerX, centerY, radius, 6);  // Hexagon
    }
  }
}

void setup() {
  size(620, 560);
  background(209);
  settlersOfCatanGrid(220, 100, 60);
}

4 Likes

@svan Grid patterns are a current focus of mine. So this is very interesting!!
And @Chrisir’s suggestion reminded me of a post by @hamoid a while back that shows another possible(?) solution to this problem using modulo.

Scroll down to Example 9B:

:nerd_face:

2 Likes

@debxyz Thanks for your interest and the reference to @hamoid 's code in the gallery. He also uses nested ‘for’ loops, but has added the interesting concept of not displaying graphics beyond a certain radius from the center of the pattern, which approximates a circle. However, I was not able to exactly replicate the Settlers of Catan board, perhaps due to an incomplete understanding of how his technique works. The line if (dist(j, k, 4.5, 4.5) < 4) { is a bit of a mystery to me and so far I am unable to grasp how it works. With my apologies for altering his demo, I am able to approximate his example’s output with this code (note that it does not use modulo), but so far cannot simulate the board.

void grid(int x, int y, int cols, int rows, float radius, int gap) {
  for (int j = 0; j < rows; j++) {
    for (int k = 0; k < cols; k++) {
      if (dist(j, k, 4.5, 4.5) < 4) {
        fill(random(255), random(255), random(255));
        circle(x + j*(radius+gap), y + k*(radius+gap), radius);
      }
    }
  }
}

void setup() {
  size(300, 300);
  background(209);
  grid(50, 40, 10, 10, 18, 4);
}

Output:

I just realized after reading @hamoid’s code again that the use of modulo was for determining fill color! And as you noted it is the dist() function that controls whether a shape prints or not… oops. Sorry for the misdirection!!
I agree this:

Is mysterious…
My thought is that is is being used as a counter? Perhaps?

:nerd_face:

I find it easiest to work with hexagons using what Amit of redblobgames.com calls doubled coordinates where the hexagons have integer coordinates that are either both even or both odd. Makes it very efficient to go between pixel and hex coordinates. You can divide either hex coordinate by 2 depending on whether you use flat- or pointy-bottomed hexes to index into a 2-D array for storing data.

void polygon(float x, float y, float radius, int npoints) {
  beginShape();
  for( int i=0; i<npoints; i++ ) {
    float a = TAU * i / npoints;
    float sx = x + sin(a) * radius;
    float sy = y - cos(a) * radius;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

float Sx = sqrt(3)/2, Sy = 1.5;
class HexPos {
  float dx, dy;
  int i, j;
  HexPos( float dx, float dy, int i, int j ) {
    this.dx = dx;  this.dy = dy;  this.i = i;  this.j = j;
  }
}

// https://www.redblobgames.com/grids/hexagons/#coordinates-doubled
// https://www.redblobgames.com/grids/hexagons/#conversions-doubled
HexPos hexParity( float x, float y ) {
  int i = floor( x / Sx );
  int j = floor( y / Sy );
  int parity = (i+j)&1;
  float lx = x - Sx * i;
  float ly = y - Sy * (j+parity);
  float rx = x - Sx * (i+1);
  float ry = y - Sy * (j+1-parity);
  return ( lx*lx+ly*ly < rx*rx+ry*ry ) ?
    new HexPos( lx, ly, i,   j+parity ) :
    new HexPos( rx, ry, i+1, j+1-parity );
}

void catan() {
  for( int j=-2; j<=2; j++ )
    for( int i=-4+abs(j); i<=4-abs(j); i+=2 ) {
      fill( random(1,6), random(5,8), random(5,9) );
      polygon( Sx*i, Sy*j, 0.98, 6 );
    }
}

float viewScale;
HexPos mouseHex;

void setup() {
  size( 400, 400 );
  viewScale = height/12.;
  mouseHex = new HexPos(0,0,10,10);  // off screen
  colorMode( HSB, 10, 10, 10, 10 );
  noStroke();
  noLoop();
}

void draw() {
  background( 0 );
  translate( width/2, height/2 );
  scale( viewScale );
  randomSeed( 5 );
  catan();
  fill( 0, 0, 10, 5 );
  polygon( mouseHex.i*Sx, mouseHex.j*Sy, 0.9, 6 );
}

void mouseMoved() {
  mouseHex = hexParity( (mouseX-width/2)/viewScale, (mouseY-height/2)/viewScale );
  redraw();
}
2 Likes

@scudly Thank you for your comments, code and valuable resources; I did not realize how much work had already been done on this subject.

The following demo is a revision of my code for the ‘catan’ hexGrid to make it more scalable. The main difference is that the number of columns in each row is now stored in an array. The maximum array column size is then obtained with ‘max()’ and row length disparity is calculated for each row in the loop. Each row’s xOffset is then adjusted according to the disparity. Since the spike end of the hexagon is pointing upward and affects row spacing, the same number of columns should not be used in succession in the array. The model is scalable as long as the ‘catan’ format is adhered to, i.e., initial ascending and then descending values flanking a single maximum length row. Other polygon shapes may be used as well; a ‘catan’ shaped grid with triangles as well as a scaled up hexagon grid is shown following the code:

int[] colNum = {3, 4, 5, 4, 3}; // avoid repeating same number in succession
//int[] colNum = {5, 6, 7, 8, 9, 8, 7, 6, 5}; // scaled up with same format as above
int colMax = max(colNum);
int lenDisparity = 0;
int counter = 0;

void polygon(float x, float y, float radius, int npoints) {
  float angle = TWO_PI / npoints;
  beginShape();
  for (float a = 0; a < TWO_PI; a += angle) {
    float sx = x + sin(a) * radius;
    float sy = y - cos(a) * radius;
    vertex(sx, sy);
  }
  endShape(CLOSE);
}

void catanGrid(float x, float y, float radius) {
  float centerX;
  float centerY;
  for (int k = 0; k < colNum.length; k++) {
    for (int j = 0; j < colNum[counter]; j++) {
      centerX = x + j*(radius + 0.7*radius);
      centerY = y + k*(radius + 0.5*radius);
      lenDisparity = colMax - colNum[counter];
      if ( lenDisparity > 0) {
        centerX += lenDisparity*0.86*radius;
      }
      stroke(0);
      fill(random(255), random(255), random(255));
      polygon(centerX, centerY, radius, 6);  // Hexagon
    }
    counter++;
  }
}

void setup() {
  size(620, 560);
// size(1200, 1000);// For scaling up
  background(209);
  catanGrid(120, 100, 60);
}


Slight Modification for Circles, other shapes:

A slight modification of the model will allow for the creation of unusual grids with circles, text, or other untested shapes. The ‘catan’ grid shape is not the only shape possible since there is no hexagon spike interference; with this modification an array of any column numbers may be used without regard to format: a complex example is shown below the source code:

int[] colNum = {3, 3, 6, 3, 4, 5, 5, 4, 4, 3, 10}; // complex
//int[] colNum = {3, 4, 5, 4, 3}; // catan
int colMax = max(colNum);
int lenDisparity = 0;
int counter = 0;

void circleGrid(int l, int t, float radius, int hgap, int vgap) {
  int x;
  int y;
  for (int k = 0; k < colNum.length; k++) {
    for (int j = 0; j < colNum[counter]; j++) {
      x = l + j*vgap;
      y = t + k*hgap;
      lenDisparity = colMax - colNum[counter];
      if ( lenDisparity > 0) {
        x += lenDisparity*vgap/2;
      }    
      fill(random(255),random(255),random(255));
      circle(x,y,radius);
    }
    counter++;
  }
}

void setup() {
  size(440, 440);
  background(209);
  circleGrid(60, 60, 28, 30, 30);
}

4 Likes
void catan( int n ) {
  for( int j=-n; j<=n; j++ )
    for( int i=-2*n+abs(j); i<=2*n-abs(j); i+=2 ) {
      fill( random(1,6), random(5,8), random(5,9) );
      polygon( Sx*i, Sy*j, 0.98, 6 );
    }
}

generalizes to a hex of hexes that’s 2n+1 across.

If you’ll accept the occasional pentagon, you can also wrap them around spheres in what’s called a Goldberg polyhedron.

https://twitter.com/scdollins/status/1319349762408157184

4 Likes