modify the following project to use a infinite configuration of conways game of life as the ground. Use shaders where possible, to optimise rendering. Make living cells act in 3d, applying force to the player.


import java.awt.*;

Input input;
Camera cam;
Player p;
Ground g;
Robot r;

void keyPressed()
{
  input.keyPressed();
}

void keyReleased()
{
  input.keyReleased();
}

void mouseMoved()
{
  p.mouseMovedAndDragged();
}

void mouseDragged()
{
  p.mouseMovedAndDragged();
}

void setup()
{
  // Init game
  fullScreen(P3D);
  noCursor();
  
  // Init objects
  input = new Input();
  cam = new Camera();
  p = new Player();
  g = new Ground();
  try {
  r = new Robot();
  }
  catch (AWTException e)
  {
    e.printStackTrace();
  }
}

void draw()
{
  // UPDATE
  p.update();
  
  // DRAW
  background(0);
  cam.draw();
  g.draw();
  p.draw();
  
  // DRAW GUI
  hint(DISABLE_DEPTH_TEST);
  camera();
  noLights();
  p.drawGui();
  hint(ENABLE_DEPTH_TEST);
}

class Camera
{
  PVector eye, center, up;
  
  Camera()
  {
    eye = new PVector(0, -5, 10);
    center = new PVector(0, 0, 0);
    up = new PVector(0, 1, 0);
    perspective(PI/3.0, 16.0 / 9.0, 5, 1000);
  }
  
  void draw()
  {
    camera(eye.x, eye.y, eye.z, center.x, center.y, center.z, up.x, up.y, up.z);
  }
}

class Ground {
  PVector pos;
  PShape ground;
  PImage tex;
  float w, h, d;
  
  GameOfLife gameOfLife;
  PImage gofTexture;
  
  Ground(GameOfLife gol) {
    w = 1000; // Increased size for a larger ground
    d = 1000;
    pos = new PVector(0, 0, 0);
    gameOfLife = gol;
    gofTexture = gameOfLife.getGridTexture();
    
    // Create a large ground plane
    ground = createShape();
    ground.beginShape(QUADS);
    ground.texture(gofTexture);
    ground.normal(0, 1, 0);
    ground.vertex(-w * 0.5, 0, -d * 0.5, 0, 0);
    ground.vertex(w * 0.5, 0, -d * 0.5, 1, 0);
    ground.vertex(w * 0.5, 0, d * 0.5, 1, 1);
    ground.vertex(-w * 0.5, 0, d * 0.5, 0, 1);
    ground.endShape();
  }
  
  void update() {
    gameOfLife.update();
    gofTexture = gameOfLife.getGridTexture();
    ground.setTexture(gofTexture);
  }
  
  void draw() {
    pushMatrix();
    translate(pos.x, pos.y, pos.z);
    textureWrap(REPEAT);
    shader(gameOfLifeShader);
    shape(ground);
    resetShader();
    popMatrix();
  }
}

class Input
{
  // Keys
  int[] keys = new int[9];
  
  void keyPressed()
  {
    if (key == 'a' || key == 'A') { keys[0] = 1; }
    else if (key == 'd' || key == 'D') { keys[1] = 1; }
    else if (key == 'w' || key == 'W') { keys[2] = 1; }
    else if (key == 's' || key == 'S') { keys[3] = 1; }
    else if (key == ' ') { keys[4] = 1; }
  }
  
  void keyReleased()
  {
    if (key == 'a' || key == 'A') { keys[0] = 0; }
    else if (key == 'd' || key == 'D') { keys[1] = 0; }
    else if (key == 'w' || key == 'W') { keys[2] = 0; }
    else if (key == 's' || key == 'S') { keys[3] = 0; }
    else if (key == ' ') { keys[4] = 0; }
  }
  
  // Move inputs
  int getMoveLeft() { return keys[0]; }
  int getMoveRight() { return keys[1]; }
  int getMoveForward() { return keys[2]; }
  int getMoveBackward() { return keys[3]; }
  int getActionJump() { return keys[4]; }
}

class Player
{
  PShape hand;
  PImage tex;
  
  GameOfLife gameOfLife;
  float interactionRadius = 50; // Adjust as needed

  PVector pos, hVel, hDir, vDir;
  float yVel, jumpStrength, moveSpeed;
  float w, h, d;
  float camMinDist;
  float gravStrength;
  
  Player(GameOfLife gol)
  {
    // Dimensions
    w = 2;
    h = 2;
    d = 2;
    
    //gol
    gameOfLife = gol;

    // Movement
    pos = new PVector(0, 0, 0);
    hVel = new PVector(0, 0);
    yVel = 0;
    moveSpeed = 0.6;
    jumpStrength = 0.3;
    
    // Camera
    hDir = new PVector(0, 1);
    vDir = new PVector(0.7, -0.7);
    camMinDist = 20;
    
    // World
    gravStrength = 0.01;
    
    // Model
    hand = loadShape("hand.obj");
    hand.setFill(color(255));
    //tex = loadImage("grid.png");
    //hand.setTexture(tex);
  }
  
  void mouseMovedAndDragged()
  {
    // Rotate horizontally
    if (pmouseX == int(width * 0.5)) { hDir.rotate((mouseX - pmouseX) * 0.003); }
    
    // Rotate vertically
    if (pmouseY == int(height * 0.5))
    {
      // Clamp rotation
      float currAngle = vDir.heading();
      float goalDir = (mouseY - pmouseY) * 0.003;
      if ((goalDir > 0 && currAngle + goalDir < -0.2) || (goalDir < 0 && currAngle + goalDir > -1.3)) { vDir.rotate(goalDir); }
    }
    
    // Move mouse back to center
    r.mouseMove(int(width * 0.5), int(height * 0.5));
  }
  
  boolean checkOnGround()
  {
    if (pos.y >= 0) { return true; }
    return false;
  }
  
  void update()
  {
    // Movement
    hVel.x = 0;
    hVel.y = 0;
    if (input.getMoveForward() == 1)
    {
      hVel.x -= hDir.x;
      hVel.y -= hDir.y;
    }
    if (input.getMoveBackward() == 1)
    {
      hVel.x += hDir.x;
      hVel.y += hDir.y;
    }
    if (input.getMoveLeft() == 1)
    {
      hDir.rotate(PI * 0.5);
      hVel.x += hDir.x;
      hVel.y += hDir.y;
      hDir.rotate(-PI * 0.5);
    }
    if (input.getMoveRight() == 1)
    {
      hDir.rotate(-PI * 0.5);
      hVel.x += hDir.x;
      hVel.y += hDir.y;
      hDir.rotate(PI * 0.5);
    }
    hVel.mult(moveSpeed);
    //hVel.normalize();
    
    // Ground check
    if (checkOnGround())
    {
      // Jump check
      if (input.getActionJump() == 1)
      {
        // Jump
        yVel = -jumpStrength;
        println(yVel);
      }
    }
    // Gravity
    else
    {
      yVel += gravStrength;
      if (pos.y + yVel >= 0)
      {
        // Land
        yVel = 0;
        pos.y = 0;
      }
    }

    applyGameOfLifeForces();
    
    // Position
    pos.add(hVel.x, yVel, hVel.y);
    
    // Camera movement
    cam.eye.x = pos.x;
    cam.eye.z = pos.z;
    cam.eye.y = pos.y;
    cam.center.x = pos.x;
    cam.center.y = pos.y;
    cam.center.z = pos.z;
    
    // Camera rotation
    cam.eye.x += hDir.x * camMinDist;
    cam.eye.y -= vDir.x * camMinDist;
    cam.eye.z += hDir.y * camMinDist;
  }

    void applyGameOfLifeForces() {
    // Determine which cells are near the player
    int cols = gameOfLife.cols;
    int rows = gameOfLife.rows;
    float cellSize = gameOfLife.cellSize;
    
    // Convert player position to grid coordinates
    int playerGridX = floor((pos.x + g.w * 0.5) / cellSize) % cols;
    int playerGridZ = floor((pos.z + g.d * 0.5) / cellSize) % rows;
    
    for (int i = -5; i <= 5; i++) {
      for (int j = -5; j <= 5; j++) {
        int xi = (playerGridX + i + cols) % cols;
        int yj = (playerGridZ + j + rows) % rows;
        if (gameOfLife.grid[xi][yj]) {
          // Calculate distance from the player to the cell
          float cellX = xi * cellSize - g.w * 0.5;
          float cellZ = yj * cellSize - g.d * 0.5;
          PVector cellPos = new PVector(cellX, 0, cellZ);
          PVector playerPos = new PVector(pos.x, pos.y, pos.z);
          PVector diff = PVector.sub(playerPos, cellPos);
          float distance = diff.mag();
          
          if (distance < interactionRadius) {
            // Apply force inversely proportional to distance
            float forceMag = map(distance, 0, interactionRadius, 5, 0);
            PVector force = diff.copy().normalize().mult(forceMag);
            pos.add(force.x, force.y * 0.5, force.z); // Adjust Y for upward force
          }
        }
      }
    }
  }

  
  void draw()
  {
    // Model
    pushMatrix();
    translate(pos.x, pos.y - h * 0.5, pos.z);
    rotateX(-PI * 0.5);
    rotateZ(-hDir.heading() + PI * 0.5);
    rotateY(-PI * 0.5);
    shape(hand);
    popMatrix();
    
    // Collision box
    pushMatrix();
    translate(pos.x, pos.y - h * 0.5, pos.z);
    noFill();
    stroke(255);
    box(w, h, d);
    stroke(0);
    fill(255);
    popMatrix();
  }
  
  void drawGui()
  {
    fill(255);
    textSize(32);
    textAlign(LEFT, TOP);
    text("Pos: (" + int(pos.x) + ", " + int(pos.y) + ", " + int(pos.z) + ")", 0, 0);
  }
}