/*********************************************************************
| threed.c -- See threed.h for documentation.
| Written by Michael Main -- Nov 8, 1994
*/

#define FALSE 0
#define TRUE 1
#define MAXSHORT 32767
#include <stdlib.h>     // Permits use of exit()
#include <math.h>       // Permits use of sin() and cos()
#include <iostream.h>   // Permits use of cerr
#include <sys/time.h>   // provides struct timeval
#include <sys/types.h>  // provides fd_set definition
#include <stdio.h>      // provides getchar
#include <stddef.h>     // defines NULL
#include "priority.h"
#include "threed.h"
#include "Xturbo.h"

class Transformation {
  float Matrix[3][3];
public:
  Transformation(); // Initialize as identity
  void operator = (const Transformation& Other);
  void MakeIdentity();
  void MakeRotation(Axis AxisOfRotation, float Radians);
  void operator *= (const Transformation& Other);
  void operator *= (float Factor);
  void Transform(Cartesian Pt, Cartesian Result);
};

const Transformation IDENTITY;
const unsigned int MAXPOLYGONS = 1000;

float Distance = 100.0;
float ViewportWidth = 15;
float ViewportHeight = 15;
int ViewChanged = TRUE;
Transformation ViewMatrix;
Polygon* Gons[MAXPOLYGONS];
unsigned int ManyGons = 0;
int InDrawingMode = FALSE;
int Visual = 0;
  
static void Stop(char *ProcName, char * Message) {
  cerr << "ERROR in " << ProcName << ": " << Message << '.' << endl;
  exit(0);
}

Transformation::Transformation() {
  int I,J;
  for (I=X; I<=Z; I++) for (J=X; J<=Z; J++)
    if (I==J) Matrix[I][J] = 1.0;
    else      Matrix[I][J] = 0.0;
}

void Transformation::operator = (const Transformation& Other) {
  int I,J;
  for (I=X; I<=Z; I++) for (J=X; J<=Z; J++)
    Matrix[I][J] = Other.Matrix[I][J];
}

void Transformation::MakeRotation(Axis AxisOfRotation, float Radians) {
  Axis Next, Prev;
  int I,J;
  for (I=X; I<=Z; I++) for (J=X; J<=Z; J++)
    Matrix[I][J] = IDENTITY.Matrix[I][J];
  if (AxisOfRotation == Z) Next = X; else Next = Axis(AxisOfRotation+1);
  if (AxisOfRotation == X) Prev = Z; else Prev = Axis(AxisOfRotation-1);
  Matrix[Next][Next] = cos(Radians);
  Matrix[Prev][Prev] = Matrix[Next][Next];
  Matrix[Prev][Next] = sin(Radians);
  Matrix[Next][Prev] = -Matrix[Prev][Next];
}

void Transformation::operator *= (const Transformation& Other) {
  Axis I, J, K;
  float Sum;
  float Answer[3][3];
  for (I=X; I<=Z; I = Axis(I+1)) for (K=X; K<=Z; K = Axis(K+1)) {
    // Compute Answer.Matrix[I,K] as sum of product
    Sum = 0;
    for (J=X; J <=Z; J = Axis(J+1)) Sum += Matrix[I][J] * Other.Matrix[J][K];
    Answer[I][K] = Sum;
  }
  for (I=X; I<=Z; I = Axis(I+1)) for (J=X; J<=Z; J = Axis(J+1))
    Matrix[I][J] = Answer[I][J];
}

void Transformation::operator *= (float Factor) {
  Axis I, J;
  for (I=X; I<=Z; I = Axis(I+1)) for (J=X; J<=Z; J = Axis(J+1))
      Matrix[I][J] *= Factor;
}

void Transformation::Transform(Cartesian Pt, Cartesian Result) {
  Axis I, J;
  Cartesian Temp;
  for (I=X; I<=Z; I = Axis(I+1)) Temp[I] = Pt[I];
  float Sum;
  for (I=X; I<=Z; I = Axis(I+1)) {
    Sum = 0;
    for (J=X; J<=Z; J = Axis(J+1)) Sum += Temp[J] * Matrix[J][I];
    Result[I] = Sum;
  }
}

void Polygon::Revive(unsigned int RequestedCapacity = 4) {
  // Note: For now, every polygon has MAXVERTICES vertices
  Size = 0;
  FillColor = 0; // Fill with white, and black border
  IsHidden = FALSE;
  Origin[X] = 0;  Origin[Y] = 0; Origin[Z] = 0;
  if (ManyGons == MAXPOLYGONS) {
    MaxSize = 0;
    Location = 0;
  }
  else {
    MaxSize = MAXVERTICES;
    Location = ManyGons;
    Gons[ManyGons++] = this;
    Changed = TRUE;
  }
}

void Polygon::operator =(const Polygon& Other) {
  Axis J;
  unsigned int I;
  if (MaxSize < Other.Size) Stop("Polygon operator =", "Capacity too small.");
  Size = Other.Size;
  FillColor = Other.FillColor;
  IsHidden = Other.IsHidden;
  for (I=0; I<Size; I = Axis(I+1)) for (J=X; J<=Z; J = Axis(J+1))
    Vertices[I][J] = Other.Vertices[I][J];
  for (J=X; J<=Z; J = Axis(J+1))
    Origin[J] = Other.Origin[J];
  Changed = TRUE;
}

void Polygon::AddVertex(Cartesian Vertex) {
  if (Size == MaxSize)
    Stop("Polygon.AddVertex", "Too many vertices");
  Vertices[Size][X] = Vertex[X];
  Vertices[Size][Y] = Vertex[Y];
  Vertices[Size][Z] = Vertex[Z];
  Size++;
  Changed = TRUE;
}
void Polygon::MoveVertex(int VertexNumber, Cartesian Vertex) {
  if ((VertexNumber <= 0) || (VertexNumber > (int) Size))
    Stop("Polygon.MoveVertex", "Illegal VertexNumber");
  Vertices[VertexNumber][X] = Vertex[X];
  Vertices[VertexNumber][Y] = Vertex[Y];
  Vertices[VertexNumber][Z] = Vertex[Z];
  Changed = TRUE;
}

void Polygon::MoveVertexXYZ(int VertexNumber, float DataX, float DataY, float DataZ) {
  if ((VertexNumber <= 0) || (VertexNumber > (int) Size))
    Stop("Polygon.MoveVertexXYZ", "Illegal VertexNumber");
  Vertices[VertexNumber][X] = DataX;
  Vertices[VertexNumber][Y] = DataY;
  Vertices[VertexNumber][Z] = DataZ;
  Changed = TRUE;
}

void Polygon::AddVertexXYZ(float DataX, float DataY, float DataZ) {
  if (Size == MaxSize)
    Stop("Polygon.AddVertexXYZ", "Too many vertices");
  Vertices[Size][X] = DataX;
  Vertices[Size][Y] = DataY;
  Vertices[Size][Z] = DataZ;
  Size++;
  Changed = TRUE;
}

void Polygon::SetColor(int Color) {
  FillColor = Color;
  Changed = TRUE;
}

void Polygon::Remove() {
  Size = 0;
  MaxSize = 0;
  if (Location == 0) return;
  Gons[Location] = Gons[--ManyGons];
  Gons[Location]->Location = Location;
  Location = 0;
}

float Polygon::DistanceFromViewer() {
  Cartesian Trans;
  Axis I;
  if (Changed || ViewChanged) {
    if (Size > 0) {
      for (I=X; I<=Z; I = Axis(I+1)) Trans[I] = Origin[I] + Vertices[0][I];
      ViewMatrix.Transform(Trans, Trans);
    }
    else
      ViewMatrix.Transform(Origin, Trans);
    DistanceRecord = sqrt( (Trans[X]*Trans[X])
                          +(Trans[Y]+Distance)*(Trans[Y]+Distance)
                          +(Trans[Z]*Trans[Z]) );
  }
  return DistanceRecord;
}

static void Project
(Cartesian A, short& XScreen, short& YScreen, int& Behind) {
  float RealValue, Scale;
  // First transform the point A according to the Viewmatrix 
  ViewMatrix.Transform(A,A);
  // Now, take the transformed coordinates of A and figure out if they are
  //behind the viewer (i.e., the Y coordinate is < Viewer's Y)
  Behind = (A[Y] < (-Distance + 1));
  if (!Behind) {
    // Draw a line from the Viewer through A and find out where it
    // intersects the xz plane 
    Scale = Distance / (Distance+A[Y]);
    A[X] =  Scale * A[X];
    RealValue =  (getmaxx() / 2.0) * (1 + A[X]/ViewportWidth);
    if (RealValue > MAXSHORT) XScreen = MAXSHORT;
    else if (RealValue < -MAXSHORT) XScreen = -MAXSHORT;
    else if (RealValue > 0) XScreen = (int) (RealValue+0.5);
    else XScreen = (int) (RealValue-0.5);
    A[Z] =  Scale * A[Z];
    RealValue =  (getmaxy() / 2.0) * (1 - A[Z]/ViewportHeight);
    if (RealValue > MAXSHORT) YScreen = MAXSHORT;
    else if (RealValue < -MAXSHORT) YScreen = -MAXSHORT;
    else if (RealValue > 0) YScreen = (int) (RealValue+0.5);
    else YScreen = (int) (RealValue-0.5);
  }
}

void Polygon::Draw() {
  int I;
  Axis J;
  Cartesian A;
  if (IsHidden) return;
  if (ViewChanged || Changed) {
    for (I=0; I<(int)Size; I++) {
      for (J=X; J<=Z; J = Axis(J+1)) A[J] = Origin[J] + Vertices[I][J];
      Project(A, QuadPoints[2*I], QuadPoints[2*I+1], Behind);
      if (Behind) return;
    }
  }
  else if (Behind) return;
  setfill(FillColor);
  setcolor(!FillColor);
  fillpoly(Size, QuadPoints);
}

void Polygon::SetOrigin(Cartesian NewOrigin) {
  Origin[X] = NewOrigin[X];  
  Origin[Y] = NewOrigin[Y];  
  Origin[Z] = NewOrigin[Z];  
  Changed = TRUE;
}

void Polygon::SetOriginXYZ(float DataX, float DataY, float DataZ) {
  Origin[X] = DataX;
  Origin[Y] = DataY;
  Origin[Z] = DataZ;
  Changed = TRUE;
}

void Polygon::TranslateOrigin(float DeltaX, float DeltaY, float DeltaZ) {
  Origin[X] += DeltaX;
  Origin[Y] += DeltaY;
  Origin[Z] += DeltaZ;
  Changed = TRUE;
}

void Polygon::Rotate(Axis AxisOfRotation, float Amount) {
  Transformation M;
  int I;
  M.MakeRotation(AxisOfRotation, Amount);
  for (I=0; I<(int)Size; I++) M.Transform(Vertices[I], Vertices[I]);
  Changed = TRUE;
}

void SetView(float DistanceFromOrigin, float Width, float Height) {
  Distance = DistanceFromOrigin;
  ViewportWidth = Width;
  ViewportHeight = Height;
  ViewChanged = TRUE;
}

void ZoomView(float Amount) {
  ViewMatrix *= Amount;
  ViewChanged = TRUE;
}

void RollView(float Amount) {
  Transformation R;
  // Rotate points around the x axis by Amount radians.  This rotation
  // is from the positive y axis to positive z axis.
  R.MakeRotation(X, -Amount);
  ViewMatrix *= R;
  ViewChanged = TRUE;
}

void TiltView(float Amount) {
  Transformation R;
  // Rotate points around the y axis by Amount radians.  This rotation
  // is from the positive z axis to positive x axis. 
  R.MakeRotation(Y, Amount);
  ViewMatrix *= R;
  ViewChanged = TRUE;
}

void SpinView(float Amount) {
  Transformation R;
  // Rotate points around the z axis by Amount radians.  This rotation
  // is from the positive x axis to positive y axis. 
  R.MakeRotation(Z, Amount);
  ViewMatrix *= R;
  ViewChanged = TRUE;
}

void RefreshScreen() {
  PriorityQueue<unsigned int, float> Q(MAXPOLYGONS);
  unsigned int I;
  if (!InGraphics())
    Stop("RefreshScreen", "Not in graphics mode");
  for (I=0; I<ManyGons; I++)
    Q.Insert(I, Gons[I]->DistanceFromViewer());
  Visual = !Visual;
  setactivepage(Visual);
  clearviewport();
  // updateall();
  while (!Q.Empty()) {
    Q.GetFront(I);
    Gons[I]->Draw();
    Gons[I]->Changed = FALSE;
  }
  ViewChanged = FALSE;
  // updateall();
  setvisualpage(Visual);
  updateall();
}

void PutIntoGraphicsMode() {
  if (InDrawingMode) return;
  initgraphics();
  InDrawingMode = TRUE;
}

int InGraphics() {
  return InDrawingMode;
}

void ShutDownGraphics() {
  if (InDrawingMode) closegraph();
  InDrawingMode = FALSE;
}

static struct timeval ZeroTime = {0,0};
static fd_set JustInput;
static int TicksBeenCalled = 0;
static struct timeval LastTime;

float Ticks() {
  struct timeval NewTime;
  float Difference;
  gettimeofday(&NewTime, NULL);
  if (!TicksBeenCalled) {
    TicksBeenCalled = TRUE;
    Difference = 0;
  }
  else
    Difference = (NewTime.tv_sec - LastTime.tv_sec)
               + (NewTime.tv_usec - LastTime.tv_usec)/1000000.0;
  LastTime.tv_sec = NewTime.tv_sec;
  LastTime.tv_usec = NewTime.tv_usec;
  return Difference;
}

char ReadChar() {
  // Non-blocking read, which returns zero if there is no char available
  *((int *) &JustInput) = 0;
  FD_SET(0, &JustInput);
  if (select(2, (fd_set *) &JustInput, NULL, NULL, &ZeroTime))
    return getchar();
  else
    return 0;
}


