How to link grid to image as a height map

I’m interested in morphing the grid with randomness while also having an image interact with the grid. How do I go about designing varying cells and joining them together as a grid image? Can I use the random(); with (TRIANGLE_STRIP),(QUAD_STRIP) to create a variation?

f29b0ec3811fbc1e82188a290247f10f6e903ac0_2_484x500
An image of a grid with random variation.


My current code using (TRIANGLE_STRIP);.

Hi @asymmetric,

Here is an example how to link grid points to an image, and how you can move

  • type r to switch random on and off
  • type space to reset
  • if random mode off you can capture the points and move them by mouse in a predefined range

You can play around with it a bit to get a better understanding… hope that helps…

Cheers
— mnse

PS: to make it easier I striped down to 2D. If you’ve understand this, it would be easy for you to add the z axis as heightmap …

//helper variables

float scl = 50.;
int rows, cols;
boolean doRandomMove = false; 

boolean mode = true;
GridPoint[][] grid;
// image used for texture and heightmap
PImage img;

class GridPoint {
  float ox, oy, ou, ov;
  float x, y, u, v;
  float sz=10;
  boolean locked;
  boolean dragged;
  float angle;
  public GridPoint(float px, float py, float pu, float pv, boolean plocked) {
    ox=x=px;
    oy=y=py;
    ou=u=pu;  // u-pos of the image
    ov=v=pv;  // v-pos of the image
    locked = plocked;
    dragged=false;
    angle = random(TWO_PI);
  }

  public void reset() {
    x=ox;
    y=oy;
    u=ou;
    v=ov;
  }

  // automover 
  public void move() {
    if (!locked) {
      PVector r=PVector.random2D();
      x=ox+0.2*scl*cos(angle);
      y=oy+0.2*scl*sin(angle);
      angle+=random(0.05, 0.1);
    }
  }

  // manual mouse movement below
  public void capture(float mx, float my) {
    if (dist(mx, my, x, y) < sz) {
      dragged=true;
    } else       
    dragged=false;
  }  

  public void move(float mx, float my) {
    if (dragged) {
      // remove check to not bound to surrounding box
      if (dist(mx, my, ox, oy) < scl) {
        x=mx;
        y=my;
        // uncomment move point but keep image
        //u=x/((cols-1)*scl);
        //v=y/((rows-1)*scl);
      }
    }
  }

  public void release() {
    dragged=false;
  }

  public void render() {
    pushStyle();
    noStroke();
    if (locked)
      fill(255, 0, 0);
    else if (dragged) 
      fill(255, 128, 64);
    else
      fill(0, 255, 0);


    ellipse(x, y, sz, sz);
    popStyle();
  }
}


void setup() {
  size(1000, 1000, P2D);  
  //  smooth(10);  
  // using normal to be independent to img size
  textureMode(NORMAL);
  // initialize our gridpoints
  rows = floor(height/scl)+1;
  cols = floor(width/scl)+1;
  grid= new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      float sx = x*scl;
      float sy = y*scl;
      // u,v can also be written by x/(cols-1), y/(rows-1) casted to floats
      grid[y][x] = new GridPoint(sx, sy, sx/((cols-1)*scl), sy/((rows-1)*scl), (x==0|y==0|x==cols-1||y==rows-1));
    }
  } 
  img = loadImage("aimage.jpg");
}

void mousePressed() {
  if (!doRandomMove) {
    for (int y = 0; y < rows; y++) {
      for (int x = 0; x < cols; x++) {
        grid[y][x].capture(mouseX, mouseY);
      }
    }
  }
}

void mouseDragged() {
  if (!doRandomMove) {
    for (int y = 0; y < rows; y++) {
      for (int x = 0; x < cols; x++) {
        grid[y][x].move(mouseX, mouseY);
      }
    }
  }
}

void mouseReleased() {
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      grid[y][x].release();
    }
  }
}

void keyPressed() {
  if (key == ' ') {
    for (int y = 0; y < rows; y++) {
      for (int x = 0; x < cols; x++) {
        grid[y][x].reset();
      }
    }
  } else if (key == 'r') {
    doRandomMove=!doRandomMove;
  }
}

void updateRandom() {
  for (int y = 0; y < rows; y ++) {
    for (int x = 0; x < cols; x++) {
      grid[y][x].move();
    }
  }
}

void draw() {
  background(0);
  if (doRandomMove) {
    updateRandom();
  }  

  // draw grid ... set noStroke to remove lines
  stroke(0, 255, 0);  
  // 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), 
    texture(img); // on textured mode set the texture
    // common triangle strips
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      vertex(ca.x, ca.y, ca.u, ca.v);
      GridPoint cb = grid[y+1][x];
      vertex(cb.x, cb.y, cb.u, cb.v);
    }
    endShape();
  }

  // draw grid points.
  for (int y = 0; y < rows; y ++) {
    for (int x = 0; x < cols; x++) {
      grid[y][x].render();
    }
  }
}
1 Like

Hi @asymmetric,

just for the sake of completness … I extended to 3D
Removed all the mouse picking stuff, as picking in 3D is another topic and would go beyond the scope here…

Cheers
— mnse

float scl = 8.;
int rows, cols;
int halfWidth;
int halfHeight;
boolean mode = true;

GridPoint[][] grid;
PImage img;

class GridPoint {
  PVector position;
  PVector oposition;
  PVector uv;
  boolean locked;
  float phase;
  public GridPoint(float x, float y, float z, float u, float v, boolean plocked) {
    position  = new PVector(x, y, z);
    oposition = position.copy();
    uv = new PVector(u, v);    
    locked = plocked;
    phase = 0;
  }

  public void reset() {
    position = oposition.copy();
  }

  // automover 
  public void move() {
    if (!locked) {
      PVector wc = new PVector(position.x/25., position.y/25.);
      position.z=25.*sin((sqrt(wc.x*wc.x + wc.y*wc.y)+phase));
      phase-=0.1;
    }
  }

  public void render() {
    pushMatrix();
    pushStyle();
    noStroke();
    if (locked)
      fill(255, 0, 0);
    else
      fill(0, 255, 0);
    translate(position.x, position.y, position.z);
    box(3.);
    popStyle();
    popMatrix();
  }
}


void setup() {
  size(800, 800, P3D);
  halfWidth=width/2;
  halfHeight=height/2;
  textureMode(NORMAL);

  rows = floor(height/scl)+1;
  cols = floor(width/scl)+1;
  grid= new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      float sx = x*scl;
      float sy = y*scl;
      grid[y][x] = new GridPoint(sx-halfWidth, sy-halfHeight, 0.0, sx/((cols-1)*scl), sy/((rows-1)*scl), false /*(x==0|y==0|x==cols-1||y==rows-1)*/);
    }
  } 
  img = loadImage("image.jpg");
}

void updateRandom() {
  for (int y = 0; y < rows; y ++) {
    for (int x = 0; x < cols; x++) {
      grid[y][x].move();
    }
  }
}

void draw() {
  background(0, 0, 64);
  updateRandom();
  directionalLight(255, 255, 255, 0, 0, -1);

  translate(halfWidth, halfHeight, -400.);
  rotateX(radians(55));

  noStroke();
  if (mousePressed)
    stroke(96);

  for (int y = 0; y < rows-1; y ++) {
    beginShape (TRIANGLE_STRIP); // or (TRIANGLE_STRIP),(QUAD_STRIP), 
    texture(img); // on textured mode set the texture
    // common triangle strips
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y+1][x];
      GridPoint cc = grid[y][x<cols-1 ? x+1 : x-1];
      // this normal is good enough here
      PVector n = PVector.sub(cb.position, ca.position).normalize().cross(PVector.sub(cc.position, ca.position).normalize()); 
      normal(n.x, n.y, n.z);
      vertex(ca.position.x, ca.position.y, ca.position.z, ca.uv.x, ca.uv.y);     
      normal(n.x, n.y, n.z);            
      vertex(cb.position.x, cb.position.y, cb.position.z, cb.uv.x, cb.uv.y);
    }
    endShape();
  }
  if (mousePressed) {
    for (int y = 0; y < rows; y ++) {
      for (int x = 0; x < cols; x++) {
        grid[y][x].render();
      }
    }
  }
}
1 Like