// Global Variables
float time = 0;      // Total elapsed time in seconds
float prevTime = 0;  // Time of the previous frame in seconds


// Player variables
PVector playerPos;
PVector playerVel;
boolean isJumping = false;

// Obstacle variables
ArrayList<Box> obstacles;
float spawnInterval = 120; // Frames between obstacle spawns
float spawnTimer = 0;

// Game variables
float speed = 5;

// Camera variables
PVector camPos;

// Terrain variables
ArrayList<TerrainChunk> terrainChunks;
float chunkDepth = 1000; // Depth of each terrain chunk

// Texture variables
PImage groundTexture;
PImage playerTexture;
PImage obstacleTexture;

// Shader variables
PShader esotericShader;


void setup() {
  size(800, 600, P3D);
  smooth(8);

  // Initialize time variables
  time = 0;
  prevTime = millis() / 1000.0; // Convert milliseconds to seconds

  // Load textures
  groundTexture = loadImage("groundTexture.jpg");
  playerTexture = loadImage("playerTexture.jpg");
  obstacleTexture = loadImage("obstacleTexture.jpg");

  // Load shaders
  esotericShader = loadShader("esotericFrag.glsl", "vert.glsl");

  initPlayer();
  obstacles = new ArrayList<Box>();

  camPos = new PVector(0, -50, 50);

  // Initialize terrain
  terrainChunks = new ArrayList<TerrainChunk>();
  terrainChunks.add(new TerrainChunk(-chunkDepth));
  terrainChunks.add(new TerrainChunk(0));
  terrainChunks.add(new TerrainChunk(chunkDepth));
}
void draw() {
  // Calculate deltaTime
  float currentTime = millis() / 1000.0; // Current time in seconds
  float deltaTime = currentTime - prevTime;
  prevTime = currentTime;

  time += deltaTime;

  esotericShader.set("time", time);

  shader(esotericShader);

  background(50, 0, 80);

  updatePlayer();
  updateObstacles();
  updateTerrain();

  setCamera();

  // Display terrain
  for (TerrainChunk tc : terrainChunks) {
    tc.display();
  }

  displayPlayer();

  // Display obstacles
  for (Box o : obstacles) {
    o.display();
  }

  resetShader();
}
void initPlayer() {
  playerPos = new PVector(0, 0, -50);
  playerVel = new PVector(0, 0, 0);
}

void updatePlayer() {
  // Gravity effect
  playerVel.y += 0.5;
  playerPos.add(playerVel);

  // Get terrain height at player's position
  float terrainHeight = getTerrainHeightAt(playerPos.x, playerPos.z);

  // Ground collision detection
  if (playerPos.y > terrainHeight) {
    playerPos.y = terrainHeight;
    playerVel.y = 0;
    isJumping = false;
  }

  // Left and right movement
  if (keyPressed) {
    if (keyCode == LEFT) {
      playerPos.x -= 5;
    } else if (keyCode == RIGHT) {
      playerPos.x += 5;
    }
  }
}

void displayPlayer() {
  pushMatrix();
  translate(playerPos.x, playerPos.y - (25), playerPos.z);
  noStroke();

  // Begin drawing the shape with texture
  beginShape();
  texture(playerTexture);

  // Define the vertices with texture coordinates
  // Front face
  vertex(-10, -25, 5, 0, 0);
  vertex(10, -25, 5, playerTexture.width, 0);
  vertex(10, 25, 5, playerTexture.width, playerTexture.height);
  vertex(-10, 25, 5, 0, playerTexture.height);
  endShape();

  popMatrix();
}

void keyPressed() {
  if (key == ' ' && !isJumping) {
    playerVel.y = -10;
    isJumping = true;
  }
}

void updateObstacles() {
  spawnTimer += 1;
  if (spawnTimer >= spawnInterval) {
    spawnTimer = 0;
    spawnObstacle();
  }

  // Update obstacles and check for collisions
  for (int i = obstacles.size() - 1; i >= 0; i--) {
    Box o = obstacles.get(i);
    o.update();
    if (o.isOffScreen()) {
      obstacles.remove(i);
    } else if (o.collidesWithPlayer()) {
      println("Collision detected!");
      noLoop(); // Stop the game
    }
  }
}

void spawnObstacle() {
  float xPos = random(-200, 200);
  float zPos = playerPos.z - 1000;
  float yPos = getTerrainHeightAt(xPos, zPos);

  PVector pos = new PVector(xPos, yPos, zPos);
  PVector size = new PVector(30, random(50, 150), 30);
  obstacles.add(new Box(pos, size, obstacleTexture));
}

void setCamera() {
  // Smooth camera movement following the player
  camPos.x = lerp(camPos.x, playerPos.x, 0.1);
  camera(camPos.x, camPos.y, camPos.z,
         playerPos.x, playerPos.y, playerPos.z,
         0, 1, 0);
}

void updateTerrain() {
  // Generate new terrain chunks if necessary
  if (terrainChunks.get(terrainChunks.size() - 1).zPosition - playerPos.z < chunkDepth) {
    float newZPos = terrainChunks.get(terrainChunks.size() - 1).zPosition + chunkDepth;
    terrainChunks.add(new TerrainChunk(newZPos));
  }

  // Remove terrain chunks that are behind the player
  if (terrainChunks.get(0).zPosition - playerPos.z > chunkDepth * 2) {
    terrainChunks.remove(0);
  }
}

float getTerrainHeightAt(float x, float z) {
  for (TerrainChunk tc : terrainChunks) {
    if (z >= tc.zPosition && z < tc.zPosition + chunkDepth) {
      return tc.getHeightAt(x, z);
    }
  }
  return 0;
}

// TerrainChunk Class
class TerrainChunk {
  PShape terrain;
  float zPosition;

  TerrainChunk(float zPos) {
    zPosition = zPos;
    terrain = createTerrain();
  }

  PShape createTerrain() {
    int cols = 50;
    int rows = 50;
    float scale = 20;
    PShape terrainShape = createShape();
    terrainShape.beginShape(TRIANGLE_STRIP);
    terrainShape.noStroke();
    terrainShape.texture(groundTexture);

    for (int z = 0; z < rows - 1; z++) {
      for (int x = 0; x < cols; x++) {
        float noiseValue1 = noise((x) * 0.2, (z) * 0.2 + zPosition * 0.001);
        float noiseValue2 = noise((x) * 0.2, (z + 1) * 0.2 + zPosition * 0.001);

        float y1 = map(noiseValue1, 0, 1, -30, 30); // Adjusted range for variation
        float y2 = map(noiseValue2, 0, 1, -30, 30);

        terrainShape.vertex(x * scale - 500, y1, (z * scale) + zPosition, x * (groundTexture.width / cols), z * (groundTexture.height / rows));
        terrainShape.vertex(x * scale - 500, y2, ((z + 1) * scale) + zPosition, x * (groundTexture.width / cols), (z + 1) * (groundTexture.height / rows));
      }
    }

    terrainShape.endShape();
    return terrainShape;
  }

  void display() {
    shape(terrain);
  }

  float getHeightAt(float x, float z) {
    int cols = 50;
    int rows = 50;
    float scale = 20;
    float tx = (x + 500) / scale;
    float tz = (z - zPosition) / scale;

    if (tx < 0 || tx > cols - 1 || tz < 0 || tz > rows - 1) {
      return 0;
    }

    float noiseValue = noise(tx * 0.2, tz * 0.2 + zPosition * 0.001);
    return map(noiseValue, 0, 1, -30, 30); // Adjusted range to match terrain
  }

  boolean isOffScreen() {
    return zPosition > camPos.z + 500; // Adjust as needed
  }
}

// Box Class
class Box {
  PVector pos;
  PVector size;
  PImage texture;

  Box(PVector pos, PVector size, PImage tex) {
    this.pos = pos.copy();
    this.size = size.copy();
    this.texture = tex;
  }

  void update() {
    pos.z += speed;
  }

  void display() {
    pushMatrix();
    translate(pos.x, pos.y - (size.y / 2), pos.z);
    noStroke();

    beginShape();
    texture(texture);

    // Front face
    vertex(-size.x / 2, -size.y / 2, size.z / 2, 0, 0);
    vertex(size.x / 2, -size.y / 2, size.z / 2, texture.width, 0);
    vertex(size.x / 2, size.y / 2, size.z / 2, texture.width, texture.height);
    vertex(-size.x / 2, size.y / 2, size.z / 2, 0, texture.height);
    endShape();

    // Optionally add other faces for a fully textured box

    popMatrix();
  }

  boolean isOffScreen() {
    return pos.z > camPos.z + 50;
  }

  boolean collidesWithPlayer() {
    float dx = abs(pos.x - playerPos.x);
    float dy = abs(pos.y - playerPos.y);
    float dz = abs(pos.z - playerPos.z);
    if (dx < (size.x / 2 + 10) && dy < (size.y / 2 + 25) && dz < (size.z / 2 + 5)) {
      return true;
    }
    return false;
  }
}

// esotericFrag.glsl

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

uniform sampler2D tex0;
uniform float time;

varying vec4 vertColor;
varying vec2 vertTexCoord;

void main() {
  vec2 uv = vertTexCoord;

  // Apply a sine wave distortion effect
  uv.x += sin(uv.y * 10.0 + time) * 0.05;
  uv.y += cos(uv.x * 10.0 + time) * 0.05;

  vec4 color = texture2D(tex0, uv);

  // Invert colors for a surreal effect
  color.rgb = 1.0 - color.rgb;

  gl_FragColor = color * vertColor;
}

// frag.glsl

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

// Default uniform sampler provided by Processing
uniform sampler2D tex0;

// Varyings received from the vertex shader
varying vec4 vertColor;
varying vec2 vertTexCoord;

void main() {
  // Get the texture color
  vec4 color = texture2D(tex0, vertTexCoord);

  // Apply color distortion effects
  color.r = color.r * sin(gl_FragCoord.y * 0.05);
  color.g = color.g * cos(gl_FragCoord.x * 0.05);
  color.b = color.b * sin(gl_FragCoord.x * 0.05);

  // Combine with the vertex color
  gl_FragColor = color * vertColor;
}

// vert.glsl

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

// Default attributes provided by Processing
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

// Uniform matrices provided by Processing for transformations
uniform mat4 transformMatrix;
uniform mat4 projectionMatrix;
uniform mat4 modelviewMatrix;

// Varyings to pass data to the fragment shader
varying vec4 vertColor;
varying vec2 vertTexCoord;

void main() {
  // Transform the vertex position
  gl_Position = projectionMatrix * modelviewMatrix * position;

  // Pass the vertex color and texture coordinates to the fragment shader
  vertColor = color;
  vertTexCoord = texCoord;
}


add your own models that you know about in place of existing models and textures. make the terrain generate infintely. add strange particle effects.