/* autogenerated by Processing revision 1286 on 2022-12-05 */
import processing.core.*;
import processing.data.*;
import processing.event.*;
import processing.opengl.*;

import java.util.HashMap;
import java.util.ArrayList;
import java.io.File;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;

public class Renderer_Incredible extends PApplet {

public static class Con
{
  public static ArrayList<Shape> GroundTruthWorld = new ArrayList<Shape>();
  public static int prevmouseX = 0;
  public static int prevmouseY = 0;
}

public static class Set
{
  public static float FocalLength = 500; // Distance in z to halve size
  public static int sizeX = 1800;
  public static int sizeY = 1080;
  public static int StepSize = 30;
}
Camera camera = new Camera();
class Camera
{
  PVector pos = new PVector(0, 0, 0);
  float a = 0;
  float b = 0;
  float Sizex = Set.sizeX;
  float Sizey = Set.sizeY;
  public Camera()
  {
    Sizex = Set.sizeX;
    Sizey = Set.sizeY;
    a = 0;
    pos.z = -1000;
  }
}

class Shape
{
  PVector[] Points = new PVector[4];
  int ShapeColor;
  public Shape()
  {
    for (int i = 0; i < 4; i++)
    {
      this.Points[i] = new PVector(0, 0, 0);
    }
  }
  public Shape(PVector p1, PVector p2, PVector p3, PVector p4, int shapeColor)
  {
    Points[0] = p1.copy();
    Points[1] = p2.copy();
    Points[2] = p3.copy();
    Points[3] = p4.copy();
    ShapeColor = shapeColor;
  }
  public Shape copy()
  {
    return new Shape(Points[0].copy(), Points[1].copy(), Points[2].copy(), Points[3].copy(), ShapeColor);
  }
  public void MoveSelf(PVector Movement)
  {
    for (int i = 0; i < 4; i++)
    {
      this.Points[i].add(Movement);
    }
  }
}
 public void DrawScreen(Camera cam, ArrayList<Shape> world) // Call Appropriate Draw functions and make Necessary Transformations
{
  translate(Set.sizeX / 2, Set.sizeY / 2);
  background(200);
  for (Shape shape : world)
  {
    DrawShape(shape, cam);
  }
}
 public PVector Transform3D(PVector StartVector, float a, float b)
{
  float newX = -sin(a) * StartVector.z + cos(a) * StartVector.x; // Y-axis rotation
  float newY = StartVector.y;
  float newZ = cos(a) * StartVector.z + sin(a) * StartVector.x;
  float FinalX = newX;                                                           // X-axis rotation
  float FinalY = cos(b) * newY + sin(b) * newZ;
  float FinalZ = -sin(b) * newY + cos(b) * newZ;
  return new PVector(FinalX, FinalY, FinalZ);
}
 public void DrawShape(Shape shape, Camera cam)
{
  for (int i = 0; i < 4; i++) // ROTATE shapes
  {
    shape.Points[i].sub(cam.pos);
    shape.Points[i] = Transform3D(shape.Points[i], cam.a, cam.b);
  }
  for (int i = 0; i < 4; i++)
  {
    for (int j = i + 1; j < 4; j++)
    {
      if (shape.Points[i].z > 0 && shape.Points[j].z > 0)
      {
        stroke(shape.ShapeColor); // DRAW shapes
        strokeWeight(5);
        point(shape.Points[i].x / (shape.Points[i].z / Set.FocalLength), shape.Points[i].y / (shape.Points[i].z / Set.FocalLength));
        point(shape.Points[j].x / (shape.Points[j].z / Set.FocalLength), shape.Points[j].y / (shape.Points[j].z / Set.FocalLength));
        strokeWeight(1);
        line(
          shape.Points[i].x / (shape.Points[i].z / Set.FocalLength),
          shape.Points[i].y / (shape.Points[i].z / Set.FocalLength),
          shape.Points[j].x / (shape.Points[j].z / Set.FocalLength),
          shape.Points[j].y / (shape.Points[j].z / Set.FocalLength));
      }
    }
  }
}

 public ArrayList<Shape> CopyWorld(ArrayList<Shape> world) // Make World Copy so the transformations dont break anything
{
  ArrayList<Shape> newWorld = new ArrayList<Shape>();
  for (Shape oldShape : world)
  {
    Shape newShape = oldShape.copy();
    newWorld.add(newShape);
  }
  return newWorld;
}
 public void settings()
{
  size(Set.sizeX, Set.sizeY);
}
 public void setup() // Initialize Shapes in the World
{
  Con.GroundTruthWorld.add(new Shape(new PVector(-200, 0, 1), new PVector(-200, 0, 1), new PVector(200, 0, 1), new PVector(100, 0, 1), color(255, 0, 0)));
  Con.GroundTruthWorld.add(new Shape(new PVector(40, 40, 40), new PVector(100, 50, 30), new PVector(-30, 20, 60), new PVector(20, 100, 50), color(0, 0, 0)));
  Con.GroundTruthWorld.add(new Shape(new PVector(100, 100, 100), new PVector(200, 100, 100), new PVector(100, 200, 100), new PVector(100, 100, 200), color(0, 0, 255)));
  Con.GroundTruthWorld.add(new Shape(new PVector(100, 200, 200), new PVector(100, 100, 200), new PVector(200, 200, 200), new PVector(100, 200, 100), color(0, 255, 0)));
  Con.GroundTruthWorld.add(new Shape(new PVector(200, 200, 200), new PVector(200, 100, 100), new PVector(100, 100, 200), new PVector(200, 100, 200), color(0, 255, 255)));
  Con.GroundTruthWorld.add(new Shape(new PVector(200, 200, 200), new PVector(200, 100, 100), new PVector(100, 100, 200), new PVector(200, 100, 200), color(255, 0, 255)));
  Con.GroundTruthWorld.add(new Shape(new PVector(200, 200, 200), new PVector(200, 200, 100), new PVector(100, 200, 100), new PVector(200, 100, 100), color(255, 255, 255)));
  DrawScreen(camera, CopyWorld(Con.GroundTruthWorld));
  frameRate(10);
}
 public void draw() // Every Frame
{
  DrawScreen(camera, CopyWorld(Con.GroundTruthWorld));
  Con.GroundTruthWorld.get(0).MoveSelf(new PVector(0, 0, 0));
  Con.prevmouseX = mouseX;
  Con.prevmouseY = mouseY;
  pushMatrix();
  textSize(20);
  text("Controls: WASD for Movement, Shift/Space for down/up, Drag with mouse to look around", -Set.sizeX / 2 + 20, -Set.sizeY / 2 + 20);
  popMatrix();
}
 public void keyPressed() // User Input
{
  switch(key)
  {
  case 'w':
    camera.pos.add(Transform3D(new PVector(0, 0, Set.StepSize), -camera.a, 0));
    break;
  case 's':
    camera.pos.add(Transform3D(new PVector(0, 0, -Set.StepSize), -camera.a, 0));
    break;
  case 'a':
    camera.pos.add(Transform3D(new PVector(-Set.StepSize, 0, 0), -camera.a, 0));
    break;
  case 'd':
    camera.pos.add(Transform3D(new PVector(Set.StepSize, 0, 0), -camera.a, 0));
    break;
  case ' ':
    camera.pos.y -= Set.StepSize;
    break;
  //case 'i':
  //  camera.a += 0.01;
  //  break;
  //case 'o':
  //  camera.b += 0.01;
  //  break;
  }
  switch(keyCode)
  {
  case SHIFT:
    camera.pos.y += Set.StepSize;
    break;
  }
}
 public void mouseDragged()
{
  if (mousePressed)
  {
    camera.a += PApplet.parseFloat(mouseX - Con.prevmouseX) / 2000;
    camera.b -= PApplet.parseFloat(mouseY - Con.prevmouseY) / 2000;
  }
}


  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "Renderer_Incredible" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
