/* 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_Questionable extends PApplet {

Camera camera = new Camera();
 public float[][] Sort2D(float[][] ToSort, int CompareValue)
{
  float[][] Sorted = new float[ToSort.length][ToSort[0].length];
  float[] RelevantValues = new float[ToSort.length];
  for (int i = 0; i < ToSort.length; i++)
  {
    RelevantValues[i] = ToSort[i][CompareValue];
  }
  RelevantValues = sort(RelevantValues);
  int j = 0;
  for (int i = 0; i < ToSort.length;)
  {
    if (RelevantValues[i] == ToSort[j][CompareValue])
    {
      Sorted[i] = ToSort[i];
      i++;
      j = 0;
    }
    else
    {
      j++;
    }
  }
  return Sorted;
}

 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);
  }
  float[][] AllXYPoints = shape.FindScreenPoints(); //Points 1-4, X-Y-scalevalue (identical to distance in this case)
  shape.DrawSelf(AllXYPoints);
}

 public float getScaleValue(PVector p)
{
  if (Con.ThreePointPerspective)
  {
    return sqrt(p.x * p.x + p.y * p.y + p.z * p.z) / Set.FocalLength;
  } else
  {
    return p.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;
}
 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;
  }
}
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;
    b = 0;
    pos.z = -1000;
  }
}
public 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 float[][] FindScreenPoints()
  {
    float[][] ScreenPoints = new float[4][3];
    for (int i = 0; i < 4; i++)
    {
      float scalevalue = getScaleValue(this.Points[i]);
      ScreenPoints[i][0] = this.Points[i].x / scalevalue;
      ScreenPoints[i][1] = this.Points[i].y / scalevalue;
      ScreenPoints[i][2] = scalevalue;
    }
    return Sort2D(ScreenPoints, 2);
  }
 
  public void DrawSelf(float[][] XYPos)
  {
    for (int i = 0; i < 4; i++)
    {
        strokeWeight(10); 
        point(XYPos[i][0], XYPos[i][1]);
        strokeWeight(1);
        for (int j = i; j < 4; j++)
        {
             line(XYPos[i][0], XYPos[i][1], XYPos[j][0], XYPos[j][1]);
        }
    }
    triangle(XYPos[0][0], XYPos[0][1], XYPos[2][0], XYPos[2][1], XYPos[3][0], XYPos[3][1]);
    triangle(XYPos[0][0], XYPos[0][1], XYPos[1][0], XYPos[1][1], XYPos[3][0], XYPos[3][1]); 
    triangle(XYPos[0][0], XYPos[0][1], XYPos[1][0], XYPos[1][1], XYPos[2][0], XYPos[2][1]);
  }
}
public static class Con
{
  public static ArrayList<Shape> GroundTruthWorld = new ArrayList<Shape>();
  public static int prevmouseX = 0;
  public static int prevmouseY = 0;
  public static boolean ThreePointPerspective = false;
}
public static class Set
{
  public static float FocalLength = 1000; // Distance in z to halve size
  public static int sizeX = 1800;
  public static int sizeY = 1080;
  public static int StepSize = 30;
}


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