////////////////////////////////////////////////////////////
//
//  PARSE.CPP - Environment Data File Parser Class
//
//  Version:    1.03A
//
//  History:    94/08/23 - Version 1.00A release.
//              94/12/16 - Version 1.01A release.
//              95/02/05 - Version 1.02A release.
//              95/07/14 - Added /Ow optimization pragma for
//                         ReadSurface function (compiler
//                         bug workaround).
//              95/07/21 - Version 1.02B release.
//              96/02/14 - Version 1.02C release.
//              96/02/27 - Modified ParseFile to calculate
//                         environment extents.
//              96/04/01 - Version 1.03A release.
//
//  Compilers:  Microsoft Visual C/C++ Professional V1.5
//              Borland C++ Version 4.5
//
//  Author:     Ian Ashdown, P.Eng.
//              byHeart Software Limited
//              620 Ballantree Road
//              West Vancouver, B.C.
//              Canada V7S 1W3
//              Tel. (604) 922-6148
//              Fax. (604) 987-7621
//
//  Copyright 1994-1996 byHeart Software Limited
//
//  The following source code has been derived from:
//
//    Ashdown, I. 1994. Radiosity: A Programmer's
//    Perspective. New York, NY: John Wiley & Sons.
//
//  It may be freely copied, redistributed, and/or modified
//  for personal use ONLY, as long as the copyright notice
//  is included with all source code files.
//
////////////////////////////////////////////////////////////

#include <string.h>
#include "error.h"
#include "parse.h"

// File path separator (MS-DOS specific)
static const char PathSeparator[] = "/";

// Data file keywords
static const char EndFileStr[] = "END_FILE";
static const char CommentStr[] = "COMMENT";

// Parse world file
BOOL Parse::ParseFile( char *fname, char *fpath, Environ
    *pe )
{
  char *pefp;           // Entity file path pointer
  Instance *pinst;      // Instance pointer
  Instance *pinsthd;    // Instance list head pointer

  penv = pe;    // Save environment pointer

  // Delete previous environment (if any)
  penv->DeleteEnv();

  pinst = pinsthd = NULL;

  // Initialize environment statistics
  penv->num_inst = (WORD) 0;
  penv->num_surf = (WORD) 0;
  penv->num_patch = (WORD) 0;
  penv->num_elem = (WORD) 0;
  penv->num_vert = (WORD) 0;

  // Build file path string
  pefp = ent_buff;
  if (*fpath != '\0')
  {
    strcpy(ent_buff, fpath);
    if (fpath[strlen(ent_buff) - 1] != *PathSeparator)
      strcat(ent_buff, PathSeparator);
    pefp += strlen(ent_buff);
  }

  if (ifile.Open(fname) != TRUE)    // Open instance file
  {
    sprintf(msg_buff, "Could not open world file %s",
        fname);
    ReportError(msg_buff);
    return FALSE;
  }

  ReadLine(ifile);      // Read world name

  for ( ; ; )
  {
    ReadLine(ifile);    // Read entity file name

    // Check for end of file
    if (strcmp(line_buff, EndFileStr) == 0)
      break;

    // Build full entity file name
    strcpy(pefp, line_buff);

    // Read entity file
    if ((pinst = ParseEntityFile()) == NULL)
    {
      ifile.Close();
      return FALSE;
    }

    // Read 3-D transformation matrix
    ReadTransform();

    // Transform entity into instance
    TransformInstance(pinst);

    // Link instance to instance list
    pinst->SetNext(pinsthd);
    pinsthd = pinst;
    penv->num_inst++;
  }

  ifile.Close();
  penv->pinsthd = pinsthd;

  penv->CalcExtents();  // Calculate environment extents

  return TRUE;
}

// Parse entity data file
Instance *Parse::ParseEntityFile()
{
  BOOL status;              // Return status
  Instance *pinst;          // Instance pointer
  Surface3 *ps;             // Surface pointer
  Vertex3 *pv;              // Vertex pointer

  pinst = NULL;
  ps = NULL;
  pv = NULL;

  surf_cnt = patch_cnt = elem_cnt = vert_cnt = (WORD) 0;

  // Open entity file
  if (efile.Open(ent_buff) != TRUE)
  {
    sprintf(msg_buff, "Could not open entity file %s",
        ent_buff);
    ReportError(msg_buff);
    return NULL;
  }

  ReadLine(efile);      // Read file description

  pv = ParseVertices();
  ps = ParseSurfaces();
  status = ParsePatches();

  if (status == TRUE)
    status = ParseElements();

  // Delete temporary pointer arrays
  delete [] pv_array;
  delete [] ps_array;
  delete [] pp_array;

  // Create new entity
  if (status == TRUE)
    pinst = new Instance(pv, ps);

  efile.Close();
  return pinst;
}

// Parse vertices
Vertex3 *Parse::ParseVertices()
{
  WORD v_index;             // Vertex pointer array index
  Vertex3 *pv;              // Vertex pointer
  Vertex3 *pvhd;            // Vertex list head ptr

  pv = pvhd = NULL;

  ReadLine(efile);      // Read vertex section header

  // Build vertex linked list
  for ( ; ; )
  {
    // Read vertex vector
    if ((pv = ReadVertex()) == NULL)
      break;

    // Link vertex to vertex list
    pv->SetNext(pvhd);
    pvhd = pv;
    penv->num_vert++;
    vert_cnt++;
  }

  // Build vertex pointer array
  pv = pvhd;
  pv_array = new VertexPtr[vert_cnt];
  v_index = (WORD)(vert_cnt - (WORD) 1);
  while (pv != NULL)
  {
    pv_array[v_index--] = pv;
    pv = pv->GetNext();
  }
  return pvhd;
}

// Parse surfaces
Surface3 *Parse::ParseSurfaces()
{
  WORD s_index;             // Surface pointer array index
  Surface3 *ps;             // Surface pointer
  Surface3 *pshd;           // Surface list head ptr

  ps = pshd = NULL;

  ReadLine(efile);      // Read surface section header

  // Build surface linked list
  for ( ; ; )
  {
    // Read surface identifier
    if ((ps = ReadSurface()) == NULL)
      break;

    // Link surface to surface list
    ps->SetNext(pshd);
    pshd = ps;
    penv->num_surf++;
    surf_cnt++;
  }

  // Build surface pointer array
  ps = pshd;
  ps_array = new SurfacePtr[surf_cnt];
  s_index = (WORD)(surf_cnt - (WORD) 1);
  while (ps != NULL)
  {
    ps_array[s_index--] = ps;
    ps = ps->GetNext();
  }
  return pshd;
}

// NOTE: The Microsoft Visual C++ Version 1.5 compiler
//       generates no code for the statement:
//
//         emit.SetBlueBand(eblue);
//
//       in the following function when the /O2 option
//       (Generate Fast Code) is specified unless aliasing
//       across function calls is assumed.

#pragma optimize( "w", on )

// Read surface identifier
Surface3 *Parse::ReadSurface()
{
  char start[2], end[2];        // Vector separators
  float ered, egreen, eblue;    // Exitance components
  float rred, rgreen, rblue;    // Reflectance components
  Spectra reflect;              // Spectral reflectance
  Spectra emit;                 // Spectral radiant exitance

  ReadLine(efile);      // Read color vector

  if (sscanf(line_buff, "%1s %f %f %f %1s %1s %f %f %f %1s",
      start, &rred, &rgreen, &rblue, end, start, &ered,
      &egreen, &eblue, end) == 10)
  {
    // Set reflectance
    reflect.SetRedBand(rred);
    reflect.SetGreenBand(rgreen);
    reflect.SetBlueBand(rblue);

    // Set initial spectral radiant exitance
    emit.SetRedBand(ered);
    emit.SetGreenBand(egreen);
    emit.SetBlueBand(eblue);

    return new Surface3(reflect, emit);
  }
  else
    return NULL;        // Must be terminator
}

#pragma optimize( "w", off )

// Parse patch identifiers
BOOL Parse::ParsePatches()
{
  char start[2], end[2];    // List separators
  int v_index;              // Vertex pointer array index
  BOOL status = TRUE;       // Status flag
  WORD p_index;             // Patch pointer array index
  WORD s_index;             // Surface index
  WORD ivtx[4];             // Vertex indices
  Patch3 *pp;               // Patch pointer
  Patch3 *pphd;             // Patch head pointer
  PatchList *ppl = NULL;    // Patch list pointer
  PatchList *pplnext;       // Next patch list pointer
  Vertex3 *pv[4];           // Vertex pointers array

  ReadLine(efile);      // Read patch section header

  for ( ; ; )


  {
    ReadLine(efile);    // Read patch identifier

    if (sscanf(line_buff, "%hd %1s %hd %hd %hd %hd %1s",
        &s_index, start, &ivtx[0], &ivtx[1], &ivtx[2],
        &ivtx[3], end) == 7)
    {
      // Validate surface index
      if (s_index >= surf_cnt)
      {
        sprintf(msg_buff,
            "Entity file: %s\nPatch # %u\nSurface index "
            "error: %s", ent_buff, patch_cnt + 1,
            line_buff);
        ReportError(msg_buff);
        status = FALSE;
        break;
      }

      // Validate vertex array indices
      for (v_index = 0; v_index < 4; v_index++)
      {
        if (ivtx[v_index] >= vert_cnt)
        {
          sprintf(msg_buff,
              "Entity file: %s\nPatch # %u\nVertex index "
              "error: %s", ent_buff, patch_cnt + 1,
              line_buff);
          ReportError(msg_buff);
          status = FALSE;
          break;
        }
      }

      if (status == FALSE)
        break;

      // Get vertex pointers
      for (v_index = 0; v_index < 4; v_index++)
        pv[v_index] = pv_array[ivtx[v_index]];

      // Update surface patch linked list
      pp = new Patch3(pv, ps_array[s_index]);
      pphd = ps_array[s_index]->GetPatchPtr();
      pp->SetNext(pphd);
      ps_array[s_index]->SetPatchPtr(pp);

      // Determine whether triangle or quadrilateral
      if (ivtx[2] != ivtx[3])
        pp->SetQuad();
    }
    else
      break;

    // Link patch to temporary patch list
    ppl = new PatchList(pp, ppl);
    penv->num_patch++;
    patch_cnt++;
  }

  // Build patch pointer array and delete patch list
  pp_array = new PatchPtr[patch_cnt];
  p_index = (WORD)(patch_cnt - (WORD) 1);
  while (ppl != NULL)
  {
    pp_array[p_index--] = ppl->GetPatchPtr();
    pplnext = ppl->GetNext();
    delete ppl;
    ppl = pplnext;
  }

  return status;
}

// Parse element identifiers
BOOL Parse::ParseElements()
{
  char start[2], end[2];    // List separators
  int nvert;                // Number of vertices
  int v_index;              // Vertex pointer array index
  BOOL status = TRUE;       // Status flag
  WORD p_index;             // Patch array index
  WORD ivtx[4];             // Vertex indices
  Element3 *pe;             // Element pointer
  Element3 *pehd;           // Element head pointer
  ElemList *pel;            // Element list pointer
  ElemList *pelhd;          // Element list head pointer
  Vertex3 *pv[4];           // Vertex pointers array

  ReadLine(efile);      // Read element section header

  for ( ; ; )
  {
    ReadLine(efile);    // Read element identifier

    if (sscanf(line_buff, "%hd %1s %hd %hd %hd %hd %1s",
        &p_index, start, &ivtx[0], &ivtx[1], &ivtx[2],
        &ivtx[3], end) == 7)
    {
      // Validate patch index
      if (p_index >= patch_cnt)
      {
        sprintf(msg_buff,
            "Entity file: %s\nElement # %u\nPatch index "
            "error: %s", ent_buff, elem_cnt + 1,
            line_buff);
        ReportError(msg_buff);
        status = FALSE;
        break;
      }

      // Validate vertex array indices
      for (v_index = 0; v_index < 4; v_index++)
      {
        if (ivtx[v_index] >= vert_cnt)
        {
          sprintf(msg_buff,
              "Entity file: %s\nElement # %u\nVertex index "
              "error: %s", ent_buff, elem_cnt + 1,
              line_buff);
          ReportError(msg_buff);
          status = FALSE;
          break;
        }
      }

      if (status == FALSE)
        break;

      // Get vertex pointers
      for (v_index = 0; v_index < 4; v_index++)
        pv[v_index] = pv_array[ivtx[v_index]];

      // Update patch element linked list
      pe = new Element3(pv, pp_array[p_index]);
      pehd = pp_array[p_index]->GetElementPtr();
      pe->SetNext(pehd);
      pp_array[p_index]->SetElementPtr(pe);
      penv->num_elem++;
      elem_cnt++;

      // Determine whether triangle or quadrilateral
      if (ivtx[2] != ivtx[3])
      {
        nvert = 4;
        pe->SetQuad();
      }
      else
        nvert = 3;

      for (v_index = 0; v_index < nvert; v_index++)
      {
        // Update vertex element linked list
        pelhd = pv[v_index]->GetElemListPtr();
        pel = new ElemList(pe, pelhd);
        pv[v_index]->SetElemListPtr(pel);
      }
    }
    else
      break;
  }

  return status;
}

void Parse::ReadTransform()
{
  double sx, sy, sz;    // Scaling parameters
  double rx, ry, rz;    // Rotation parameters
  double tx, ty, tz;    // Translation parameters

  // Read transformation vectors
  ReadVector(ifile, &sx, &sy, &sz);
  ReadVector(ifile, &rx, &ry, &rz);
  ReadVector(ifile, &tx, &ty, &tz);

  // Convert rotation angles to radians
  rx = DegToRad(rx);
  ry = DegToRad(ry);
  rz = DegToRad(rz);

  // Calculate vertex transformation matrix
  tm.SetScale(sx, sy, sz);
  tm.SetRotation(rx, ry, rz);
  tm.SetTranslation(tx, ty, tz);
  tm.BuildTransform();
}

// Read vertex identifier
Vertex3 *Parse::ReadVertex()
{
  double xval, yval, zval;      // Vertex coordinates

  if ((ReadVector( efile, &xval, &yval, &zval)) == TRUE)
  {
    Point3 *p3;
    p3 = new Point3(xval,yval,zval);
    return new Vertex3(*p3);
  }
  else
    return NULL;
}

// Read vector
BOOL Parse::ReadVector( WinText &file, double *px, double
    *py, double *pz )
{
  float x, y, z;                // Temporary variables
  char start[2], end[2];        // Data separators

  ReadLine(file);       // Read vector

  if (sscanf(line_buff, "%1s %f %f %f %1s", start, &x, &y,
      &z, end) == 5)
  {
    *px = x;
    *py = y;
    *pz = z;

    return TRUE;
  }
  else
    return FALSE;
}

// Transform entity into instance
void Parse::TransformInstance( Instance *pinst )
{
  Element3 *pe;         // Element pointer
  Patch3 *pp;           // Patch pointer
  Surface3 *ps;         // Surface pointer
  Vertex3 *pv;          // Vertex pointer

  // Transform vertex co-ordinates
  pv = pinst->GetVertPtr();
  while (pv != NULL)
  {
    tm.Transform(pv->GetPosnPtr());
    pv = pv->GetNext();
  }

  // Calculate patch attributes
  ps = pinst->GetSurfPtr();
  while (ps != NULL)
  {
    pp = ps->GetPatchPtr();
    while (pp != NULL)
    {
      // Calculate element attributes
      pe = pp->GetElementPtr();
      while (pe != NULL)
      {
        pe->CalcArea();
        pe->CalcNormal();
        pe = pe->GetNext();
      }

      pp->CalcArea();
      pp->CalcCenter();
      pp->CalcNormal();
      pp = pp->GetNext();
    }
    ps = ps->GetNext();
  }

  // Calculate vertex normals
  pv = pinst->GetVertPtr();
  while (pv != NULL)
  {
    pv->CalcNormal();
    pv = pv->GetNext();
  }
}

// Read next line from file
void Parse::ReadLine( WinText &file )
{
  for ( ; ; )
  {
    file.GetLine(line_buff, MaxLine);

    // Skip comment lines
    if (strncmp(line_buff, CommentStr, strlen(CommentStr))
        != 0)
      break;
  }
}

