#pragma ident "@(#)cgpmisc.c	1.21 99/04/26 SMI"

/*
 * Copyright (c) 1997 by Sun Microsystems, Inc.
 * All Rights Reserved
 */


/******************************************************************************
 * cgpmisc.c
 *
 * This file contains all the miscellaneous compressed geometry routines.
 *
 *  List of functions in this file:
 *	cgBegin
 *	cgBeginCompressedGeometryStream
 *	cgEnd
 *	cgEndCompressedGeometryStream
 *      cgGetError
 *      cgGeomFrontFace
 *	cgiSetError
 *	
 *****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <values.h>

#include "types.h"
#include "meshify.h"
#include "zdebug.h"

#include "cgpi.h"
#include "cgpmesher.h"
#include "cgpstream.h"
#include "cgpcompress.h"
#include "cgpbuildbuffer.h"

/*
 * Global state values
 */

/* values for Error located in cgp.h */
CGenum          CgError = CG_NO_ERROR;

CGbitfield      CurrentState = 0;       /* state of CG library       */

CGenum          CurFrontFace = CGP_FRONT_FACE_CCW ;
CGfloat         CurNX = 0.0;            /* global normal             */
CGfloat         CurNY = 0.0;
CGfloat         CurNZ = 1.0;
CGfloat         CurRed = 0.0;           /* global color              */
CGfloat         CurGreen = 0.0;
CGfloat         CurBlue = 0.0;
CGfloat         CurAlpha = 1.0;

static CGint    GeomQuant;		/* number of bits to quantize*/
static CGint    NormalQuant;            /*  data to                  */
static CGint    ColorQuant;
static CGenum   QuantEnforcement;       /* how strict should         */
                                        /*  quantize values be       */
                                        /*  followed                 */

CGPprimlist*    Prims;                  /* list of primitives        */
CGPprimlist*    CurPrim;                /* current primitive         */
CGuint          NumPrims;               /* total number of primitives*/

CGPptbuffer*    CurPtBuffer;            /* Buffer being filled       */
CGPpoint*       CurPt;                  /* Point being processed     */
CGuint          NumPts;                 /* number of points in       */
                                        /*  current buffer           */

CGenum		PrimClass;	  	/* class of primitives in a  */
					/*  compressed geometry      */
         				/*  buffer.  All primitives  */
                              		/*  must belong to the same  */
					/*  class:		     */
 					/*    CG_POINT_CLASS         */
       					/*    CG_LINE_CLASS          */
					/*    CG_FILL_CLASS          */

/* need max/min geom values to compute scale/offset */
CGfloat  MaxX;
CGfloat  MinX;
CGfloat  MaxY;
CGfloat  MinY;
CGfloat  MaxZ;
CGfloat  MinZ;


/*******************
 * cgBegin
 *
 *  Used to delimit the start of a compressed geometry primitive.
 *  Allocates a primList structure and copies any global data.
 *
 *  Input:
 *    CGenum  primType  - the type of primitive being constructed.
 *
 *  Output:
 *    None.
 *
 */
void
cgBegin (CGenum primType)
{
    CGPprimitive*  prim;  /* will point to the actual primitive structure */

    /* must be within a BeginStream for this to be valid */
    if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM))
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    /* make sure this isn't being called within a cgBegin - cgEnd pair */
    if (CurrentState & CGP_CS_LIB_IN_CGBEGIN)
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    /* Check for valid primitive type   */
    /*  OK, kinda cheat here and just check for valid numeric range */
    if (!(primType >= CG_POINTS && primType <= CG_POLYGON))
    {
        cgiSetError(CG_INVALID_ENUM);
        return;
    }

    /* first time a cgBegin has been called? */
    if (Prims == NULL)
    {
        /* assign primitive class */
        switch (primType)
        {
            case CG_POINTS:
                PrimClass = CGP_CLASS_POINT;
                break;

            case CG_LINES:
            case CG_LINE_LOOP:
            case CG_LINE_STRIP:
                PrimClass = CGP_CLASS_LINE;
                break;

            case CG_TRIANGLES:
            case CG_TRIANGLE_STRIP:
            case CG_TRIANGLE_FAN:
            case CG_QUADS:
            case CG_QUAD_STRIP:
            case CG_POLYGON:
                PrimClass = CGP_CLASS_FILL;
                break;
        }

        /* allocate first primList */
        CurPrim = Prims = (CGPprimlist *) malloc(sizeof(CGPprimlist));

        if (Prims == NULL)
        {
            cgiSetError(CG_OUT_OF_MEMORY);
            return;
        }

   }
    else
    {
        /* ensure prim class is the same */
        switch (primType)
        {
            case CG_POINTS:
                if (PrimClass != CGP_CLASS_POINT)
                {
                    cgiSetError(CG_DATA_INCONSISTENT);
                    return;
                }
                break;

            case CG_LINES:
            case CG_LINE_LOOP:
            case CG_LINE_STRIP:
                if (PrimClass != CGP_CLASS_LINE)
                {
                    cgiSetError(CG_DATA_INCONSISTENT);
                    return;
                }
                break;

            case CG_TRIANGLES:
            case CG_TRIANGLE_STRIP:
            case CG_TRIANGLE_FAN:
            case CG_QUADS:
            case CG_QUAD_STRIP:
            case CG_POLYGON:
                if (PrimClass != CGP_CLASS_FILL)
                {
                    cgiSetError(CG_DATA_INCONSISTENT);
                    return;
                }
                break;
        }

        /* add another primitive to the list */
        CurPrim->nextPrimitive = (CGPprimlist *) malloc (sizeof(CGPprimlist));

        if (CurPrim->nextPrimitive == NULL)
        {
            cgiSetError(CG_OUT_OF_MEMORY);
            return;
        }

        CurPrim = CurPrim->nextPrimitive;
    }

    NumPrims++;

    /* set values */
    memset((char *) CurPrim, 0, sizeof(CGPprimlist));
    prim = &CurPrim->primitive;
    prim->primType = primType;

    /*
     * intialize primitive with global data - if point type
     *  has this data then get it from there
     */

    if (CurrentState & CGP_CS_CURRENT_COLOR_VALID)
    {
        prim->globalColorR = CurRed;
        prim->globalColorG = CurGreen;
        prim->globalColorB = CurBlue;

        prim->primFlags |= CGP_GLOBAL_COLOR_VALID;
    }

    if (CurrentState & CGP_CS_CURRENT_ALPHA_VALID)
    {
        prim->globalAlpha = CurAlpha;

        prim->primFlags |= CGP_GLOBAL_ALPHA_VALID;
    }

    if (CurrentState & CGP_CS_CURRENT_NORMAL_VALID)
    {
        prim->globalNormalX = CurNX;
        prim->globalNormalY = CurNY;
        prim->globalNormalZ = CurNZ;

        prim->primFlags |= CGP_GLOBAL_NORMAL_VALID;
    }

    /* Note: CG_FRONT_FACE_CW is the "on" bit */
    if (CurFrontFace == CG_CW)
        prim->primFlags |= CGP_FRONT_FACE_CW;

    /* set up current point buffer pointers */
    CurPtBuffer = &prim->ptBuff;
    CurPt = &CurPtBuffer->pts[0];
    NumPts = 0;

    /* update library state */
    CurrentState |= (CGP_CS_LIB_NO_VERTEX | CGP_CS_LIB_IN_CGBEGIN);
}


/*******************
 * cgBeginCompressedGeometryStream
 *
 *  Used to delimit the start of a compressed geometry stream.
 *  Resets all state.
 *
 *  Input:
 *    CGint  geomQuant		- number of bits to quantize geometry data to
 *    CGint  colorQuant		- number of bits to quantize color data to
 *    CGint  normalQuant	- number of bits to quantize normal data to
 *    CGenum quantEnforcement	- how strict must the quantization levels
 *                                be enforced
 *
 *  Output:
 *    None.
 *
 */
void
cgBeginCompressedGeometryStream (CGint geomQuant, CGint normalQuant,
				 CGint colorQuant,
				 CGenum quantEnforcement)
{
    /* make sure this isn't being called before an EndStream */
    if (CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM)
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    /* have a BeginStream, enable other commands */
    CurrentState = CGP_CS_LIB_IN_BEGIN_STREAM;

    NumPrims = 0;
    CurPrim = Prims = NULL;

    /* set up max/min values for viewport info */
    MaxX = -MAXFLOAT;
    MinX = MAXFLOAT;
    MaxY = -MAXFLOAT;
    MinY = MAXFLOAT;
    MaxZ = -MAXFLOAT;
    MinZ = MAXFLOAT;

    /* save compression args, doing mimimal error checking */
    if (geomQuant < 2)
        GeomQuant = 2;
    else if (geomQuant > 16)
        GeomQuant = 16;
    else
        GeomQuant = geomQuant;

    if (normalQuant < 8)
        NormalQuant = 8;
    else if (normalQuant > 18)
        NormalQuant = 18;
    else
        NormalQuant = normalQuant;

    if (colorQuant < 2)
        ColorQuant = 2;
    else if (colorQuant > 16)
        ColorQuant = 16;
    else
        ColorQuant = colorQuant;

    if (quantEnforcement == CG_QUANTIZE_STRICT)
        QuantEnforcement = CG_QUANTIZE_STRICT;
    else
        QuantEnforcement = CG_QUANTIZE_LOOSE;
}


/*******************
 * cgEnd
 *
 *  Used to delimit the end of a compressed geometry primitive.
 *
 *  Input:
 *    None.
 *
 *  Output:
 *    None.
 *
 */
void cgEnd (void)
{
    CGPprimitive* prim = &CurPrim->primitive;
    CGPprimlist*  lastPrim;
    CGPptbuffer*  buff;

    /* must be within a BeginStream and a cgBegin for this to be valid */
    if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM) ||
        !(CurrentState & CGP_CS_LIB_IN_CGBEGIN))
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }
 
    /* reset library state */
    CurrentState &= ~(CGP_CS_LIB_IN_CGBEGIN | CGP_CS_CURRENT_COLOR_VALID |
                      CGP_CS_CURRENT_ALPHA_VALID | CGP_CS_CURRENT_NORMAL_VALID);
    

    /* was the last buffer allocated used? */
    if (NumPts == 0)
    {
        /* is this the one and only point buffer? */
        if (CurPtBuffer == &prim->ptBuff)
        {
            /* 
             * there are no cgVertex* calls in this geometry!
             *  because current color/normal are undefined after
             *  rendering compressed geometry and subsequent primitves
             *  global state data is already handled, no need to bother
             *  with this - just pretend all this never happened.
             */

            free(CurPtBuffer);

            if (CurPrim == Prims)
            {
                /* this is the first primitive */
                free (CurPrim);
                Prims = NULL;
            }
            else
            {
                /* get to this primitive in the list */
                for (lastPrim = Prims; lastPrim->nextPrimitive != CurPrim;
                     lastPrim = lastPrim->nextPrimitive);

                /* and nuke it */
                free(lastPrim->nextPrimitive);
                lastPrim->nextPrimitive = NULL;
            }

            NumPrims--;
            return;
        }
        else
        {

            /* Search for previous point buffer (the last one with data) */
            for (buff = &prim->ptBuff;
                 buff->nextPtBuffer != CurPtBuffer; buff = buff->nextPtBuffer);

            /* free unused point buffer */
            free(buff->nextPtBuffer);
            buff->nextPtBuffer = NULL;
        }
    }

}



/*******************
 * cgEndCompressedGeometryStream
 *
 *  Used to delimit the end of a compressed geometry stream.
 *  This is also the place where all of the compression routines
 *  will be called.  When this routine completes, the user will
 *  have their compressed geometry buffer.
 *
 *  Input:
 *    None.
 *
 *  Output:
 *    CGubyte**   data      - pointer to the compressed geometry.  This memory
 *                            will be malloc'd by the compression routines, the
 *                            user is responsible for freeing this memory.
 *    CGint*      size      - will hold the size (in bytes) of the data.
 *    CGviewport* viewport  - will hold the scale and offset values needed to
 *                            tranform the compressed data back to its original
 *                            place in the scene.  This memory will be malloc'd
 *                            by the compression routines, the user is
 *                            responsible for freeing this memory.  If no scale
 *                            or offset values are necessary (would be 1.0 and
 *                            0.0, respectively) then NULL is returned.
 *
 */
void
cgEndCompressedGeometryStream(CGubyte **data, CGint *size,
                                              CGviewport *viewport)
{
    CGviewport vwpt;
    CGfloat min, max;
    CGboolean normalPresent;
    CGboolean colorPresent;
    CGboolean alphaPresent;

    /* ensure library is in proper state */
    if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM) ||
        CurrentState & CGP_CS_LIB_IN_CGBEGIN)
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    CurrentState &= ~CGP_CS_LIB_IN_BEGIN_STREAM;

    if (CgError == CG_NO_ERROR)
    {
        CGPcommandstream *firstCommand;

        /* need to check 1st buffer to determine what "Extra" info present */
        /* look for normal */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_NORMAL_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_NORMAL))
            normalPresent = CGP_FALSE;
        else
            normalPresent = CGP_TRUE;

        /* look for color */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_COLOR_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_COLOR))
            colorPresent = CGP_FALSE;
        else
            colorPresent = CGP_TRUE;

        /* look for alpha */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_ALPHA_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_COLOR_ALPHA))
            alphaPresent = CGP_FALSE;
        else
            alphaPresent = CGP_TRUE;

        /* want to have same scale value so GL_RESCALE_NORMAL_EXT */
        /* can be used instead of GL_NORMALIZE                    */

        /* find largest dimesnions needed to enclose shape */
        max = MaxX - MinX;

        if (MaxY - MinY > max)
            max = MaxY - MinY;

        if (MaxZ - MinZ > max)
            max = MaxZ - MinZ;

	viewport->scale = max / (float) 2.0;
        vwpt.scale = 1.0 / viewport->scale;

	vwpt.xo = ((MaxX - MinX) / 2.0) + MinX;
        vwpt.yo = ((MaxY - MinY) / 2.0) + MinY;
        vwpt.zo = ((MaxZ - MinZ) / 2.0) + MinZ;

        viewport->xo = vwpt.xo;
        viewport->yo = vwpt.yo;
        viewport->zo = vwpt.zo;

        /* TODO: return an error value? */
        cgpBuildMesh(Prims, GeomQuant, NormalQuant, ColorQuant,
          QuantEnforcement);

        cgpStreamData(Prims, &firstCommand, &vwpt, GeomQuant, ColorQuant,
                      NormalQuant);

        cgpCompressStream(firstCommand, GeomQuant, ColorQuant, NormalQuant);

        cgpBuildCompressedBuffer(firstCommand, PrimClass, normalPresent,
                                 colorPresent, alphaPresent, viewport,
                                 data, size);
    }
}

/*******************
 * cgAbortGeometryStream
 *
 *  Used to abort the compression of a geometry stream.  Leaves the state
 *  cleaned as if cgEndCompressedGeometryStream was called.
 *
 *  Input:
 *    None.
 *
 *  Output:
      None.
 *
 */
void
cgAbortGeometryStream()
{
    CGPprimlist*   primlist;
    CGPprimitive*  curPrim;
    CGPptbuffer*   curPtBuff;

    /* ensure library is in proper state */
    if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM) ||
        CurrentState & CGP_CS_LIB_IN_CGBEGIN)
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    CurrentState &= ~CGP_CS_LIB_IN_BEGIN_STREAM;

    cgResetPrims();
}


/*******************
 * cgGetError
 *
 * Returns the current error state of the library and resets
 * the current error state to CG_NO_ERROR.
 *
 *  Input:
 *    None.
 *
 *  Output:
 *    CGenum .
 *
 */
CGenum
cgGetError(void)
{
    CGenum error;

    error = CgError;

    CgError = CG_NO_ERROR;

    return error;
}


/*******************
 * cgGeomFrontFace
 *
 *  Sets the winding for subsequent polygons.  Cannot be called
 *  within a cgBegin - cgEnd pair.
 *
 *  Input:
 *    CGenum  frontFace  - the winding to set.
 *
 *  Output:
 *    None.
 *
 */
void
cgGeomFrontFace(CGenum frontFace)
{
    /*
     * must begin a compressed geometry stream but not in a
     * cgBegin - cgEnd pair.
     */
    if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM) ||
        CurrentState & CGP_CS_LIB_IN_CGBEGIN)
    {
        cgiSetError(CG_INVALID_OPERATION);
        return;
    }

    if (frontFace != CG_CW && frontFace != CG_CCW)
    {
        cgiSetError(CG_INVALID_ENUM);
        return;
    }

    CurFrontFace = frontFace;
}


/*******************
 * cgiSetError
 *
 *  Tests to see if an error has already been logged, if not sets
 *  Error to the passed in value.
 *
 *  Input:
 *    CGenum  error  - the error to be set.
 *
 *  Output:
 *    None.
 *
 */
void
cgiSetError (CGenum error)
{
    if (CgError == CG_NO_ERROR)
        CgError = error;
}


void
cgPutEndStream(){
  CurrentState &= ~CGP_CS_LIB_IN_BEGIN_STREAM;
}

void
cgPutBeginStream(){
    CurrentState = CGP_CS_LIB_IN_BEGIN_STREAM;
}



void
primlistDestroy(CGPprimlist *prims)
{
  CGPprimlist *currPrim;

  currPrim = prims;
  while (currPrim != NULL){
    CGPprimlist *nextPrim;
    CGPptbuffer *currPtBuff;

    nextPrim = currPrim->nextPrimitive;

    currPtBuff = &currPrim->primitive.ptBuff;
    while (currPtBuff != NULL){
      CGPptbuffer *nextPtBuff;

      nextPtBuff = currPtBuff->nextPtBuffer;
      zdo6 printf("freed a pt buff \n"); 
      if (currPtBuff != &currPrim->primitive.ptBuff) {
	  free(currPtBuff);
      }
      currPtBuff = nextPtBuff;
    }
    zdo6 printf("freed a prim \n");
    free(currPrim);
    currPrim = nextPrim;
  }
}


void
cgResetPrims()
{
  primlistDestroy(Prims);
  NumPrims = 0;
  CurPrim = Prims = NULL;
}


/*
 * prims2Compress
 *
 *    Continue compression steps given a set of primitives.
 */

void
prims2Compress(CGubyte **data, CGint *size,
		CGviewport *viewport)
{
    CGboolean normalPresent;
    CGboolean colorPresent;
    CGboolean alphaPresent;

    /* TODO - doc this */
    if (CgError == CG_NO_ERROR)
    {
        CGPcommandstream *firstCommand;
        CGubyte *cgBuff;
       /* need to check 1st buffer to determine what "Extra" info present */
        /* look for normal */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_NORMAL_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_NORMAL))
            normalPresent = CGP_FALSE;
        else
            normalPresent = CGP_TRUE;

        /* look for color */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_COLOR_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_COLOR))
            colorPresent = CGP_FALSE;
        else
            colorPresent = CGP_TRUE;

        /* look for alpha */
        if (!(Prims->primitive.primFlags & CGP_GLOBAL_ALPHA_VALID) &&
              !(Prims->primitive.primFlags & CGP_PT_TYPE_VTX_COLOR_ALPHA))
            alphaPresent = CGP_FALSE;
        else
            alphaPresent = CGP_TRUE;

	/* ensure library is in proper state */
	if (!(CurrentState & CGP_CS_LIB_IN_BEGIN_STREAM) ||
	    CurrentState & CGP_CS_LIB_IN_CGBEGIN)
	  {
	    cgiSetError(CG_INVALID_OPERATION);
	    return;
	  }
	
	CurrentState &= ~CGP_CS_LIB_IN_BEGIN_STREAM;
	
	cgpStreamData(Prims, &firstCommand, viewport, GeomQuant, ColorQuant, NormalQuant);
	
        cgpCompressStream(firstCommand, GeomQuant, ColorQuant, NormalQuant);
	
        cgpBuildCompressedBuffer(firstCommand,
                                 PrimClass, normalPresent, colorPresent,
				 alphaPresent, viewport, data, size);

    }
}
  

void cgMBPush()
{
 CurPt->header |= CGP_POINT_HDR_PUSH; 
}

void cgMBRef(int n)
{
 CurPt->header |= CGP_POINT_HDR_REF;	
 CGP_SET_POINT_HDR_REF_NUMBER(CurPt->header, n);
}

void cgCurPtSetHeader(int mask)
{
  CurPt->header |= mask;
}

void cgCurPtSetQuant(int geomQuant, int normalQuant, int colorQuant)
{
  CurPt->geomQuant = (CGshort) geomQuant;
  CurPt->colorQuant = (CGshort) colorQuant;
  CurPt->normalQuant = (CGshort) normalQuant;
}

void
cgCurPtSetMBRef(int ref)
{
  CGP_SET_POINT_HDR_REF_NUMBER(CurPt->header, ref);
}


void
cgCurPtSetFakeQuant(float x, float y, float z)
{
  CurPt->qx = x;
  CurPt->qy = y;
  CurPt->qz = z;
}
