/***************************************************************************
                          EMath.h  -  description
                             -------------------
    begin                : Sat Jan 29 2000
    copyright            : (C) 2000 by Henrik Enqvist
    email                : henqvist@excite.com
***************************************************************************/

#ifndef EMATH_H
#define EMATH_H

#define EM_ZERO(a) ( (a) > -0.000001 && (a) < 0.000001 )

#define EMathApplyMatrix(mtx, vtxIn, vtxOut) \
vtxOut.x = vtxIn.x * mtx.v[0][0] + vtxIn.y * mtx.v[0][1] + vtxIn.z * mtx.v[0][2] + mtx.t[0]; \
vtxOut.y = vtxIn.x * mtx.v[1][0] + vtxIn.y * mtx.v[1][1] + vtxIn.z * mtx.v[1][2] + mtx.t[1]; \
vtxOut.z = vtxIn.x * mtx.v[2][0] + vtxIn.y * mtx.v[2][1] + vtxIn.z * mtx.v[2][2] + mtx.t[2];

#define EMathApplyMatrixRot(mtx, vtxIn, vtxOut) \
vtxOut.x = vtxIn.x * mtx.v[0][0] + vtxIn.y * mtx.v[0][1] + vtxIn.z * mtx.v[0][2]; \
vtxOut.y = vtxIn.x * mtx.v[1][0] + vtxIn.y * mtx.v[1][1] + vtxIn.z * mtx.v[1][2]; \
vtxOut.z = vtxIn.x * mtx.v[2][0] + vtxIn.y * mtx.v[2][1] + vtxIn.z * mtx.v[2][2];

#define EM_PI   		3.1415926f
#define EM_PI_DIV_2 1.5707963f
#define EM_PI_2	    6.2831853f
#define EM_SQRT_2		1.4142135f
#define EM_SIN_45		0.7071069f
#define EM_SIN_60   0.8660254f
#define EM_SQRT_3 	1.73205f

#define EM_MAX(a, b) ( (a) > (b) ? (a) : (b) )

#define EM_MIN(a, b) ( (a) < (b) ? (a) : (b) )

#define EM_ABS(a) ( (a) > 0 ? (a) : -(a) )

#if EM_USE_FAST_FLOAT2INT

typedef struct {
  union {
    int i;
    float f;
  };
} IntOrFloat;

extern IntOrFloat gBias;

#define ConvFloat2Int(a) \
a.f += gBias.f; \
a.i -= gBias.i;

#endif // EM_USE_FAST_FLOAT2INT


typedef struct {
  float v[3][3];
  float t[3];
} Matrix;

typedef struct {
  float x, y, z;
} Vertex3D;

typedef struct {
  float u, v;
} TexCoord;

typedef struct {
  float r, g, b, a;
} Color;

typedef struct {
  float x, y, z, w;
} Quaternion;

/** A class that collects the math functions. */
class EMath {
 public:
  EMath();
  ~EMath();

  inline static void applyMatrix(const Matrix & mtx, const Vertex3D & vtxIn, 
                                 Vertex3D & vtxOut) {
    vtxOut.x = vtxIn.x * mtx.v[0][0] + vtxIn.y * mtx.v[0][1] 
      + vtxIn.z * mtx.v[0][2] + mtx.t[0];
    vtxOut.y = vtxIn.x * mtx.v[1][0] + vtxIn.y * mtx.v[1][1] 
      + vtxIn.z * mtx.v[1][2] + mtx.t[1];
    vtxOut.z = vtxIn.x * mtx.v[2][0] + vtxIn.y * mtx.v[2][1] 
      + vtxIn.z * mtx.v[2][2] + mtx.t[2];
  };
  inline static void applyMatrixRot(const Matrix & mtx, const Vertex3D & vtxIn, 
                             Vertex3D & vtxOut) {
    vtxOut.x = vtxIn.x * mtx.v[0][0] + vtxIn.y * mtx.v[0][1] + vtxIn.z * mtx.v[0][2];
    vtxOut.y = vtxIn.x * mtx.v[1][0] + vtxIn.y * mtx.v[1][1] + vtxIn.z * mtx.v[1][2];
    vtxOut.z = vtxIn.x * mtx.v[2][0] + vtxIn.y * mtx.v[2][1] + vtxIn.z * mtx.v[2][2];
  };
  static void applyMatrixTrans(const Matrix & mtx, const Vertex3D & vtxIn, Vertex3D & vtxOut);
  static void crossProduct(const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxOut);
  // static void getCameraMatrix(Matrix & mtx, const Vertex3D & trans, Vertex3D & front, 
  // Vertex3D & up, float fov, float aspect);
  static void getTransformationMatrix(Matrix & mtx, const Vertex3D & vtxT, 
                                      const Vertex3D & vtxR, const Vertex3D & vtxS);
  static void getTransformationMatrix(Matrix & mtx, const Vertex3D & vtxT, 
                                      const Quaternion & qRot, const Vertex3D & vtxS);
  static void inverse(const Matrix & mtx, Matrix & inv);
  static void matrixMulti(const Matrix & mtxA, const Matrix & mtxB, Matrix & mtxOut);


  static float dotProduct(const Vertex3D & vtxA, const Vertex3D & vtxB);
  static float emAcos(float f);
  static float emAtan(float f);
  static float emCos(float f);
  static float emRand();
  static float emSin(float f);
  static float emSqrt(float f);
  static float emTan(float f);
  static float emPow(float x, float y);

  inline static void add(const Vertex3D & vtxA, Vertex3D & vtxOut) {
    vtxOut.x += vtxA.x;
    vtxOut.y += vtxA.y;
    vtxOut.z += vtxA.z;
  };
  inline static void add(const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxOut) {
    vtxOut.x = vtxA.x + vtxB.x;
    vtxOut.y = vtxA.y + vtxB.y;
    vtxOut.z = vtxA.z + vtxB.z;
  };
  inline static void subst(const Vertex3D & vtxA, Vertex3D & vtxOut) {
    vtxOut.x -= vtxA.x;
    vtxOut.y -= vtxA.y;
    vtxOut.z -= vtxA.z;
  };
  inline static void subst(const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxOut) {
    vtxOut.x = vtxA.x - vtxB.x;
    vtxOut.y = vtxA.y - vtxB.y;
    vtxOut.z = vtxA.z - vtxB.z;
  };
  inline static void scaleVertex(Vertex3D & vtx, float s) {
    vtx.x *= s; 
    vtx.y *= s;
    vtx.z *= s;
  };
  static void normalizeVector(Vertex3D & vtx);
  static float polygonZNormal(const Vertex3D & edgeA, const Vertex3D & edgeB, 
			      const Vertex3D & edgeC);

  void planeLineIntersection(const Vertex3D & nrml, float dist, 
			     const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxDiff);

  /** The projection of vxtA onto vxtB. vtxA and vxtOut is ( in this case )
   * allowed to be the same vector. */
  static float projection(const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxOut);
  /** Get the perpendicular component of the projection */
  static float perpendicular(const Vertex3D & vtxA, const Vertex3D & vtxB, Vertex3D & vtxOut);
  /** Counts the "reflection" vector of vtxIn onto a plane with the normal vtxWall.
   * vtxIn and vtxOut is ( in this case ) allowed to be the same vector. */
  static void reflection(const Vertex3D & vtxIn, const Vertex3D & vtxWall, 
			 Vertex3D & vtxOut, bool bBehind = false);
  /** Counts the "reflection" vector of vtxIn onto a plane with the normal vtxWall.
   * vtxIn and vtxOut is ( in this case ) allowed to be the same vector. The damping
   * factor is 1 for normal, 0 for max damping and over 1 for extra bounce. The wall
   * factor is as if the wall would give a little push, 0 for normal. Scale is the
   * length of the vector, 1 for normal. */
  static void reflectionDamp(const Vertex3D & vtxIn, const Vertex3D & vtxWall, 
			     Vertex3D & vtxOut, float damp, float extra, 
			     float scale, bool bBehind = false);
  static void scaleVector(Vertex3D & vtx, float sc);
  inline static float vectorLength(const Vertex3D & vtx) {
    return EMath::emSqrt(vtx.x * vtx.x + vtx.y * vtx.y + vtx.z * vtx.z);
  };
  /** The vector length but without the square root */
  inline static float vectorLengthSqr(const Vertex3D & vtx) {
    return (vtx.x * vtx.x + vtx.y * vtx.y + vtx.z * vtx.z);
  };

  static float volume(const Vertex3D & vtxA, const Vertex3D & vtxB, const Vertex3D & vtxC);
  static void rotationArc(const Vertex3D & vtxA, const Vertex3D & vtxB, Quaternion & vtxOut);
  static void quaternionMulti(const Quaternion & qA, const Quaternion & qB, Quaternion & qOut);
  /** Quadratic interpolation. f0 is at t=0, f1 is at t=1 and f2 at t=2. You
   * may give any t for recieving values. */
  static float quadratic(float f0, float f1, float f2, float t);
  static float cubic(float f0, float f1, float f2, float f3, float t);

  static const Matrix identityMatrix;
};

#endif // EMATH_H
