#define	LIBQDISPLAY_CORE
#include "../include/libqdisplay.h"

#define DOT_VEC_DBL(a1,a2,a3,b1,b2,b3) ((float)(a1*b1 + a2*b2 + a3*b3))

float clipScaleX, clipScaleY, incSpeed, maxSpeed, decSpeed;
vec3_t cameraLocation, currentSpeed;
bool changedLocation, changedAngles, changedMatrix;
angvec cameraAngles;
struct extplane planes[4];
float xCenter, yCenter;

static float mainMatrix[3][3], viewMatrix[3][3], translate[3];
static int clipXLow, clipXHigh, clipYLow, clipYHigh;
static float projScaleX, projScaleY;
static float nearClip = 0.01, nearCode = 16.0;

void set_clip_values(int width, int height)
{
  clipXLow = -32768;
  clipXHigh = (width << 16) - 32768;
  clipYLow = -32768;
  clipYHigh = (height << 16) - 32768;
  projScaleX = (width >> 1);
  projScaleY = (projScaleX * height) / (width * 0.75);
  xCenter = -0.5 + (width >> 1);
  yCenter = -0.5 + (height >> 1);
  clipScaleX = 1.0 / (width >> 1);
  clipScaleY = 1.0 / (height >> 1);
  incSpeed = width * (2 / 91.5);
  maxSpeed = width * (2 / 22.5);
  decSpeed = incSpeed * (2 / 1.5);
}

void set_view_info(void)
{
  /* compute rotation matrix */
  if (changedAngles) {
    /*
     * the main matrix is the representation of the three vectors
     * that makes the lokal coordinate-system, called local-axis
     */
    if (!cameraAngles.ty) {
      mainMatrix[0][0] =  cosTable[cameraAngles.tz];
      mainMatrix[0][1] = -sinTable[cameraAngles.tz];
      mainMatrix[0][2] =  0;
      mainMatrix[1][0] =  sinTable[cameraAngles.tz] *  sinTable[cameraAngles.tx];
      mainMatrix[1][1] =  cosTable[cameraAngles.tz] *  sinTable[cameraAngles.tx];
      mainMatrix[1][2] = 			       cosTable[cameraAngles.tx];
      mainMatrix[2][0] =  sinTable[cameraAngles.tz] *  cosTable[cameraAngles.tx];
      mainMatrix[2][1] =  cosTable[cameraAngles.tz] *  cosTable[cameraAngles.tx];
      mainMatrix[2][2] = 			      -sinTable[cameraAngles.tx];

      changedAngles = FALSE;
    }
    else {
      mainMatrix[0][0] =  cosTable[cameraAngles.tz] * 				    cosTable[cameraAngles.ty] + sinTable[cameraAngles.tz] * sinTable[cameraAngles.tx] * sinTable[cameraAngles.ty];
      mainMatrix[0][1] = -sinTable[cameraAngles.tz] * 				    cosTable[cameraAngles.ty] + cosTable[cameraAngles.tz] * sinTable[cameraAngles.tx] * sinTable[cameraAngles.ty];
      mainMatrix[0][2] = 			       cosTable[cameraAngles.tx] *  sinTable[cameraAngles.ty];
      mainMatrix[1][0] =  cosTable[cameraAngles.tz] * 				   -sinTable[cameraAngles.ty] + sinTable[cameraAngles.tz] * sinTable[cameraAngles.tx] * cosTable[cameraAngles.ty];
      mainMatrix[1][1] = -sinTable[cameraAngles.tz] * 				   -sinTable[cameraAngles.ty] + cosTable[cameraAngles.tz] * sinTable[cameraAngles.tx] * cosTable[cameraAngles.ty];
      mainMatrix[1][2] = 			       cosTable[cameraAngles.tx] *  cosTable[cameraAngles.ty];
      mainMatrix[2][0] =  sinTable[cameraAngles.tz] *  cosTable[cameraAngles.tx];
      mainMatrix[2][1] =  cosTable[cameraAngles.tz] *  cosTable[cameraAngles.tx];
      mainMatrix[2][2] = 			      -sinTable[cameraAngles.tx];

      if ((cameraAngles.ty > 1) && (cameraAngles.ty <= 45))
	cameraAngles.ty -= ((cameraAngles.ty - 0) / 6) + 1;
      else if ((cameraAngles.ty >= 315) && (cameraAngles.ty < 359))
	cameraAngles.ty += ((360 - cameraAngles.ty) / 6) + 1;
      else
	cameraAngles.ty = 0;
    }
    memcpy(viewMatrix, mainMatrix, sizeof(viewMatrix));
    /*
     * the view-matrix is the perspective correction of the main
     * matrix in advance of the screen-ratio
     */
    viewMatrix[0][0] *= projScaleX;
    viewMatrix[0][1] *= projScaleX;
    viewMatrix[0][2] *= projScaleX;
    viewMatrix[1][0] *= projScaleY;
    viewMatrix[1][1] *= projScaleY;
    viewMatrix[1][2] *= projScaleY;
    /*viewMatrix[2][0] *= projScaleZ; */
    /*viewMatrix[2][1] *= projScaleZ; */
    /*viewMatrix[2][2] *= projScaleZ; */

    changedMatrix = TRUE;
  }

  if (changedLocation) {
    /*
     * so (1,0,0) in camera space maps to viewMatrix[0] in world space.
     * thus we multiply on the right by a worldspace vector to transform
     * it to camera space.
     * now, to account for translation, we just subtract the camera
     * Center before multiplying
     */
    translate[0] = cameraLocation[0];
    translate[1] = cameraLocation[1];
    translate[2] = cameraLocation[2];

    if ((currentSpeed[0] != 0) || (currentSpeed[2] != 0)) {
      cameraLocation[0] += -mainMatrix[0][1] * currentSpeed[0];
      cameraLocation[1] +=  mainMatrix[0][0] * currentSpeed[0];
      cameraLocation[2] +=  mainMatrix[1][2] * currentSpeed[2];

      if (currentSpeed[0] > decSpeed)
	currentSpeed[0] -= decSpeed;
      else if (currentSpeed[0] < -decSpeed)
	currentSpeed[0] += decSpeed;
      else
	currentSpeed[0] = 0.0;

      if (currentSpeed[2] > decSpeed)
	currentSpeed[2] -= decSpeed;
      else if (currentSpeed[2] < -decSpeed)
	currentSpeed[2] += decSpeed;
      else
	currentSpeed[2] = 0.0;
    }
    else
      changedLocation = FALSE;					/* release only if we really stop */
  }
}

float dist2_from_viewer(vec_t *in)
{
  vec3_t temp;

  VectorSubtract(in, cameraLocation, temp);

  return VectorDist(temp);
}

#if 0								/* this works if IEEE and, sizeof(float) == sizeof(int) */
#define FLOAT_POSITIVE(x)   (*((int *)(&x)) >= 0)
#else
#define FLOAT_POSITIVE(x)   ((x) >= 0)
#endif

/*
 * the view-frustum are four planes that shoot from the camera-point
 * in a direction that is the visible area
 * everything between the four planes (the frustum) is visible
 * this is called frustum-culling
 *
 * we form the four planes with four vectors (-1,0,1), (1,0,1), (0,1,1), (0,-1,1)
 * with the cameras position as origin
 * we use the hesse-normal-form
 *
 * in screen-space the z-coordinate is tht one that goes into the monitor
 * y- and x-coordinate are the same as the screens coordinate, (0,0,0) is exactly
 * in the middle of and on the screen
 */
void compute_view_frustrum(void)
{
  if (changedMatrix) {
    /*
     * why are the normals not normalized? (is the mainMatrix normalized?) the length are sqrt(2) instead of 1
     * what means the dists?
     * what is the direction of the normals (into the screen-space or out of?)?
     */
    planes[0].positive[0] = FLOAT_POSITIVE(
	planes[0].normal[0] = -mainMatrix[0][0] 		+ mainMatrix[2][0]);
    planes[0].positive[1] = FLOAT_POSITIVE(
	planes[0].normal[1] = -mainMatrix[0][1] 		+ mainMatrix[2][1]);
    planes[0].positive[2] = FLOAT_POSITIVE(
	planes[0].normal[2] = -mainMatrix[0][2] 		+ mainMatrix[2][2]);
    planes[1].positive[0] = FLOAT_POSITIVE(
	planes[1].normal[0] =  mainMatrix[0][0] 		+ mainMatrix[2][0]);
    planes[1].positive[1] = FLOAT_POSITIVE(
	planes[1].normal[1] =  mainMatrix[0][1] 		+ mainMatrix[2][1]);
    planes[1].positive[2] = FLOAT_POSITIVE(
	planes[1].normal[2] =  mainMatrix[0][2] 		+ mainMatrix[2][2]);
    planes[2].positive[0] = FLOAT_POSITIVE(
	planes[2].normal[0] = 		       mainMatrix[1][0] + mainMatrix[2][0]);
    planes[2].positive[1] = FLOAT_POSITIVE(
	planes[2].normal[1] = 		       mainMatrix[1][1] + mainMatrix[2][1]);
    planes[2].positive[2] = FLOAT_POSITIVE(
	planes[2].normal[2] = 		       mainMatrix[1][2] + mainMatrix[2][2]);
    planes[3].positive[0] = FLOAT_POSITIVE(
	planes[3].normal[0] = 		      -mainMatrix[1][0] + mainMatrix[2][0]);
    planes[3].positive[1] = FLOAT_POSITIVE(
	planes[3].normal[1] = 		      -mainMatrix[1][1] + mainMatrix[2][1]);
    planes[3].positive[2] = FLOAT_POSITIVE(
	planes[3].normal[2] = 		      -mainMatrix[1][2] + mainMatrix[2][2]);
  }

  if (changedMatrix || changedLocation) {
    /*
     * we move the planes to the cameras position, up to now the distance to
     * the origin is 0 so 
     *  DotProduct(frustumNormal, point) + frustumDistance
     * gives
     *  DotProduct(frustumNormal, point)
     *
     * why do we move it to cameras position?
     * why is the dist not -DotProduct(frustumNormal, point) as it should be?
     * is this correct??? I think the length of the cameras vector must
     * be the distance for all planes[?].dist, and for all equal
     *  VectorLength(cameraLocation)
     */
    planes[0].dist = DotProduct(planes[0].normal, cameraLocation);
    planes[1].dist = DotProduct(planes[1].normal, cameraLocation);
    planes[2].dist = DotProduct(planes[2].normal, cameraLocation);
    planes[3].dist = DotProduct(planes[3].normal, cameraLocation);
  }

  changedMatrix = FALSE;
}

/*
 * we transform the world-axis-vector to the local-axis-vector
 * without take attention to the camera-position
 */
void transform_vector(vec_t *out, vec_t *in)
{
  out[0] = DotProduct(viewMatrix[0], in);
  out[1] = DotProduct(viewMatrix[1], in);
  out[2] = DotProduct(viewMatrix[2], in);
}

/*
 * we transform the world-axis-vector to the local-axis-vector
 * with previous camera-position correction
 */
void transform_point_raw(vec_t *out, vec_t *in)
{
  vec3_t temp;

  VectorSubtract(in, translate, temp);
  out[0] = DotProduct(viewMatrix[0], temp);
  out[1] = DotProduct(viewMatrix[1], temp);
  out[2] = DotProduct(viewMatrix[2], temp);
}

static void project_point(point_3d * p)
{
  if (p->p[2] >= nearClip) {
    double div = 1.0 / p->p[2];

    p->sx = FLOAT_TO_FIX( p->p[0] * div + xCenter);
    p->sy = FLOAT_TO_FIX(-p->p[1] * div + yCenter);
  }
}

static void code_point(point_3d * p)
{
  if (p->p[2] >= nearCode) {
    /* if point is far enough away, code in 2d from fixedpoint (faster) */
    if (p->sx < clipXLow)
      p->ccodes = CC_OFF_LEFT;
    else if (p->sx > clipXHigh)
      p->ccodes = CC_OFF_RIGHT;
    else
      p->ccodes = 0;
    if (p->sy < clipYLow)
      p->ccodes |= CC_OFF_TOP;
    else if (p->sy > clipYHigh)
      p->ccodes |= CC_OFF_BOT;
  }
  else {
    p->ccodes = (p->p[2] > 0) ? 0 : CC_BEHIND;
    if (p->p[0] * clipScaleX < -p->p[2])
      p->ccodes |= CC_OFF_LEFT;
    if (p->p[0] * clipScaleX >  p->p[2])
      p->ccodes |= CC_OFF_RIGHT;
    if (p->p[1] * clipScaleY >  p->p[2])
      p->ccodes |= CC_OFF_TOP;
    if (p->p[1] * clipScaleY < -p->p[2])
      p->ccodes |= CC_OFF_BOT;
  }
}

void transform_point(point_3d * p, vec_t *v)
{
  transform_point_raw(p->p, v);
  project_point(p);
  code_point(p);
}

void transform_rotated_point(point_3d * p)
{
  project_point(p);
  code_point(p);
}
