/******************************************************************************
  plush.h
  PLUSH 3D VERSION 2.0  MAIN HEADER
  Copyright (c) 1996-2000 Justin Frankel
  Copyright (c) 1998-2000 Nullsoft, Inc.
  Copyright (c) 2008 Cockos Incorporated

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.


******************************************************************************/

#ifndef _PLUSH_H_
#define _PLUSH_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "../lice/lice.h" // using LICE for images
#include "../ptrlist.h"
#include "../wdltypes.h"

typedef float pl_ZBuffer;              /* z-buffer type (must be float) */
typedef double pl_Float;               /* General floating point */
typedef float pl_IEEEFloat32;          /* IEEE 32 bit floating point */ 
typedef signed int pl_sInt32;          /* signed 32 bit integer */
typedef unsigned int pl_uInt32;        /* unsigned 32 bit integer */
typedef signed short int pl_sInt16;    /* signed 16 bit integer */
typedef unsigned short int pl_uInt16;  /* unsigned 16 bit integer */
typedef signed int pl_sInt;            /* signed optimal integer */
typedef unsigned int pl_uInt;          /* unsigned optimal integer */
typedef bool pl_Bool;                  /* boolean */
typedef unsigned char pl_uChar;        /* unsigned 8 bit integer */
typedef signed char pl_sChar;          /* signed 8 bit integer */



/* pi! */
#define PL_PI 3.141592653589793238

/* Utility min() and max() functions */
#define plMin(x,y) (( ( x ) > ( y ) ? ( y ) : ( x )))
#define plMax(x,y) (( ( x ) < ( y ) ? ( y ) : ( x )))



/*
** Light modes. Used with plLight.Type or plLightSet().
** Note that PL_LIGHT_POINT_ANGLE assumes no falloff and uses the angle between
** the light and the point, PL_LIGHT_POINT_DISTANCE has falloff with proportion
** to distance**2 (see plLightSet() for setting it), PL_LIGHT_POINT does both.
*/
#define PL_LIGHT_NONE (0x0)
#define PL_LIGHT_VECTOR (0x1)
#define PL_LIGHT_POINT (0x2|0x4)
#define PL_LIGHT_POINT_DISTANCE (0x2)
#define PL_LIGHT_POINT_ANGLE (0x4)


#define PLUSH_MAX_MAPCOORDS 3 // 2 + envmap slot


class pl_Mat
{
public:
  pl_Mat()
  {
    memset(Ambient,0,sizeof(Ambient));
    Diffuse[0]=Diffuse[1]=Diffuse[2]=1.0;
    SolidOpacity=1.0;
    SolidCombineMode= LICE_BLIT_MODE_COPY;
    
    Texture=NULL;
    TexCombineMode = LICE_BLIT_MODE_ADD; //LICE_BLIT_USE_SOURCE_ALPHA?
    TexOpacity=1.0;
    TexScaling[0]=TexScaling[1]=1.0;
    TexMapIdx=0;

    Texture2=NULL;
    Tex2CombineMode = LICE_BLIT_MODE_ADD;
    Tex2Opacity=1.0;
    Tex2Scaling[0]=Tex2Scaling[1]=1.0;
    Tex2MapIdx=-1;

    FadeDist=0.0;
    Smoothing=Lightable=true;
    zBufferable=1;
    PerspectiveCorrect=16;
    BackfaceCull=true;
    BackfaceIllumination=0.0;

    cachedTexture=cachedTexture2=0;
    cachesInvalid=true;
  }

  ~pl_Mat()
  {
    delete cachedTexture;
    delete cachedTexture2;
  }


  pl_Bool Smoothing; // smoothing of lighting
  pl_Bool Lightable; // affected by lights
  pl_uChar zBufferable;         /* Can this material be Z-buffered? 2=yes, but does not update Z buffer */
  pl_uChar PerspectiveCorrect; /* Correct texture perspective every n pixels */

  pl_Float FadeDist WDL_FIXALIGN;            /* For distance fading, distance at which intensity is 0. set to 0.0 for no distance shading */

  pl_Bool BackfaceCull;               /* Are backfacing polys drawn? */
  pl_Float BackfaceIllumination WDL_FIXALIGN;       /* Illuminated by lights behind them, and by how much of a factor? */ 

  // colors
  pl_Float Ambient[3];          /* RGB of surface (0-1 is a good range) */
  pl_Float Diffuse[3];          /* RGB of diffuse (0-1 is a good range) */
  pl_Float SolidOpacity;
  int SolidCombineMode;           /* LICE combine mode for first pass (color), default should be replace (or add-black for transparent) */

  // textures
  LICE_IBitmap *Texture;         /* Texture map (not owned by Material but a reference)*/
  pl_Float TexScaling[2] WDL_FIXALIGN;         /* Texture map scaling */
  pl_Float TexOpacity;
  int TexCombineMode;           /* Texture combine mode (generally should be additive) */
  int TexMapIdx; // -1 for env

  LICE_IBitmap *Texture2;     
  pl_Float Tex2Scaling[2] WDL_FIXALIGN;         
  pl_Float Tex2Opacity;
  int Tex2CombineMode;             
  int Tex2MapIdx; // -1 for env


  void InvalidateTextureCaches() { cachesInvalid=true; } // call this if you change Texture or Texture2 after rendering

private:
  bool cachesInvalid;
  LICE_IBitmap *cachedTexture,*cachedTexture2; // these may need to be LICE_GL_MemBitmaps etc

} WDL_FIXALIGN;


struct pl_Vertex {
  pl_Float x, y, z;              /* Vertex coordinate (objectspace) */
  pl_Float nx, ny, nz;           /* Unit vertex normal (objectspace) */
  
  pl_Float xformedx, xformedy, xformedz;   /* Transformed vertex  coordinate (cameraspace) */
  pl_Float xformednx, xformedny, xformednz;  /* Transformed unit vertex normal  (cameraspace) */
};

struct pl_Face {
  pl_Mat *Material;            /* Material of triangle */
  int VertexIndices[3];      /* Vertices of triangle */

  pl_Float nx WDL_FIXALIGN;
  pl_Float ny;
  pl_Float nz;         /* Normal of triangle (object space) */

  pl_Float MappingU[PLUSH_MAX_MAPCOORDS][3], MappingV[PLUSH_MAX_MAPCOORDS][3];  /* Texture mapping coordinates */ 

  pl_Float sLighting[3];          /* Face static lighting. Should usually be 0.0 */
  pl_Float vsLighting[3][3];      /* Vertex static lighting. Should usually be 0.0 */


  // calculated:
  pl_Float Shades[3][3];          /* colors (first 3 used for flat, all for Gouraud) */
  pl_Float Scrx[3], Scry[3];  /* Projected screen coordinates */
  pl_Float Scrz[3];            /* Projected 1/Z coordinates */

};


class pl_Obj {
public:
  pl_Obj(int nv=0, int nf=0) 
  {
    if (nv) memset(Vertices.Resize(nv),0,nv*sizeof(pl_Vertex));
    if (nf) memset(Faces.Resize(nf),0,nf*sizeof(pl_Face));

    GenMatrix=true;
    Xp=Yp=Zp=Xa=Ya=Za=0.0;
  }
  ~pl_Obj() { Children.Empty(true); }

  pl_Obj *Clone();
  void Scale(pl_Float sc);
  void Stretch(pl_Float x, pl_Float y, pl_Float z); // scales but preserves normals
  void Translate(pl_Float x, pl_Float y, pl_Float z);
  void FlipNormals();

  void SetMaterial(pl_Mat *m, pl_Bool recurse=true);
  void CalculateNormals();


  pl_Float Xp, Yp, Zp, Xa, Ya, Za;    /* Position and rotation of object:
                                         Note: rotations are around X then Y then Z. Measured in degrees */
  pl_Float Matrix[16];                /* Transformation matrix */
  pl_Float RotMatrix[16];             /* Rotation-only, for normals */

  WDL_TypedBuf<pl_Vertex> Vertices;
  WDL_TypedBuf<pl_Face> Faces;
  WDL_PtrList<pl_Obj> Children;

  pl_Bool GenMatrix;                  /* Generate Matrix from X-Z* if set */
};


class pl_Spline {
public:
  pl_Spline() { cont=1.0; bias=0.3; tens=0.3; keyWidth=1; }
  ~pl_Spline () { }
  void GetPoint(pl_Float frame, pl_Float *out);
  WDL_TypedBuf<pl_Float> keys;              /* Key data, keyWidth*numKeys */
  pl_sInt keyWidth;            /* Number of floats per key */

  pl_Float cont WDL_FIXALIGN;               /* Continuity. Should be -1.0 -> 1.0 */
  pl_Float bias;               /* Bias. -1.0 -> 1.0 */
  pl_Float tens;               /* Tension. -1.0 -> 1.0 */
};


class pl_Light {
public:
  pl_Light() { Type = PL_LIGHT_VECTOR; Xp=Yp=0.0; Zp=1.0; Intensity[0]=Intensity[1]=Intensity[2]=1.0; }
  ~pl_Light() { }

/*
  Set() sets up a light:
    mode: the mode of the light (PL_LIGHT_*)
    x,y,z: either the position of the light (PL_LIGHT_POINT*) or the angle
           in degrees of the light (PL_LIGHT_VECTOR)
    intensity: the intensity of the light (0.0-1.0)
    halfDist: the distance at which PL_LIGHT_POINT_DISTANCE is 1/2 intensity
*/
  void Set(pl_uChar mode, pl_Float x, pl_Float y, pl_Float z, pl_Float intensity_r, pl_Float intensity_g, pl_Float intensity_b, pl_Float halfDist);


  // privatestuff
  pl_uChar Type;               /* Type of light: PL_LIGHT_* */
  pl_Float Xp WDL_FIXALIGN;
  pl_Float Yp, Zp;         /* If Type=PL_LIGHT_POINT*,
                                  this is Position (PL_LIGHT_POINT_*),
                                  otherwise if PL_LIGHT_VECTOR,
                                  Unit vector */
  pl_Float Intensity[3];           /* Intensity. 0.0 is off, 1.0 is full */
  pl_Float HalfDistSquared;     /* Distance squared at which 
                                   PL_LIGHT_POINT_DISTANCE is 50% */
};


class pl_Cam {
public:
  pl_Cam() : m_fBuffer(NULL,0,0,0,false)
  {
    m_lastFBScaling=256;
    m_lastFBWidth=m_lastFBHeight=0;
    Fov=90.0;
    AspectRatio=1.0;
    Sort=1;
    ClipBack=-1.0;
    CenterX=CenterY=0;
    X=Y=Z=0.0;
    WantZBuffer=false;
    Pitch=Pan=Roll=0.0;
    GenMatrix = true;
  }
  ~pl_Cam()
  {
  }


  void SetTarget(pl_Float x, pl_Float y, pl_Float z); 

  pl_Float Pitch, Pan, Roll;     /* Camera angle in degrees in worldspace */
  pl_Float Fov;                  /* FOV in degrees valid range is 1-179 */
  pl_Float AspectRatio;          /* Aspect ratio (usually 1.0) */

  pl_Float ClipBack WDL_FIXALIGN;             /* Far clipping ( < 0.0 is none) */
  pl_Float X, Y, Z;              /* Camera position in worldspace */

  pl_Float CamMatrix[16];        /* should be rotation-only. translation will mess with the normals */

  pl_sChar Sort;                 /* Sort polygons, -1 f-t-b, 1 b-t-f, 0 no */
  pl_sInt CenterX, CenterY;      /* Offset center of screen from actual center by this much... */
  
  pl_Bool WantZBuffer;
  pl_Bool GenMatrix;             /* if set, generates CamMatrix from Pan/Pitch/Roll in Begin() */

  void Begin(LICE_IBitmap *fb, bool want_zbclear=true, pl_ZBuffer zbclear=0.0);
  void RenderLight(pl_Light *light);
  void RenderObject(pl_Obj *obj, const pl_Float *bmatrix=NULL, const pl_Float *bnmatrix=NULL);
  void SortToCurrent(); // sorts all faces added since Begin() or last SortToCurrent() call. useful for if you use zbuffering with transparent objects (draw them last)

  // returns true if onscreen x/y/z are world coordinates
  bool ProjectCoordinate(pl_Float x, pl_Float y, pl_Float z, pl_Float *screen_x, pl_Float *screen_y, pl_Float *dist); // outputs can be NULL if not needed

  LICE_IBitmap *GetFrameBuffer() { return m_fBuffer.m_buf ? &m_fBuffer : NULL; }
  WDL_TypedBuf<pl_ZBuffer> zBuffer;           /* Z Buffer (validate size before using)*/

  void End();

  int RenderTrisIn;
  int RenderTrisCulled;
  int RenderTrisOut;

  double RenderPixelsOut WDL_FIXALIGN;

  void PutFace(pl_Face *TriFace);

protected:
  LICE_WrapperBitmap m_fBuffer;
  pl_uInt m_lastFBWidth, m_lastFBHeight; // unscaled sizes when compared to m_fBuffer
  pl_uInt m_lastFBScaling;
  pl_sInt m_lastCX, m_lastCY; // calculated as w/2+CenterX/2 etc

    // internal use
  void ClipRenderFace(pl_Face *face, pl_Obj *obj);
  int ClipNeeded(pl_Face *face, pl_Obj *obj); // 0=no draw, 1=drawing (possibly splitting) necessary
  void RecalcFrustum(int fbw, int fbh);
  double CalcFOVFactor(double fbw) const
  {
    return fbw/tan(plMin(plMax(Fov,1.0),179.0)*(PL_PI/360.0));
  }


  #define PL_NUM_CLIP_PLANES 5

  struct _clipInfo
  {
    pl_Vertex newVertices[8];
    pl_Float ShadeInfos[8][3];
    pl_Float MappingU[PLUSH_MAX_MAPCOORDS][8];
    pl_Float MappingV[PLUSH_MAX_MAPCOORDS][8];
  };

  _clipInfo m_cl[2] WDL_FIXALIGN;
  pl_Float  m_clipPlanes[PL_NUM_CLIP_PLANES][4];
  pl_Float  m_fovfactor, m_adj_asp; // recalculated

   /* Returns: 0 if nothing gets in,  1 or 2 if pout1 & pout2 get in */
  pl_uInt _ClipToPlane(pl_uInt numVerts, pl_Float  *plane);


  struct _faceInfo {
    pl_Float zd;
    pl_Face *face;
    pl_Obj *obj;
  } WDL_FIXALIGN;

  struct _lightInfo {
    pl_Float l[3];
    pl_Light *light;
  } WDL_FIXALIGN;

  static int sortRevFunc(const void *a, const void *b);
  static int sortFwdFunc(const void *a, const void *b);

  int _numfaces,_numfaces_sorted;
  WDL_TypedBuf<_faceInfo> _faces;

  int _numlights;
  WDL_TypedBuf<_lightInfo> _lights;

  void _RenderObj(pl_Obj *, pl_Float *, pl_Float *);

  WDL_HeapBuf _sort_tmpspace;

};




/******************************************************************************
** Object Primitives Code (pl_make.cpp)
******************************************************************************/

/* 
  plMakePlane() makes a plane centered at the origin facing up the y axis.
  Parameters:
    w: width of the plane (along the x axis)
    d: depth of the plane (along the z axis)
    res: resolution of plane, i.e. subdivisions
    m: material to use
  Returns:
    pointer to object created.
*/
pl_Obj *plMakePlane(pl_Float w, pl_Float d, pl_uInt res, pl_Mat *m);

/*
  plMakeBox() makes a box centered at the origin
  Parameters:
    w: width of the box (x axis)
    d: depth of the box (z axis)
    h: height of the box (y axis)
  Returns:
    pointer to object created.
*/
pl_Obj *plMakeBox(pl_Float w, pl_Float d, pl_Float h, pl_Mat *m);

/*
 plMakeDisc() makes a disc centered at the origin
 Parameters:
   r: radius of the disc (x-z axis)
   divr: division of of disc (around the circle) (>=3)
   m: material to use
 Returns:
   pointer to object created.
 */
pl_Obj *plMakeDisc(pl_Float r, pl_uInt divr, pl_Mat *m);

/* 
  plMakeCone() makes a cone centered at the origin
  Parameters:
    r: radius of the cone (x-z axis)
    h: height of the cone (y axis)
    div: division of cone (>=3)
    cap: close the big end?
    m: material to use
  Returns:
    pointer to object created.
*/
pl_Obj *plMakeCone(pl_Float r, pl_Float h, pl_uInt div, pl_Bool cap, pl_Mat *m);

/*
  plMakeCylinder() makes a cylinder centered at the origin
  Parameters:
    r: radius of the cylinder (x-z axis)
    h: height of the cylinder (y axis)
    divr: division of of cylinder (around the circle) (>=3)
    captop: close the top
    capbottom: close the bottom
    m: material to use
  Returns:
    pointer to object created.
*/
pl_Obj *plMakeCylinder(pl_Float r, pl_Float h, pl_uInt divr, pl_Bool captop, 
                       pl_Bool capbottom, pl_Mat *m);

/*
  plMakeSphere() makes a sphere centered at the origin.
  Parameters:
    r: radius of the sphere
    divr: division of the sphere (around the y axis) (>=3)
    divh: division of the sphere (around the x,z axis) (>=3)
    m: material to use
  Returns:
    pointer to object created.
*/
pl_Obj *plMakeSphere(pl_Float r, pl_uInt divr, pl_uInt divh, pl_Mat *m);

/*
  plMakeTorus() makes a torus centered at the origin
  Parameters:
    r1: inner radius of the torus
    r2: outer radius of the torus
    divrot: division of the torus (around the y axis) (>=3)
    divrad: division of the radius of the torus (x>=3)
    m: material to use
  Returns:
    pointer to object created.
*/
pl_Obj *plMakeTorus(pl_Float r1, pl_Float r2, pl_uInt divrot, 
                    pl_uInt divrad, pl_Mat *m);


/******************************************************************************
** File Readers (pl_read_*.cpp)
******************************************************************************/

/* 
  plRead3DSObj() reads a 3DS object
  Parameters:
    fn: filename of object to read
    m: material to assign it
  Returns:
    pointer to object
  Notes:
    This reader organizes multiple objects like so:
      1) the first object is returned
      2) the second object is the first's first child
      3) the third object is the second's first child
      4) etc
*/
pl_Obj *plRead3DSObj(void *ptr, int size, pl_Mat *m);
pl_Obj *plRead3DSObjFromFile(char *fn, pl_Mat *m);
pl_Obj *plRead3DSObjFromResource(HINSTANCE hInst, int resid, pl_Mat *m);

/*
  plReadCOBObj() reads an ascii .COB object
  Parameters:
    fn: filename of object to read
    mat: material to assign it
  Returns:
    pointer to object
  Notes:
    This is Caligari's ASCII object format.
    This reader doesn't handle multiple objects. It just reads the first one.
    Polygons with lots of sides are not always tesselated correctly. Just
      use the "Tesselate" button from within truespace to improve the results.
*/
pl_Obj *plReadCOBObj(char *fn, pl_Mat *mat);

/*
  plReadJAWObj() reads a .JAW object.
  Parameters:
    fn: filename of object to read
    m: material to assign it
  Returns:
    pointer to object
  Notes:
    For information on the .JAW format, please see the jaw3D homepage,
      http://www.tc.umn.edu/nlhome/g346/kari0022/jaw3d/
*/
pl_Obj *plReadJAWObj(char *fn, pl_Mat *m);



/******************************************************************************
** Math Code (pl_math.cpp)
******************************************************************************/

/*
  plMatrixRotate() generates a rotation matrix
  Parameters:
    matrix: an array of 16 pl_Floats that is a 4x4 matrix
    m: the axis to rotate around, 1=X, 2=Y, 3=Z.
    Deg: the angle in degrees to rotate
  Returns: 
    nothing
*/
void plMatrixRotate(pl_Float matrix[], pl_uChar m, pl_Float Deg);

/*
  plMatrixTranslate() generates a translation matrix
  Parameters:
    m: the matrix (see plMatrixRotate for more info)
    x,y,z: the translation coordinates
  Returns:
    nothing
*/
void plMatrixTranslate(pl_Float m[], pl_Float x, pl_Float y, pl_Float z);

/* 
  plMatrixMultiply() multiplies two matrices
  Parameters:
    dest: destination matrix will be multipled by src
    src: source matrix
  Returns:
    nothing
  Notes: 
    this is the same as dest = dest*src (since the order *does* matter);
*/
void plMatrixMultiply(pl_Float *dest, const pl_Float src[]);

/*
   plMatrixApply() applies a matrix.
  Parameters:
    m: matrix to apply
    x,y,z: input coordinate
    outx,outy,outz: pointers to output coords.
  Returns:
    nothing
  Notes: 
    applies the matrix to the 3d point to produce the transformed 3d point
*/
void plMatrixApply(pl_Float *m, pl_Float x, pl_Float y, pl_Float z, 
                   pl_Float *outx, pl_Float *outy, pl_Float *outz);

/*
  plNormalizeVector() makes a vector a unit vector
  Parameters:
    x,y,z: pointers to the vector
  Returns:
    nothing
*/
void plNormalizeVector(pl_Float *x, pl_Float *y, pl_Float *z);

/*
  plDotProduct() returns the dot product of two vectors
  Parameters:
    x1,y1,z1: the first vector
    x2,y2,z2: the second vector
  Returns:
    the dot product of the two vectors
*/
pl_Float plDotProduct(pl_Float x1, pl_Float y1, pl_Float z1,
                      pl_Float x2, pl_Float y2, pl_Float z2);




#endif /* !_PLUSH_H_ */
