/******************************Module*Header*******************************\
* Module Name: nff.c
*
* Parse the Wavefront OBJ format.
*
* Documentation on OBJ available from:
*
*   Wavefront Technologies, 530 E. Montecito St., Santa Barbara, CA 93103,
*   Phone: 805-962-8117.
*
*   Murray, J. D. and van Ryper, W., _Encyclopedia of Graphics File Formats_,
*   O'Reilly & Associates, 1994, pp. 727-734.
*
* Created: 15-Mar-1995 23:27:08
* Author: Gilman Wong [gilmanw]
*
* Copyright (c) 1995 Microsoft Corporation
*
\**************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>

#include "global.h"
#include "nff.h"

#define STATIC

typedef struct tagOBJPRIV
{
    BOOL bInList;       // TRUE while compiling display list

// Vertex arrays

    MyXYZ   *pxyzGeoVert;   // array of geometric vertices
    MyXYZ   *pxyzTexVert;   // array of texture vertices
    MyXYZ   *pxyzNorm;      // array of vertex normals

// Number allocated for each array

    GLuint  numGeoVert;     // total number of geometric vertices
    GLuint  numTexVert;     // total number of texture vertices
    GLuint  numNorm;        // total number of normals

// Number of vertices currently in each array

    GLuint  curGeoVert;     // current number of geometric vertices
    GLuint  curTexVert;     // current number of texture vertices
    GLuint  curNorm;        // current number of normals

    GLfloat rMax;

} OBJPRIV;

GLint maxLights;

STATIC void parseObjFile(SCENE *, LPTSTR);
STATIC void DoDisplayList(SCENE *);
STATIC void EndDisplayList(SCENE *);
STATIC void parseObjNormal(SCENE *scene, FILE *file, char *ach, int n);
STATIC void parseObjTextureVertex(SCENE *scene, FILE *file, char *ach, int n);
STATIC void parseObjVertex(SCENE *scene, FILE *file, char *ach, int n);
STATIC void parseObjFace(SCENE *scene, FILE *file, char *ach, int n);
STATIC void fakeObjLight(SCENE *scene, GLfloat x, GLfloat y, GLfloat z);

SCENE *ObjOpenScene(LPTSTR lpstr)
{
    SCENE *scene;

    scene = (SCENE *) LocalAlloc(LMEM_FIXED, sizeof(SCENE) + sizeof(OBJPRIV));
    if (scene)
    {
        OBJPRIV *objPriv = (OBJPRIV *) (scene + 1);

        glGetIntegerv(GL_MAX_LIGHTS, &maxLights);

        scene->xyzFrom.x = 0.0;
        scene->xyzFrom.y = 0.0;
        //scene->xyzFrom.z = 5.0;

        scene->xyzAt.x = 0.0;
        scene->xyzAt.y = 0.0;
        scene->xyzAt.z = 0.0;

        scene->xyzUp.x = 0.0;
        scene->xyzUp.y = 1.0;
        scene->xyzUp.z = 0.0;

        scene->ViewAngle   = 45.0f;
        scene->Hither      = 0.1f;
        //scene->Yon         = 100.0f;
        scene->AspectRatio = 1.0f;

        scene->szWindow.cx = 400;
        scene->szWindow.cy = 400;

        scene->rgbaClear.r = (GLfloat) 0.0; // default is black
        scene->rgbaClear.g = (GLfloat) 0.0;
        scene->rgbaClear.b = (GLfloat) 0.0;
        scene->rgbaClear.a = (GLfloat) 1.0;

        scene->Lights.count = 0;
        scene->Lights.listBase = 1;

        scene->Objects.count = 1;
        scene->Objects.listBase = maxLights + 1;

        scene->pvData = (VOID *) objPriv;
        objPriv->bInList     = FALSE;
        objPriv->pxyzGeoVert = (MyXYZ *) NULL;
        objPriv->pxyzTexVert = (MyXYZ *) NULL;
        objPriv->pxyzNorm    = (MyXYZ *) NULL;
        objPriv->numGeoVert  = 0;
        objPriv->numTexVert  = 0;
        objPriv->numNorm     = 0;
        objPriv->curGeoVert  = 0;
        objPriv->curTexVert  = 0;
        objPriv->curNorm     = 0;
        objPriv->rMax        = (GLfloat) 0.0;

        LBprintf("================================");
        LBprintf("Parsing OBJ file, please wait...");
        LBprintf("================================");
        parseObjFile(scene, lpstr);
        LBprintf("Here we go!");

        scene->xyzFrom.z = 2.0f * objPriv->rMax * tan(90.0f - scene->ViewAngle);
        scene->Yon = (scene->xyzAt.z - scene->xyzFrom.z) * (scene->xyzAt.z - scene->xyzFrom.z);
        scene->Yon = sqrt(scene->Yon) * 2.5f;

        fakeObjLight(scene, objPriv->rMax, objPriv->rMax, scene->xyzFrom.z);
    }
    else
    {
        LBprintf("ObjOpenScene: memory allocation failure");
    }

    return scene;
}

STATIC void parseObjFile(SCENE *scene, LPTSTR lpstr)
{
    FILE *file;
    char ach[512];
    char *pch;
    OBJPRIV *objPriv = (OBJPRIV *) scene->pvData;

    file = fopen(lpstr, "rt");

    if (file)
    {
        BOOL bKeepGoing = TRUE;

    // Do a quick first pass through the file so we know how big to
    // make the arrays.

        do
        {
            if (fgets(ach, sizeof(ach), file) != NULL)
            {
            // Skip leading whitespace.  Some input files have this.

                pch = ach;
                while (*pch && isspace(*pch)) pch++;

                switch(pch[0])
                {
                case 'V':
                case 'v':
                    switch(pch[1])
                    {
                    case 'N':
                    case 'n':
                        objPriv->numNorm++;
                        break;
                    case 'T':
                    case 't':
                        //objPriv->numTexVert++;
                        break;
                    case 'P':
                    case 'p':
                        break;
                    default:
                        objPriv->numGeoVert++;
                        break;
                    }
                    break;

                default:
                    break;
                }
            }
            else
            {
                bKeepGoing = FALSE;
                //LBprintf("possible EOF");
            }

        } while (bKeepGoing || !feof(file));
        rewind(file);

    // Allocate the arrays.

        objPriv->pxyzGeoVert = (MyXYZ *)
            LocalAlloc(LMEM_FIXED, sizeof(MyXYZ) * (objPriv->numNorm +
                                                    objPriv->numTexVert +
                                                    objPriv->numGeoVert));
        objPriv->pxyzTexVert = objPriv->pxyzGeoVert + objPriv->numGeoVert;
        objPriv->pxyzNorm    = objPriv->pxyzTexVert + objPriv->numTexVert;

    // Parse the file for real.

        bKeepGoing = TRUE;
        do
        {
            if (fgets(ach, sizeof(ach), file) != NULL)
            {
            // Skip leading whitespace.  Some input files have this.

                pch = ach;
                while (*pch && isspace(*pch)) pch++;

                switch(pch[0])
                {
                case 'G':
                case 'g':
                    //LBprintf(ach);
                    break;

                case 'V':
                case 'v':
                    switch(pch[1])
                    {
                    case 'N':
                    case 'n':
                        parseObjNormal(scene, file, pch, sizeof(ach) - (pch - ach));
                        break;
                    case 'T':
                    case 't':
                        //parseObjTextureVertex(scene, file, pch, sizeof(ach) - (pch - ach));
                        break;
                    case 'P':
                    case 'p':
                        break;
                    default:
                        parseObjVertex(scene, file, pch, sizeof(ach) - (pch - ach));
                        break;
                    }
                    break;

                case 'f':
                case 'F':
                    switch(pch[1])
                    {
                    case 'O':
                    case 'o':
                        parseObjFace(scene, file, &pch[1], sizeof(ach) - (pch - ach) - 1);
                        break;
                    default:
                        parseObjFace(scene, file, pch, sizeof(ach) - (pch - ach));
                        break;
                    }
                    break;

                case '#':
                    LBprintf(pch);
                    break;

                default:
                    //LBprintf("unknown: %s", ach);
                    break;
                }
            }
            else
            {
                bKeepGoing = FALSE;
                //LBprintf("possible EOF");
            }

        } while (bKeepGoing || !feof(file));
        fclose(file);

        EndDisplayList(scene);
    }
    else
    {
        LBprintf("fopen failed %s", lpstr);
        LBprintf("USAGE: viewer [OBJ filename].OBJ");
    }
}

STATIC void DoDisplayList(SCENE *scene)
{
    if (!((OBJPRIV *)scene->pvData)->bInList)
    {
        ((OBJPRIV *)scene->pvData)->bInList = TRUE;

        glNewList(scene->Objects.listBase, GL_COMPILE);

        //LBprintf("BEGIN DISPLAY LIST");
    }
}

STATIC void EndDisplayList(SCENE *scene)
{
    if (((OBJPRIV *)scene->pvData)->bInList)
    {
        ((OBJPRIV *)scene->pvData)->bInList = FALSE;

        glEndList();

        //LBprintf("END DISPLAY LIST");
    }
}

STATIC void parseObjNormal(SCENE *scene, FILE *file, char *ach, int n)
{
    OBJPRIV *objPriv = (OBJPRIV *) scene->pvData;

    sscanf(ach, "vn %g %g %g", &objPriv->pxyzNorm[objPriv->curNorm].x,
                               &objPriv->pxyzNorm[objPriv->curNorm].y,
                               &objPriv->pxyzNorm[objPriv->curNorm].z);

    objPriv->curNorm++;
}

STATIC void parseObjTextureVertex(SCENE *scene, FILE *file, char *ach, int n)
{
    OBJPRIV *objPriv = (OBJPRIV *) scene->pvData;

    sscanf(ach, "vt %g %g %g", &objPriv->pxyzTexVert[objPriv->curTexVert].x,
                               &objPriv->pxyzTexVert[objPriv->curTexVert].y,
                               &objPriv->pxyzTexVert[objPriv->curTexVert].z);

    objPriv->curTexVert++;
}

STATIC void parseObjVertex(SCENE *scene, FILE *file, char *ach, int n)
{
    OBJPRIV *objPriv = (OBJPRIV *) scene->pvData;

    sscanf(ach, "v %g %g %g", &objPriv->pxyzGeoVert[objPriv->curGeoVert].x,
                              &objPriv->pxyzGeoVert[objPriv->curGeoVert].y,
                              &objPriv->pxyzGeoVert[objPriv->curGeoVert].z);

    objPriv->curGeoVert++;
}

STATIC void parseObjFace(SCENE *scene, FILE *file, char *ach, int n)
{
    OBJPRIV *objPriv = (OBJPRIV *) scene->pvData;
    char *pch;
    GLuint i, numVert = 0;
    char achFormat[20];
    BOOL bReadTex = FALSE, bReadNorm = FALSE;
    GLuint v, vt, vn;
    GLfloat rMax;
    GLuint vv[3];
    MyXYZ  faceNorm;

    DoDisplayList(scene);

// Determine number of vertices.
// Each vertex is represented by a field of non-whitespace characters
// which are numbers or '/' characters.

    pch = &ach[1];
    while ( *pch )
    {
        while (*pch && isspace(*pch))
            pch++;

        if (*pch)
            numVert++;

        while (*pch && !isspace(*pch))
            pch++;
    }

// Bail if there aren't enough vertices to define a face.

    if (numVert < 3)
    {
        LBprintf("bad line: %s", ach);
        return;
    }

// Determine type of vertex info available.  Each vertex has the form
// v[/[vt][/[vn]]].  Some examples include:
//
//      f 1/2/3 ...
//      f 1//3  ...
//      f 1/2   ...
//      f 1/2/  ...
//      f 1     ...
//      f 1//   ...
//          etc.

    pch = &ach[1];

    while (*pch && isspace(*pch))
        pch++;

    if (*pch && isdigit(*pch))
        lstrcpy(achFormat, "%ld");

    while (*pch && isdigit(*pch))
        pch++;

    if (*pch == '/')
    {
        lstrcat(achFormat, "/");
        pch++;
    }

    if (*pch && isdigit(*pch))
    {
        bReadTex = TRUE;
        lstrcat(achFormat, "%ld");

        while (*pch && isdigit(*pch))
            pch++;
    }

    if (*pch == '/')
    {
        lstrcat(achFormat, "/");
        pch++;
    }

    if (*pch && isdigit(*pch))
    {
        bReadNorm = TRUE;
        lstrcat(achFormat, "%ld");

        while (*pch && isdigit(*pch))
            pch++;
    }

// If we need to compute our own normal, let's do it now.

    pch = &ach[1];

    if (!bReadNorm)
    {
        for ( i = 0; i < 3; i++ )
        {
            while (*pch && isspace(*pch))
                pch++;

            sscanf(pch, "%ld", &vv[i]);
            if (vv[i] > 0)
                vv[i] = vv[i] - 1;
            else
                vv[i] = objPriv->curGeoVert + vv[i];

            while (*pch && !isspace(*pch))
                pch++;

        }

        calcNorm((GLfloat *) &faceNorm,
                 (GLfloat *) &objPriv->pxyzGeoVert[vv[0]],
                 (GLfloat *) &objPriv->pxyzGeoVert[vv[1]],
                 (GLfloat *) &objPriv->pxyzGeoVert[vv[2]]);
    }

// Finally, parse out the vertices.

    pch = &ach[1];

    glBegin(numVert == 3 ? GL_TRIANGLES :
            numVert == 4 ? GL_QUADS :
                           GL_POLYGON);
    if (!bReadTex && !bReadNorm)
    {
        glNormal3fv((GLfloat *) &faceNorm);

        for ( i = 0; i < numVert; i++ )
        {
            while (*pch && isspace(*pch))
                pch++;

            sscanf(pch, achFormat, &v);
            if (v > 0)
                v = v - 1;
            else
                v = objPriv->curGeoVert + v;

            glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]);

            if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x);
            if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y);
            if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z);

            while (*pch && !isspace(*pch))
                pch++;

        }
    }
    else if (!bReadTex && bReadNorm)
    {
        for ( i = 0; i < numVert; i++ )
        {
            while (*pch && isspace(*pch))
                pch++;

            sscanf(pch, achFormat, &v, &vn);
            if (v > 0)
                v = v - 1;
            else
                v = objPriv->curGeoVert + v;
            if (vn > 0)
                vn = vn - 1;
            else
                vn = objPriv->curGeoVert + vn;

            glNormal3fv((GLfloat *)&objPriv->pxyzNorm[vn]);
            glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]);

            if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x);
            if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y);
            if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z);

            while (*pch && !isspace(*pch))
                pch++;

        }
    }
    else if (bReadTex && !bReadNorm)
    {
        glNormal3fv((GLfloat *) &faceNorm);

        for ( i = 0; i < numVert; i++ )
        {
            while (*pch && isspace(*pch))
                pch++;

            sscanf(pch, achFormat, &v, &vt);
            if (v > 0)
                v = v - 1;
            else
                v = objPriv->curGeoVert + v;
            if (vt > 0)
                vt = vt - 1;
            else
                vt = objPriv->curGeoVert + vt;

            //!!! ignore texture vertex for now; just use the geometry
            glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]);

            if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x);
            if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y);
            if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z);

            while (*pch && !isspace(*pch))
                pch++;

        }
    }
    else
    {
        for ( i = 0; i < numVert; i++ )
        {
            while (*pch && isspace(*pch))
                pch++;

            sscanf(pch, achFormat, &v, &vt, &vn);
            if (v > 0)
                v = v - 1;
            else
                v = objPriv->curGeoVert + v;
            if (vt > 0)
                vt = vt - 1;
            else
                vt = objPriv->curGeoVert + vt;
            if (vn > 0)
                vn = vn - 1;
            else
                vn = objPriv->curGeoVert + vn;

            //!!! ignore texture vertex for now; just use the geometry
            glNormal3fv((GLfloat *)&objPriv->pxyzNorm[vn]);
            glVertex3fv((GLfloat *)&objPriv->pxyzGeoVert[v]);

            if (fabs(objPriv->pxyzGeoVert[v].x) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].x);
            if (fabs(objPriv->pxyzGeoVert[v].y) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].y);
            if (fabs(objPriv->pxyzGeoVert[v].z) > objPriv->rMax)
                objPriv->rMax = fabs(objPriv->pxyzGeoVert[v].z);

            while (*pch && !isspace(*pch))
                pch++;

        }
    }
    glEnd();
}

STATIC void fakeObjLight(SCENE *scene, GLfloat x, GLfloat y, GLfloat z)
{
    MyXYZ xyz;
    MyRGBA rgb = {1.0f, 1.0f, 1.0f, 1.0f};

    xyz.x = x;
    xyz.y = y;
    xyz.z = z;

    if (scene->Lights.count < (maxLights + 1))
    {
        glNewList(scene->Lights.listBase + scene->Lights.count, GL_COMPILE);

        glLightfv(GL_LIGHT0 + scene->Lights.count, GL_POSITION, (GLfloat *) &xyz);
        glLightfv(GL_LIGHT0 + scene->Lights.count, GL_DIFFUSE, (GLfloat *) &rgb);
        glLightfv(GL_LIGHT0 + scene->Lights.count, GL_SPECULAR, (GLfloat *) &rgb);

        glEndList();

        scene->Lights.count++;
    }
}