/*======================================================================================*/
/* 3DLib file I/O module for Wavefront's OBJ files					*/
/*											*/
/* AUTHOR:	Gabor Nagy								*/
/* DATE:	1996-Dec-24 23:54:47							*/
/*											*/
/* 3DPanel(TM) and 3DLib(TM) Copyright (C) 1995 By Gabor Nagy. All rights reserved.	*/
/*======================================================================================*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include <EParam.h>
#include <EMalloc.h>
#include <EStrings.h>

#include <Color/Color.h>

#include <E3D/E3D.h>
#include <E3D/IO.h>
#include <E3D/Material.h>
#include <E3D/Mesh.h>
#include <E3D/Scene.h>


static void*	E3d_FileFormat=NULL;

static char*	E3d_FileNameExtensions[]=
{
 ".obj", ".OBJ", NULL
};

typedef struct
{
 char*		Name;
 E3dMaterial*	Material;
 E3dPolygon*	Polygons;
 unsigned int	NumOfPolygons, NumOfPolygonsAllocated;
} E3dOBJPolyGroup;



// Vertex record components
//
#define OBJV_v		0x01
#define OBJV_t		0x02
#define OBJV_n		0x04
#define OBJV_t_slash	0x08	// got a '/' after the ST index, format:  "v/t/", as opposed to "v/t"



#define	ObjMAX_LINE_LENGTH		8192


#define	ObjOK					0
#define	ObjBAD_FACE_VERTEX			-1
#define ObjCOULD_NOT_ALLOCATE_LINEBUFFER	-2


static unsigned char*	_Buffer=NULL;



//========================================
// Check a file if it is an OBJ file
//========================================
EBool E3d_ModelIsOBJFile(char* LFileName, FILE* LFile)
{
 int	LLen;

 LLen=strlen(LFileName);

 if(EStr_StringsEqual(LFileName+LLen-4, ".obj")) return(TRUE);
 if(EStr_StringsEqual(LFileName+LLen-4, ".OBJ")) return(TRUE);

 return(FALSE);
}


//========================================
// Read Material(s) from a ".mtl" file
//========================================
static E3dMaterial** _MaterialReadMTL(char* LPathName, char* LFileName, int* LNumOfMaterialsPtr)
{
 char		LTmpStr[MAXPATHLEN+1];
 char		LTextureName[MAXPATHLEN+1];

 FILE*		LInFile;
 E3dMaterial*	LMaterial;
 E3dMaterial**	LMaterials;
 E3dMaterial**	LMaterialsT;
 E3d2DTexture*	L2DTexture;
 int		LNumOfMaterials;
 float		LRF, LGF, LBF;


 sprintf(LTmpStr, "%s%s", LPathName, LFileName);
//printf("[%s]\n", LTmpStr);fflush(stdout);
 if((LInFile = fopen(LTmpStr, "r"))!=NULL)
 {
// Count the number of materials in the file
//
  LNumOfMaterials = 0;
  LMaterials = NULL;
  LMaterial = NULL;

  while(fscanf(LInFile, "%s", _Buffer) != EOF)
  {
   switch(_Buffer[0])
   {
    case '#':							// Comment
     fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LInFile);	// Skip the rest of the line
    break;

    case 'n':							// "newmtl"
     fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LInFile);

     sscanf((char*)_Buffer, "%s %s", _Buffer, _Buffer);

     if(LMaterials==NULL) LMaterials=(E3dMaterial**)EMalloc(sizeof(E3dMaterial*));
     else
     {
      if((LMaterialsT=(E3dMaterial**)ERealloc(LMaterials, sizeof(E3dMaterial*)*(LNumOfMaterials+1)))!=NULL)
      {
       LMaterials=LMaterialsT;
      }
      else { EFree(LMaterials);return(NULL); }
     }

     if((LMaterial=E3d_MaterialAllocate())!=NULL)
     {
      
      LMaterials[LNumOfMaterials]=LMaterial;
      LMaterial->Name=strdup((char*)_Buffer);
      LMaterial->Type=E3dMAT_LAMBERT;
/*
      LMaterial->Specular.R=0.0;
      LMaterial->Specular.G=0.0;
      LMaterial->Specular.B=0.0;
*/
      LNumOfMaterials++;
     }
    break;

    case 'K':
     switch(_Buffer[1])
     {
      case 'a':			// Ambient color
       fscanf(LInFile, "%f %f %f", &LRF, &LGF, &LBF);
       LMaterial->Ambient.R=LRF;
       LMaterial->Ambient.G=LGF;
       LMaterial->Ambient.B=LBF;
      break;

      case 'd':			// Diffuse color
       fscanf(LInFile, "%f %f %f", &LRF, &LGF, &LBF);
       LMaterial->Diffuse.R=LRF;
       LMaterial->Diffuse.G=LGF;
       LMaterial->Diffuse.B=LBF;
      break;

      case 's':			// Specular color
       fscanf(LInFile, "%f %f %f", &LRF, &LGF, &LBF);
       LMaterial->Specular.R=LRF;
       LMaterial->Specular.G=LGF;
       LMaterial->Specular.B=LBF;
       LMaterial->Type=E3dMAT_PHONG;
      break;

      default:
       fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LInFile);		// Skip the rest of the line
      break;
     }
    break;

    case 'N':
     switch(_Buffer[1])
     {
      case 's':							// Specular exponent
       if(LMaterial)
       {
        fscanf(LInFile, "%f", &LRF);
// Wavefront shininess is from [0, 1000], so scale for OpenGL
//
        LRF /= 1000.0;LRF*= 128.0;
        LMaterial->Specularity = LRF;
        LMaterial->Type=E3dMAT_PHONG;
       }
      break;

// FIXME
// I just made these up!
//
      case 'r':							// Reflectivity
       if(LMaterial)
       {
        fscanf(LInFile, "%f", &LRF);
        LMaterial->Reflectivity = LRF;
       }
      break;

      case 't':							// Transparency
       if(LMaterial)
       {
        fscanf(LInFile, "%f", &LRF);
        LMaterial->Transparency = LRF;
       }
      break;
     }
    break;

    case 'm':
     if(LMaterial)
     {
// Texture mapped on Diffuse color
//
      if(EStr_StringsEqual((char*)_Buffer, "map_Kd"))
      {
       if((LMaterial->Textures2D=(E3d2DTexture**)EMalloc(sizeof(E3d2DTexture*)))!=NULL)
       {
        if((L2DTexture=E3d_2DTextureAllocate())!=NULL)
	{
	 L2DTexture->MappingMethod=E3dTXT_FREEHAND;
	 L2DTexture->SCount=2;
	 L2DTexture->TCount=2;

	 LMaterial->Textures2D[0]=L2DTexture;L2DTexture->RefCnt+=1;

         fscanf(LInFile, "%s", LTextureName);

// If the texture file name has no absolute path, add the path name to it
//
/*
         if(LTextureName[0]!='/') { sprintf(LTmpStr, "%s%s", LPathName, LTextureName);L2DTexture->FileName=strdup(LTmpStr); }
         else L2DTexture->FileName=EStrDup(LTextureName);
*/
         L2DTexture->FileName=EStrDup(LTextureName);

	 LMaterial->NumOf2DTextures=1;
	}
       }
      }
     }
    break;

    default:
     fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LInFile);			// Skip the rest of the line
    break;
   }
  }

  if(LNumOfMaterialsPtr) *LNumOfMaterialsPtr=LNumOfMaterials;
  fclose(LInFile);
  return(LMaterials);
 }
 return(NULL);
}


//========================================================
// Add a polygon group to an E3dOBJPolyGroup array
//========================================================
static void _MaterialSaveMTL(char* LFileName, E3dMaterial** LMaterials, unsigned long LNumOfMaterials)
{
 unsigned int	LC;
 FILE*		LOutFile;
 E3dMaterial*	LMaterial;
 E3d2DTexture*	L2DTexture;

 if((LOutFile=fopen(LFileName, "w"))!=NULL)
 {
  fprintf(LOutFile, "#====================================================\n");
  fprintf(LOutFile, "# Wavefront MTL file generated by EQUINOX-3D\n");
  fprintf(LOutFile, "#\n");
  fprintf(LOutFile, "# http://www.equinox3d.com/\n");
  fprintf(LOutFile, "#====================================================\n\n");

  for(LC=0;LC<LNumOfMaterials;LC++)
  {
   LMaterial=LMaterials[LC];

   if(LMaterial->Name)
   {
    fprintf(LOutFile, "newmtl %s\n", LMaterial->Name);
    fprintf(LOutFile, "\tKa %f %f %f\n", LMaterial->Ambient.R, LMaterial->Ambient.G, LMaterial->Ambient.B);
    fprintf(LOutFile, "\tKd %f %f %f\n", LMaterial->Diffuse.R, LMaterial->Diffuse.G, LMaterial->Diffuse.B);

    switch(LMaterial->Type)
    {
     case E3dMAT_PHONG:
     case E3dMAT_BLINN:
      fprintf(LOutFile, "\tKs %f %f %f\n", LMaterial->Specular.R, LMaterial->Specular.G, LMaterial->Specular.B);

// Wavefront shininess is from [0, 1000]
//
      fprintf(LOutFile, "\tNs %f\n", LMaterial->Specularity*1000.0/128.0);
     break;
    }

// FIXME
// I just made this up!
//
    if(LMaterial->Reflectivity!=0.0) fprintf(LOutFile, "\tNr %f\n", LMaterial->Reflectivity);
    if(LMaterial->Transparency!=0.0) fprintf(LOutFile, "\tNt %f\n", LMaterial->Transparency);

    if(LMaterial->NumOf2DTextures>0)
    {
     L2DTexture=LMaterial->Textures2D[0];
     fprintf(LOutFile, "\tmap_Kd %s\n", L2DTexture->FileName);
    }
   }

   if(LC<(LNumOfMaterials-1)) fprintf(LOutFile, "\n");
  }

  fclose(LOutFile);
 }
}


//========================================================
// Add a polygon group to an E3dOBJPolyGroup array
//========================================================
static E3dOBJPolyGroup* _OBJPolyGroupAdd(E3dOBJPolyGroup* LOBJPolyGroups, unsigned int LNumOfPolyGroups, char* LGroupName)
{
 E3dOBJPolyGroup*	LOBJPolyGroupsT;

 if(LOBJPolyGroups==NULL)
 {
  LOBJPolyGroups=(E3dOBJPolyGroup*)EMalloc(sizeof(E3dOBJPolyGroup));
 }
 else
 {
  if((LOBJPolyGroupsT=(E3dOBJPolyGroup*)ERealloc(LOBJPolyGroups, sizeof(E3dOBJPolyGroup)*(LNumOfPolyGroups+1)))!=NULL)
  {
   LOBJPolyGroups=LOBJPolyGroupsT;
  }
 }
 if(LOBJPolyGroups)
 {
  LOBJPolyGroups[LNumOfPolyGroups].Name=EStrDup(LGroupName);
  LOBJPolyGroups[LNumOfPolyGroups].Material=NULL;
  LOBJPolyGroups[LNumOfPolyGroups].Polygons=NULL;
  LOBJPolyGroups[LNumOfPolyGroups].NumOfPolygons=0;
  LOBJPolyGroups[LNumOfPolyGroups].NumOfPolygonsAllocated=0;
 }
 return(LOBJPolyGroups);
}


//========================================================================
// Read a line from a text file and return the length of that line
// This function does a simple ring-buffering.
//========================================================================
static int _FGetLine(FILE* LFile, char* LBuffer, int* LStartOffsetPtr, int* LBytesInBufferPtr, char** LBufferRet)
{
 int		LC, LBytesInBuffer=*LBytesInBufferPtr;
 char*		LPtr;

 if(LBytesInBuffer==0)
 {
  if((LBytesInBuffer=fread(LBuffer, 1, ObjMAX_LINE_LENGTH, LFile))<=0) return(-1);
  *LStartOffsetPtr=0;
 }

//printf("Read %d start %d\n", LBytesInBuffer, *LStartOffsetPtr);fflush(stdout);

 LPtr=LBuffer+(*LStartOffsetPtr);
 for(LC=0;LC<LBytesInBuffer;LC++)
 {
//printf("%c", LPtr[LC]);fflush(stdout);
  switch(LPtr[LC])
  {
   case '\n':
   case '\0':
//assert(((*LStartOffsetPtr)+(LC+1))<=ObjMAX_LINE_LENGTH);

    (*LStartOffsetPtr)+=(LC+1);
    LBytesInBuffer-=(LC+1);
    *LBytesInBufferPtr=LBytesInBuffer;

// Check for that stupid "Bill Gates end of line"... (0x0D, 0x0A)
//
    if(LC) { if(LPtr[LC-1]==13) LC--; }
    LPtr[LC]=0;

//printf("Ret [%s] %d\n", LPtr, LC);fflush(stdout);

    *LBufferRet=LPtr;

   return(LC);
  }
 }

//printf("Len: %d SO %d %s\n", LC, *LStartOffsetPtr, LPtr);fflush(stdout);

// No end of line was found, try again
//
 if((*LStartOffsetPtr)>0)
 {
  int	LNewBytesInBuffer;

  memmove(LBuffer, LPtr, LBytesInBuffer);
  *LStartOffsetPtr=0;
  if((LNewBytesInBuffer=fread(LBuffer+LBytesInBuffer, 1, ObjMAX_LINE_LENGTH-LBytesInBuffer, LFile))<=0) return(-1);


  LPtr=LBuffer;

  LC=LBytesInBuffer;
  LBytesInBuffer+=LNewBytesInBuffer;

  for(;LC<LBytesInBuffer;LC++)
  {
   switch(LPtr[LC])
   {
    case '\n':
    case '\0':
     (*LStartOffsetPtr)+=(LC+1);
     LBytesInBuffer-=(LC+1);

// Check for that stupid "Bill Gates end of line"... (0x0D, 0x0A)
//
     if(LC) { if(LPtr[LC-1]==13) LC--; }
     LPtr[LC]=0;

     *LBufferRet=LBuffer;
     *LBytesInBufferPtr=LBytesInBuffer;

    return(LC);
   }
  }
 }

 *LBytesInBufferPtr=LBytesInBuffer;

 return(-1);
}



//================================================================
// Determine vertex format and read the first Vertex record
//================================================================
static int _VertexFormat(char* LPtr, int* LLenRet, int* LVRet, int* LSTRet, int* LNRet)
{
 int	LNum=0, LC=0, LDigitC=0, LNumC=0, LSlashC=0;
 int	LGot=0;
 int*	LNumPtr=LVRet;

 for(;LC<24;LC++)
 {
  switch(LPtr[LC])
  {
   case '/':
    switch(LNumC)
    {
     case 0:	return(0);						// Can't start with a '/'
     case 2:	if(LSlashC==1) { LGot|=OBJV_t;LGot|=OBJV_t_slash;*LSTRet=LNum;} break;	// If we had one '/' before this one and we got 2 numbers so far, the last number was a texture-coordinate index
    }
    LSlashC++;LDigitC=0;
    if(LSlashC>2) return(0);
   break;

   case '0':
   case '1':
   case '2':
   case '3':
   case '4':
   case '5':
   case '6':
   case '7':
   case '8':
   case '9':
    if(LDigitC==0)
    {
     switch(LNumC)
     {
      case 0:	LGot|=OBJV_v;break;				// The first number is the Vertex index
      case 1:	if(LSlashC==1) LGot|=OBJV_t;break;		// If we've only had one '/',  the second number is the ST index
     }

     if(LSlashC==2) { LGot|=OBJV_n;LNumPtr=LNRet; }	// If we had two '/'s already, this number is a normal index

     LNumC++;
     LNum=(int)(LPtr[LC]-'0');
    }
    else LNum=LNum*10+(int)(LPtr[LC]-'0');

    *LNumPtr=LNum;

    LDigitC++;
   break;

// Any character other than numbers and '/' will end the vertex record
//
   default:
    if(LNumC)
    {
     *LLenRet=LC+1;


/*
{
 char	LStr[32];

memcpy(LStr, LPtr, 31);LStr[31]=0;
printf("VtxFormat %08x %s\n", LGot, LPtr);fflush(stdout);
}
*/
     return(LGot);
    }
   break;
  }
 }

 *LLenRet=LC;
 return(0);
}


//========================================================
// Read face with only Vertex indices
//========================================================
static int _GetFaceV(FILE* LFile, E3dOBJPolyGroup* LOBJPolyGroup, char* LPtr, int LNumOfVertices)
{
 E3dVertexNode*		LVertexNode;
 E3dPolygon*		LPolygon;
 char*			LTPtr;
 int			LVtxId, LLen,
			LVI;
 unsigned int		LC, LN;


 sscanf(LPtr, "%d%n", &LVtxId, &LLen);

 if((LVtxId<1)||(LVtxId>LNumOfVertices))
 {
  printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVtxId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }

 LPtr+=LLen;
 LN=1;

// Count VertexNodes and check Vertex indices
//
 LTPtr=LPtr;
 while(sscanf(LPtr, "%d%n", &LVI, &LLen)>0)
 {
  if((LVI<1)||(LVI>LNumOfVertices))
  {
   printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   return(-1);
  }
  LPtr+=(LLen+1);LN++;
 }
 LPtr=LTPtr;

 if(LN>2)
 {
  if((LOBJPolyGroup->Polygons=E3d_PolygonAddOneToArray(LOBJPolyGroup->Polygons, LOBJPolyGroup->NumOfPolygons, &(LOBJPolyGroup->NumOfPolygonsAllocated)))!=NULL)
  {
   LPolygon=LOBJPolyGroup->Polygons+LOBJPolyGroup->NumOfPolygons;
   LOBJPolyGroup->NumOfPolygons+=1;

   LVertexNode=E3d_PolygonVertexNodesAllocate(LPolygon, LN);

   LVertexNode->VertexID=LVtxId-1;
   LVertexNode++;

   for(LC=1;LC<LN;LC++)
   {
    sscanf(LPtr, "%d%n", &LVtxId, &LLen);

    LPtr+=(LLen+1);

    LVertexNode->VertexID=LVtxId-1;

    LVertexNode++;
   }

   LPolygon->NumOfVertices=LN;
   LPolygon->NumOfExteriorVertices=LN;
  }
 }

 return(0);
}



//========================================================
// Read face with Vertex and ST indices
//========================================================
static int _GetFaceVT(unsigned int LVertexFormat, FILE* LFile, E3dOBJPolyGroup* LOBJPolyGroup, char* LPtr, int LNumOfVertices, E3dST* LSTs, int LNumOfSTs)
{
 E3dVertexNode*		LVertexNode;
 E3dPolygon*		LPolygon;
 E3dST*			LSTsT;
 char*			LTPtr;
 int			LVtxId, LSTId, LLen,
			LVI, LSTI;
 unsigned int		LC, LN;


 if(LVertexFormat&OBJV_t_slash) sscanf(LPtr, "%d/%d/%n", &LVtxId, &LSTId, &LLen);
 else sscanf(LPtr, "%d/%d/%n", &LVtxId, &LSTId, &LLen);

 if((LVtxId<1)||(LVtxId>LNumOfVertices))
 {
  printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVtxId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }
 if((LSTId<1)||(LSTId>LNumOfSTs))
 {
  printf("OBJ read: Bad ST index (%d of %d) in face\n", LSTId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }

 LPtr+=LLen;
 LN=1;

// Count Vertex nodes and check Vertex and ST indices
//
 LTPtr=LPtr;
 if(LVertexFormat&OBJV_t_slash)
 {
  while(sscanf(LPtr, "%d/%d/%n", &LVI, &LSTI, &LLen)>=2)
  {
   if((LVI<1)||(LVI>LNumOfVertices))
   {
    printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVI, LNumOfVertices);fflush(stdout);
    fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
    LN=0;
    break;
   }
   if((LSTI<1)||(LSTI>LNumOfSTs))
   {
    printf("OBJ read: Bad ST index (%d of %d) in face\n", LSTI, LNumOfVertices);fflush(stdout);
    fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
    LN=0;
    break;
   }
   LPtr+=(LLen+1);LN++;
  }
 }
 else
 {
  while(sscanf(LPtr, "%d/%d%n", &LVI, &LSTI, &LLen)>=2)
  {
   if((LVI<1)||(LVI>LNumOfVertices))
   {
    printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVI, LNumOfVertices);fflush(stdout);
    fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
    LN=0;
    break;
   }
   if((LSTI<1)||(LSTI>LNumOfSTs))
   {
    printf("OBJ read: Bad ST index (%d of %d) in face\n", LSTI, LNumOfVertices);fflush(stdout);
    fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
    LN=0;
    break;
   }
   LPtr+=(LLen+1);LN++;
  }
 }
 LPtr=LTPtr;

 if(LN>2)
 {
  if((LOBJPolyGroup->Polygons=E3d_PolygonAddOneToArray(LOBJPolyGroup->Polygons, LOBJPolyGroup->NumOfPolygons, &(LOBJPolyGroup->NumOfPolygonsAllocated)))!=NULL)
  {
   LPolygon=LOBJPolyGroup->Polygons+LOBJPolyGroup->NumOfPolygons;
   LOBJPolyGroup->NumOfPolygons+=1;

   LVertexNode=E3d_PolygonVertexNodesAllocate(LPolygon, LN);

   LVertexNode->VertexID=LVtxId-1;

// Set ST of Vertex0
//
   if(LSTId<=LNumOfSTs)
   {
    LSTsT=LSTs+LSTId-1;
    LVertexNode->S=LSTsT->S;LVertexNode->T=LSTsT->T;
   }


   LVertexNode++;

   for(LC=1;LC<LN;LC++)
   {
    if(LVertexFormat&OBJV_t_slash) sscanf(LPtr, "%d/%d/%n", &LVtxId, &LSTId, &LLen);
    else sscanf(LPtr, "%d/%d%n", &LVtxId, &LSTId, &LLen);
    LPtr+=(LLen+1);

    LVertexNode->VertexID=LVtxId-1;

// Set vertex ST
//
    if(LSTId<=LNumOfSTs)
    {
     LSTsT=LSTs+LSTId-1;
     LVertexNode->S=LSTsT->S;LVertexNode->T=LSTsT->T;
    }

    LVertexNode++;
   }

   LPolygon->NumOfVertices=LN;
   LPolygon->NumOfExteriorVertices=LN;
  }
 }

 return(0);
}


//========================================================
// Read face with Vertex, ST and Normal indices
//========================================================
static int _GetFaceVTN(FILE* LFile, E3dOBJPolyGroup* LOBJPolyGroup, char* LPtr, int LNumOfVertices, E3dST* LSTs, int LNumOfSTs, E3dVector* LNormals, int LNumOfNormals)
{
 E3dVertexNode*		LVertexNode;
 E3dPolygon*		LPolygon;
 E3dST*			LSTsT;
 E3dVector*		LNormalsT;
 char*			LTPtr;
 int			LVtxId, LSTId, LNormalId, LLen,
			LVI, LSTI, LNI;
 unsigned int		LC, LN;


 sscanf(LPtr, "%d/%d/%d%n", &LVtxId, &LSTId, &LNormalId, &LLen);

 if((LVtxId<1)||(LVtxId>LNumOfVertices))
 {
  printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVtxId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }
 if((LSTId<1)||(LSTId>LNumOfSTs))
 {
  printf("OBJ read: Bad ST index (%d of %d) in face\n", LSTId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }
 if((LNormalId<1)||(LNormalId>LNumOfNormals))
 {
  printf("OBJ read: Bad Normal index (%d of %d) in face\n", LNormalId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }

 LPtr+=LLen;
 LN=1;

// Count Vertex nodes and check Vertex, ST and Normal indices
//
 LTPtr=LPtr;
 while(sscanf(LPtr, "%d/%d/%d%n", &LVI, &LSTI, &LNI, &LLen)>=3)
 {
  if((LVI<1)||(LVI>LNumOfVertices))
  {
   printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   LN=0;
   return(-1);
  }
  if((LSTI<1)||(LSTI>LNumOfSTs))
  {
   printf("OBJ read: Bad ST index (%d of %d) in face\n", LSTI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   LN=0;
   return(-1);
  }
  if((LNI<1)||(LNI>LNumOfNormals))
  {
   printf("OBJ read: Bad Normal index (%d of %d) in face\n", LNI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   LN=0;
   return(-1);
  }

  LPtr+=LLen;LN++;
 }
 LPtr=LTPtr;

 if(LN>2)
 {
  if((LOBJPolyGroup->Polygons=E3d_PolygonAddOneToArray(LOBJPolyGroup->Polygons, LOBJPolyGroup->NumOfPolygons, &(LOBJPolyGroup->NumOfPolygonsAllocated)))!=NULL)
  {
   LPolygon=LOBJPolyGroup->Polygons+LOBJPolyGroup->NumOfPolygons;
   LOBJPolyGroup->NumOfPolygons+=1;

   LVertexNode=E3d_PolygonVertexNodesAllocate(LPolygon, LN);

   LVertexNode->VertexID=LVtxId-1;

// Set ST of Vertex0
//
   if(LSTId<=LNumOfSTs)
   {
    LSTsT=LSTs+LSTId-1;
    LVertexNode->S=LSTsT->S;LVertexNode->T=LSTsT->T;
   }

// Set normal of Vertex0
//

   if(LNormalId<=LNumOfNormals)
   {
    LNormalsT=LNormals+LNormalId-1;

    LVertexNode->Normal.X=LNormalsT->X;LVertexNode->Normal.Y=LNormalsT->Y;LVertexNode->Normal.Z=LNormalsT->Z;
   }

   LVertexNode++;

   for(LC=1;LC<LN;LC++)
   {
    sscanf(LPtr, "%d/%d/%d%n", &LVtxId, &LSTId, &LNormalId, &LLen);
    LPtr+=(LLen+1);

    LVertexNode->VertexID=LVtxId-1;

// Set Vertex ST
//
    if(LSTId<=LNumOfSTs)
    {
     LSTsT=LSTs+LSTId-1;
     LVertexNode->S=LSTsT->S;LVertexNode->T=LSTsT->T;
    }

// Set Vertex normal
//
    if(LNormalId<=LNumOfNormals)
    {
     LNormalsT=LNormals+LNormalId-1;

     LVertexNode->Normal.X=LNormalsT->X;LVertexNode->Normal.Y=LNormalsT->Y;LVertexNode->Normal.Z=LNormalsT->Z;
    }

    LVertexNode++;
   }

   LPolygon->NumOfVertices=LN;
   LPolygon->NumOfExteriorVertices=LN;
  }
 }

 return(0);
}


//========================================================
// Read face with Vertex and Normal indices
//========================================================
static int _GetFaceVN(FILE* LFile, E3dOBJPolyGroup* LOBJPolyGroup, char* LPtr, int LNumOfVertices, E3dVector* LNormals, int LNumOfNormals)
{
 E3dVertexNode*		LVertexNode;
 E3dPolygon*		LPolygon;
 char*			LTPtr;
 int			LVtxId, LNormalId, LLen,
			LVI, LNI;
 unsigned int		LC, LN;


 sscanf(LPtr, "%d//%d%n", &LVtxId, &LNormalId, &LLen);

 if((LVtxId<1)||(LVtxId>LNumOfVertices))
 {
  printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVtxId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }
 if((LNormalId<1)||(LNormalId>LNumOfNormals))
 {
  printf("OBJ read: Bad Normal index (%d of %d) in face\n", LNormalId, LNumOfVertices);fflush(stdout);
  fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
  return(-1);
 }

 LPtr+=LLen;
 LN=1;

// Count Vertex nodes and check Vertex and Normal indices
//
 LTPtr=LPtr;
 while(sscanf(LPtr, "%d//%d%n", &LVI, &LNI, &LLen)>=2)
 {
  if((LVI<1)||(LVI>LNumOfVertices))
  {
   printf("OBJ read: Bad Vertex index (%d of %d) in face\n", LVI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   LN=0;
   return(-1);
  }
  if((LNI<1)||(LNI>LNumOfNormals))
  {
   printf("OBJ read: Bad Normal index (%d of %d) in face\n", LNI, LNumOfVertices);fflush(stdout);
   fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile);		// Skip the rest of the line
   LN=0;
   return(-1);
  }

  LPtr+=(LLen+1);LN++;
 }

 LPtr=LTPtr;


 if(LN>2)
 {
  if((LOBJPolyGroup->Polygons=E3d_PolygonAddOneToArray(LOBJPolyGroup->Polygons, LOBJPolyGroup->NumOfPolygons, &(LOBJPolyGroup->NumOfPolygonsAllocated)))!=NULL)
  {
   LPolygon=LOBJPolyGroup->Polygons+LOBJPolyGroup->NumOfPolygons;
   LOBJPolyGroup->NumOfPolygons+=1;

   LVertexNode=E3d_PolygonVertexNodesAllocate(LPolygon, LN);

   LVertexNode->VertexID=LVtxId-1;

// Set Vertex normal
//
   if(LNormalId<=LNumOfNormals)
   {
    E3dVector*	LNormalsT=LNormals+LNormalId-1;

    LNormalsT=LNormals+LNormalId-1;
    LVertexNode->Normal.X=LNormalsT->X;LVertexNode->Normal.Y=LNormalsT->Y;LVertexNode->Normal.Z=LNormalsT->Z;
   }

   LVertexNode++;

   for(LC=1;LC<LN;LC++)
   {
    sscanf(LPtr, "%d//%d%n", &LVtxId, &LNormalId, &LLen);
    LPtr+=(LLen+1);

    LVertexNode->VertexID=LVtxId-1;
// Set Vertex normal
//
    if(LNormalId<=LNumOfNormals)
    {
     E3dVector*	LNormalsT=LNormals+LNormalId-1;

     LNormalsT=LNormals+LNormalId-1;
     LVertexNode->Normal.X=LNormalsT->X;LVertexNode->Normal.Y=LNormalsT->Y;LVertexNode->Normal.Z=LNormalsT->Z;
    }

    LVertexNode++;
   }

   LPolygon->NumOfVertices=LN;
   LPolygon->NumOfExteriorVertices=LN;
  }
 }

 return(0);
}


//================================================
// Read the Geometries
//================================================
static int _ReadGeometries(FILE* LFile, char* LFileName, E3dModel* LModel)
{
 char			LGroupName[256];
 char			LTmpStr[MAXPATHLEN+1];
 char			LMaterialName[256];

 int			LStartOffset=0, LBytesInBuffer=0;

 char*			LLinePtr;
 char*			LPtr;
 char			LPathName[MAXPATHLEN+1];

 E3dVertex*		LVertex=NULL;
 E3dVertex*		LVertices;
 E3dVertex*		LVerticesT;

 E3dVector*		LNormal=NULL;
 E3dVector*		LNormals;
 E3dVector*		LNormalsT;

 E3dST*			LST=NULL;
 E3dST*			LSTs;
 E3dST*			LSTsT;

 E3dPolyGroup*		LPolyGroup;
 E3dMesh*		LMesh;

 E3dOBJPolyGroup*	LOBJPolyGroup;
 E3dOBJPolyGroup*	LOBJPolyGroups;

 unsigned int		LNumOfVertices, LNumOfVerticesAllocated,
			LNumOfNormals, LNumOfNormalsAllocated,
			LNumOfPolyGroups, LNumOfPolyGroupsAllocated,
			LNumOfSTs, LNumOfSTsAllocated, LC,
			LVertexFormat;

 E3dMaterial*		LMaterial;
 E3dMaterial**		LMaterials;
 int			LVtxId, LNormalId, LSTId, LNumOfMaterials,
			LLen;
 float			LFX, LFY, LFZ;
 EBool			LStartNewMesh, LGoOn=TRUE;


 LNumOfMaterials=0;
 LMaterial=NULL;
 LMaterials=NULL;

 LStartNewMesh=TRUE;

 LMesh=NULL;

 LPathName[0]='\0';
 EStr_GetPathName(LFileName, LPathName, MAXPATHLEN);

 LNumOfVertices = LNumOfVerticesAllocated = 0;
 LNumOfNormals = LNumOfNormalsAllocated = 0;
 LNumOfSTs = LNumOfSTsAllocated = 0;
 LNumOfPolyGroups = LNumOfPolyGroupsAllocated = 0;
 LVertices = NULL;LSTs = NULL;LNormals = NULL;
 LOBJPolyGroups = NULL;
 LOBJPolyGroup = NULL;
 LGroupName[0] = '\0';

// while(fscanf(LFile, "%s", LBuffer) != EOF)
// while(fgets((char*)_Buffer, ObjMAX_LINE_LENGTH, LFile) != EOF)

 rewind(LFile);

 _Buffer=EMalloc(ObjMAX_LINE_LENGTH);
 if(_Buffer==NULL) return(ObjCOULD_NOT_ALLOCATE_LINEBUFFER);

 while(((LLen=_FGetLine(LFile, (char*)_Buffer, &LStartOffset, &LBytesInBuffer, &LLinePtr))>=0)&&(LGoOn))
 {
  if(LLen==0) continue;

//printf("%4d %s\n", LLen, LLinePtr);fflush(stdout);
  switch(LLinePtr[0])
  {
   case '#':		// Comment, ignore the rest of the line
   break;

   case 'v':								// v, vn or vt
    switch(LLinePtr[1])
    {
     case ' ':								// Vertex array
     case '\t':
     case '\0':
// Start new Mesh
//
      if(LStartNewMesh)
      {

       if(LMesh)
       {
// Set the stats in the E3dMesh structure
//
	LMesh->NumOfVertices  = LNumOfVertices;

	if(LNumOfVertices>0)
	{
// Free unnecesserarily allocated Mesh vertices
//
	 if(LNumOfVerticesAllocated>LNumOfVertices)
	 {
	  LMesh->Vertices=(E3dVertex*)ERealloc(LVertices, sizeof(E3dVertex)*LNumOfVertices);
	 }
	 else LMesh->Vertices=LVertices;

	 LMesh->NumOfVertices=LNumOfVertices;
	}
	else EFree(LVertices);

// Convert PolyGroups
//
	if((LNumOfPolyGroups>0)&&(LOBJPolyGroups!=NULL))
	{
	 LOBJPolyGroup = LOBJPolyGroups;
	 for(LC = 0; LC < LNumOfPolyGroups; LC++, LOBJPolyGroup++)
	 {
	  if((LPolyGroup=E3d_MeshAddPolyGroup(LMesh))!=NULL)
	  {
	   LPolyGroup->Name=LOBJPolyGroup->Name;

// Free unnecesserarily allocated Polygons
//
	   if(LOBJPolyGroup->NumOfPolygonsAllocated>LOBJPolyGroup->NumOfPolygons)
	   {
	    LPolyGroup->Polygons=(E3dPolygon*)ERealloc(LOBJPolyGroup->Polygons, sizeof(E3dPolygon)*LOBJPolyGroup->NumOfPolygons);
	   }
	   else LPolyGroup->Polygons=LOBJPolyGroup->Polygons;

	   LPolyGroup->Material=LOBJPolyGroup->Material;
	   if(LOBJPolyGroup->Material) LOBJPolyGroup->Material->RefCnt+=1;
	   LPolyGroup->NumOfPolygons=LOBJPolyGroup->NumOfPolygons;
	   if(LNumOfNormals>0) LPolyGroup->Flags|=E3dUSE_VERTEX_NORMALS;
	  }
	 }

	 EFree(LOBJPolyGroups);
	}

	EFree(LNormals);
	if(LSTs) EFree(LSTs);

        E3d_MeshRefreshPolygonNormals(LMesh);
       }





       LNumOfVertices = LNumOfNormals = LNumOfSTs = 0;
       LNumOfPolyGroups = LNumOfPolyGroupsAllocated = 0;
       LOBJPolyGroups=NULL;
       LOBJPolyGroup=NULL;
       LGroupName[0]='\0';


       LStartNewMesh=FALSE;
       if((LMesh=E3d_MeshAllocate())!=NULL)
       {
        E3d_ModelAppendGeometry(LModel, (E3dGeometry*)LMesh);
// Allocate first chunk of vertex array
//
	if((LVertices=(E3dVertex*)EMalloc(sizeof(E3dVertex)*E3dVTXALLOC_INCREMENT))!=NULL)
	{
	 LNumOfVerticesAllocated=E3dVTXALLOC_INCREMENT;
	 LVertex=LVertices;
	}
	else return(0);

// Allocate first chunk of normal array
//
	if((LNormals=(E3dVector*)EMalloc(sizeof(E3dVector)*E3dVTXALLOC_INCREMENT))!=NULL)
	{
	 LNumOfNormalsAllocated=E3dVTXALLOC_INCREMENT;
	 LNormal=LNormals;
	}
	else { EFree(LVertices);return(0); }
       }


// Initialize ST array
//
       LSTs = NULL;
       LNumOfSTsAllocated = 0;
      }
      else
      {
       if(LNumOfVertices>=LNumOfVerticesAllocated)
       {
	if((LVerticesT=(E3dVertex*)ERealloc(LVertices, sizeof(E3dVertex)*(LNumOfVertices+E3dVTXALLOC_INCREMENT)))!=NULL)
	{
	 LVertices=LVerticesT;
	 LNumOfVerticesAllocated+=E3dVTXALLOC_INCREMENT;
	 LVertex=LVertices+LNumOfVertices;
	}
	else
	{
	 EFree(LVertices);EFree(LNormals);
	 if(LSTs) EFree(LSTs);
	 return(0);
	}
       }
      }

      sscanf(LLinePtr+2, "%f %f %f", &LFX, &LFY, &LFZ);

//if(LNumOfVertices<4) { printf("%f %f %f\n", LFX, LFY, LFZ);fflush(stdout); }

      LVertex->X=LFX;
      LVertex->Y=LFY;
      LVertex->Z=LFZ;
      LVertex++;
      LNumOfVertices++;
     break;

     case 'n':								// Normal array
      if(LNumOfNormals>=LNumOfNormalsAllocated)
      {
       if((LNormalsT=(E3dVector*)ERealloc(LNormals, sizeof(E3dVector)*(LNumOfNormals+E3dVTXALLOC_INCREMENT)))!=NULL)
       {
        LNormals=LNormalsT;
        LNumOfNormalsAllocated+=E3dVTXALLOC_INCREMENT;
        LNormal=LNormals+LNumOfNormals;
       }
       else
       {
        EFree(LNormals);EFree(LNormals);
	if(LSTs) EFree(LSTs);
	return(0);
       }
      }

      sscanf(LLinePtr+2, "%f %f %f", &LFX, &LFY, &LFZ);

//printf("vn %f %f %f\n", LFX, LFY, LFZ);fflush(stdout);

      LNormal->X=LFX;
      LNormal->Y=LFY;
      LNormal->Z=LFZ;
      LNormal++;
      LNumOfNormals++;
     break;

     case 't':								// Texture STs
      if(LNumOfSTs>=LNumOfSTsAllocated)
      {
       if((LSTsT=(E3dST*)ERealloc(LSTs, sizeof(E3dST)*(LNumOfSTs+E3dVTXALLOC_INCREMENT)))!=NULL)
       {
        LSTs=LSTsT;
        LNumOfSTsAllocated+=E3dVTXALLOC_INCREMENT;
        LST=LSTs+LNumOfSTs;
       }
       else
       {
	EFree(LNormals);EFree(LNormals);
	if(LSTs) EFree(LSTs);
	return(0);
       }
      }

      sscanf(LLinePtr+2, "%f %f", &LFX, &LFY);

      LST->S=LFX;
      LST->T=1.0-LFY;
      LST++;
      LNumOfSTs++;
     break;

     default:
      printf("OBJ reader: Unknown token: [%s] %d %d\n", LLinePtr, LStartOffset, LBytesInBuffer);
      exit(1);
     break;
    }
   break;

   case 'm':								// Material reference
    LMaterialName[0]='\0';
    sscanf(LLinePtr+1, "%s %s", LTmpStr, LMaterialName);

//    sscanf(LLinePtr, "%s %s", LMaterialName, LTmpStr);

    if((LMaterials=_MaterialReadMTL(LPathName, LMaterialName, &LNumOfMaterials))!=NULL) E3d_SceneAddMaterials(E3d_Scene, LMaterials, LNumOfMaterials);
   break;

   case 'u':
    if(EStr_StringsNEqual(LLinePtr, "usemtl", 6))
    {
     sscanf(LLinePtr+7, "%s", LMaterialName);	// +7: after "usemtl"

     LMaterial=NULL;
// Find Material with that name
//
     for(LC=0;LC<LNumOfMaterials;LC++)
     {
      if(EStr_StringsEqual(LMaterials[LC]->Name, LMaterialName)) { LMaterial=LMaterials[LC];break; }
     }

//printf("Add Mat %s\n", LMaterialName);fflush(stdout);

// Material not found, create one with this name
//
     if(LMaterial==NULL)
     {
      if(LMaterials==NULL) LMaterials=(E3dMaterial**)EMalloc(sizeof(E3dMaterial*));
      else
      {
       E3dMaterial**	LMaterialsT;

       if((LMaterialsT=(E3dMaterial**)ERealloc(LMaterials, sizeof(E3dMaterial*)*(LNumOfMaterials+1)))!=NULL)
       {
	LMaterials=LMaterialsT;
       }
       else { EFree(LMaterials);break; }
      }

      if((LMaterial=E3d_MaterialAllocate())!=NULL)
      {
       
       LMaterials[LNumOfMaterials]=LMaterial;
       if(LMaterialName[0]) LMaterial->Name=strdup(LMaterialName);
       LNumOfMaterials++;
      }
     }


// Are we in a PolyGroup yet?
//
     if(LOBJPolyGroup)
     {
// If this PolyGroup doesn't have a Material yet, assign this one to it
//
      if(LOBJPolyGroup->Material==NULL)
      {
       LOBJPolyGroup->Material=LMaterial;LMaterial->RefCnt+=1;
      }
      else
      {
// Search for the PolyGroup using this Material and make it the current PolyGroup
//
       if(LNumOfPolyGroups>0)
       {
	LOBJPolyGroup=LOBJPolyGroups;

	for(LC=0;LC<LNumOfPolyGroups;LC++)
	{
	 if(LOBJPolyGroup->Material==LMaterial) break;
	 LOBJPolyGroup++;
	}

// PolyGroup with this Material was not found, create new PolyGroup and assign this Material to it
//
	if(LC>=LNumOfPolyGroups)
	{
	 if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
	 {
	  LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
          LOBJPolyGroup->Material=LMaterial;LMaterial->RefCnt+=1;

	  LNumOfPolyGroups++;
	 }
	}
       }
       else
       {
// There are no PolyGroups defined yet, create new PolyGroup and assign this Material to it
//
	if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
	{
	 LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
         LOBJPolyGroup->Material=LMaterial;LMaterial->RefCnt+=1;

	 LNumOfPolyGroups++;
	}
       }

      }
     }
     else
     {
// There are no PolyGroups defined yet, create a new PolyGroup and assign this Material to it
//
      if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
      {
       LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
       LOBJPolyGroup->Material=LMaterial;LMaterial->RefCnt+=1;

       LNumOfPolyGroups++;
      }

//printf("Material [%s] outside of PolyGroups\n", LMaterial->Name);fflush(stdout);

     }
    }
   break;

   case 'g':								// Group
    if(LLen>2) sscanf(LLinePtr+2, "%s", LGroupName);
    else LGroupName[0]=0;

// Look for a PolyGroup with this name
//
    if(LNumOfPolyGroups>0)
    {
     LOBJPolyGroup=LOBJPolyGroups;

     for(LC=0;LC<LNumOfPolyGroups;LC++)
     {
      if(LOBJPolyGroup->Name==NULL)
      {
       if(LGroupName[0]==0) break;
      }
      else
      {
       if(EStr_StringsEqual(LOBJPolyGroup->Name, LGroupName)) break;
      }
      LOBJPolyGroup++;
     }

// PolyGroup with that name was not found
//
     if(LC>=LNumOfPolyGroups)
     {
// Search for a PolyGroup using this Material
//
      if(LMaterial)
      {
       LOBJPolyGroup=LOBJPolyGroups;

       for(LC=0;LC<LNumOfPolyGroups;LC++)
       {
        if(LOBJPolyGroup->Material==LMaterial) { if(LOBJPolyGroup->Name==NULL);LOBJPolyGroup->Name=EStrDup(LGroupName);break; }
        LOBJPolyGroup++;
       }
      }

// PolyGroup with that Material was not found either, create new PolyGroup
//
      if(LC>=LNumOfPolyGroups)
      {
       if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
       {
	LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
        LOBJPolyGroup->Material=LMaterial;
	LNumOfPolyGroups++;
       }
      }
     }
    }
    else
    {
     if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
     {
      LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
      LOBJPolyGroup->Material=LMaterial;
      LNumOfPolyGroups++;
     }
    }
   break;

   case 'f':								// Polygons ("Faces")
// We are starting the "faces" now, so the next Vertex will belong to a new Mesh
//
//    LStartNewMesh=TRUE;

    if(LNumOfPolyGroups==0)						// If there is no PolyGroup yet, create one
    {
     if((LOBJPolyGroups=_OBJPolyGroupAdd(LOBJPolyGroups, LNumOfPolyGroups, LGroupName))!=NULL)
     {
      LOBJPolyGroup=LOBJPolyGroups+LNumOfPolyGroups;
      LOBJPolyGroup->Material=LMaterial;
      LNumOfPolyGroups++;
     }
    }

    LVtxId = LNormalId = LSTId = 0;

// Softimage does this funny thing with faces:
// "f \"
// 25//1 26//2 27//3
//
    if(LLinePtr[0]=='\\') sscanf(LLinePtr, "%s", LTmpStr);

// The vertex node data can be in either of these formats:
//  %d (vertex index only)
//  %d//%d (vertex index//normal index)
//  %d/%d (vertex index/ST index)
//  %d/%d/%d (vertex index/ST index/normal index)
//
    LPtr=LLinePtr+2;

    LVertexFormat=_VertexFormat(LPtr, &LLen, &LVtxId, &LSTId, &LNormalId);
    switch(LVertexFormat&(OBJV_v | OBJV_t | OBJV_n))
    {
     case OBJV_v|OBJV_n:
      _GetFaceVN(LFile, LOBJPolyGroup, LPtr, LNumOfVertices, LNormals, LNumOfNormals);
     break;

     case OBJV_v|OBJV_t|OBJV_n:
      _GetFaceVTN(LFile, LOBJPolyGroup, LPtr, LNumOfVertices, LSTs, LNumOfSTs, LNormals, LNumOfNormals);
     break;

     case OBJV_v|OBJV_t:					// "v/t/"  or "v/t"
      _GetFaceVT(LVertexFormat, LFile, LOBJPolyGroup, LPtr, LNumOfVertices, LSTs, LNumOfSTs);
     break;

     case OBJV_v:
      _GetFaceV(LFile, LOBJPolyGroup, LPtr, LNumOfVertices);
     break;
    }
   break;

   default:	break;		// Ignore the rest of the line
  }
 }


// Set the stats in the E3dMesh structure
//
 if(LMesh) LMesh->NumOfVertices  = LNumOfVertices;

 if(LNumOfVertices>0)
 {
// Free unnecesserarily allocated Mesh vertices
//
  if(LNumOfVerticesAllocated>LNumOfVertices)
  {
   LMesh->Vertices=E3d_VerticesReallocate(LVertices, LNumOfVertices, TRUE);
  }
  else
  {
   LVertex=LVertices;
   for(LC=0;LC<LNumOfVertices;LC++, LVertex++)
   {
    LVertex->Flags=0;
   }

   LMesh->Vertices=LVertices;
  }

  LMesh->NumOfVertices=LNumOfVertices;
 }
 else
 {
  if(LVertices) EFree(LVertices);
 }

// Convert PolyGroups
//
 if((LNumOfPolyGroups>0)&&(LOBJPolyGroups!=NULL))
 {
  LOBJPolyGroup = LOBJPolyGroups;
  for(LC = 0; LC < LNumOfPolyGroups; LC++, LOBJPolyGroup++)
  {
   if((LPolyGroup=E3d_MeshAddPolyGroup(LMesh))!=NULL)
   {
    LPolyGroup->Name=LOBJPolyGroup->Name;

// Free unnecesserarily allocated Polygons
//
    if(LOBJPolyGroup->NumOfPolygonsAllocated>LOBJPolyGroup->NumOfPolygons)
    {
     LPolyGroup->Polygons=(E3dPolygon*)ERealloc(LOBJPolyGroup->Polygons, sizeof(E3dPolygon)*LOBJPolyGroup->NumOfPolygons);
    }
    else LPolyGroup->Polygons=LOBJPolyGroup->Polygons;

    LPolyGroup->Material=LOBJPolyGroup->Material;
    if(LOBJPolyGroup->Material) LOBJPolyGroup->Material->RefCnt+=1;
    LPolyGroup->NumOfPolygons=LOBJPolyGroup->NumOfPolygons;
    if(LNumOfNormals>0) LPolyGroup->Flags|=E3dUSE_VERTEX_NORMALS;
   }
  }

  EFree(LOBJPolyGroups);
 }

 if(LSTs) EFree(LSTs);
 if(LNormals) EFree(LNormals);
 if(LMesh) E3d_MeshRefreshPolygonNormals(LMesh);


 if(LMaterials) EFree(LMaterials);

 if(_Buffer) EFree(_Buffer);

 return(LNumOfPolyGroups);
}


//========================================
// Read an OBJ file
//========================================
int E3d_ModelReadFromOBJFile(char* LFileName, FILE* LInFile, E3dModel** LModelRet)
{
 E3dModel*	LModel;
 char		LModelName[MAXPATHLEN+1];
 unsigned int	LLen;
 int		LStatus;


 LModel=NULL;
 EStr_GetFileName(LFileName, LModelName, MAXPATHLEN);

 if((LLen=strlen(LModelName))>4)
 {
  if(EStr_StringsEqual(LModelName+LLen-4, ".obj")) LModelName[LLen-4]='\0';
 }
 LModel=E3d_ModelAllocate(LModelName);
 if(LModel)
 {
  LStatus=_ReadGeometries(LInFile, LFileName, LModel);
  if(LStatus>=0)
  {
   if(LModel->NumOfGeometries)
   {
// There is no wireframe info in an OBJ file, so we have to create it
//
    E3d_ModelHrcCreateEdgesFromPolyData(LModel);

    if(LModelRet) *LModelRet=LModel;
    LModel->Selection=E3dSEL_BRANCH_ROOT;
    return(EIO_SUCCESS);
   }
  }
/*
  else
  {
   switch(LStatus)
   {
    case ObjBAD_FACE_VERTEX:	printf("OBJ read fatal error: bad Vertex index in face\n");fflush(stdout);break;
   }
  }
*/
 }

 if(LModelRet) *LModelRet=LModel;
 return(EIO_ERROR);
}


//========================================
// Write Model to OBJ file
//========================================
int E3d_ModelWriteToOBJFile(char* LFileName, E3dModel* LModel, unsigned char LType)
{
 char		LMatLibName[MAXPATHLEN+1];
 char		LTmpStr[MAXPATHLEN+1];
 unsigned int	LL;
 FILE*		LOutFile;
 E3dMaterial*	LMaterial;
 E3dMaterial**	LMaterials;
 unsigned int	LNumOfMaterials;
 EBool		LSaveSTs;

 if((LOutFile=fopen(LFileName, "w"))!=NULL)
 {
// Collect Materials into an array
//
  LMaterials=E3d_ModelCollectMaterials(LModel, &LNumOfMaterials);

  if(LNumOfMaterials>0)
  {
   LL=strlen(LFileName);
   if(LL>4)
   {
    if(EStr_StringsEqual(LFileName+LL-4, ".obj")||EStr_StringsEqual(LFileName+LL-4, ".OBJ"))
    {
     memcpy(LMatLibName, LFileName, LL-3);
     LMatLibName[LL-3]='m';
     LMatLibName[LL-2]='t';
     LMatLibName[LL-1]='l';
     LMatLibName[LL]='\0';
    }
    else sprintf(LMatLibName, "%s.mtl", LFileName);
   }
   else sprintf(LMatLibName, "%s.mtl", LFileName);

   _MaterialSaveMTL(LMatLibName, LMaterials, LNumOfMaterials);
  }

  if(LMaterials) EFree(LMaterials);

  if(LModel->Type == E3dMDL_NORMAL)
  {
   E3dVertexNode*	LVertexNode;
   E3dVertex*		LVertex;
   E3dPolygon*		LPolygon;
   E3dPolyGroup*	LPolyGroup;
   E3dPolyGroup**	LPolyGroups;
   E3dMesh*		LMesh;
   E3dGeometry*		LGeometry;
   E3dGeometry**	LGeometries;
   unsigned int		LGmCnt, LGmNum, LGCnt, LGN, LVN, LVC, LPolyCnt,
			LVertexIDOffset, LVertexNormalID, LVertexSTID;

   fprintf(LOutFile, "##====================================================\n");
   fprintf(LOutFile, "## Wavefront OBJ file generated by EQUINOX-3D\n");
   fprintf(LOutFile, "##\n");
   fprintf(LOutFile, "## http://www.equinox3d.com/\n");
   fprintf(LOutFile, "##====================================================\n\n");

// Include '.mtl' file
//
   if(LNumOfMaterials>0)
   {
    EStr_GetFileName(LMatLibName, LTmpStr, MAXPATHLEN);
    fprintf(LOutFile, "mtllib %s\n\n", LTmpStr);
   }

   LGmNum=LModel->NumOfGeometries;LGeometries=LModel->Geometries;

   LVertexIDOffset=1;LVertexNormalID=1;LVertexSTID=1;

   for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
   {
    if((LGeometry=LGeometries[LGmCnt])!=NULL)
    {
     switch(LGeometry->GeoType)
     {
      case E3dGEO_MESH:
       LMesh=(E3dMesh*)LGeometry;

       LVertex=LMesh->Vertices;
       LVN=LMesh->NumOfVertices;

       for(LVC=0;LVC<LVN;LVC++, LVertex++)
       {
        fprintf(LOutFile, "v %f %f %f\n", LVertex->X, LVertex->Y, LVertex->Z);
       }


// Save Vertex normals
//
       fprintf(LOutFile, "\n");

       LPolyGroups=LMesh->PolyGroups;
       LGN=LMesh->NumOfPolyGroups;

       for(LGCnt=0;LGCnt<LGN;LGCnt++)
       {
	LPolyGroup=LPolyGroups[LGCnt];

        if(LPolyGroup->VertexNormalType!=E3dNormalNONE)
	{
	 LPolyCnt=LPolyGroup->NumOfPolygons;
	 if(LPolyCnt)
	 {
	  LPolygon=LPolyGroup->Polygons;

	  do
	  {
	   LVertexNode=LPolygon->VertexNodes;
	   LVN=LPolygon->NumOfExteriorVertices;

	   for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
	   {
	    fprintf(LOutFile, "vn %f %f %f\n", LVertexNode->Normal.X, LVertexNode->Normal.Y, LVertexNode->Normal.Z);

//printf("vn %f %f %f\n", LVertexNode->Normal.X, LVertexNode->Normal.Y, LVertexNode->Normal.Z);
	   }
	   LPolygon++;
	  } while(--LPolyCnt);
	 }
        }

       }


       fprintf(LOutFile, "\n");

// Save vertex STs
//
       for(LGCnt = 0; LGCnt < LGN; LGCnt++)
       {
	LPolyGroup=LPolyGroups[LGCnt];

// See if we should write out the texture coordinates of this PolyGroup
//
	LSaveSTs=FALSE;
	if(LPolyGroup->Material==NULL) LMaterial=E3d_ModelInheritMaterial(LModel);
	else LMaterial=LPolyGroup->Material;
	if(LMaterial->NumOf2DTextures>0)
	{
	 if((LPolyCnt=LPolyGroup->NumOfPolygons)>0)
	 {
	  LPolygon=LPolyGroup->Polygons;

	  do
	  {
	   LVertexNode=LPolygon->VertexNodes;
	   if(LVertexNode)
	   {
	    LVN=LPolygon->NumOfExteriorVertices;

	    for(LVC = 0; LVC < LVN; LVC++, LVertexNode++)
	    {
	     if(LVertexNode->VertexID!=-1) fprintf(LOutFile, "vt %f %f\n", LVertexNode->S,  1.0-LVertexNode->T);
	    }
	   }
	   LPolygon++;
	  } while(--LPolyCnt);
	 }
	}
       }


//       LVertexNormalID=1;LVertexSTID=1;

// Export the PolyGroups
//
       LPolyGroups=LMesh->PolyGroups;
       for(LGCnt=0;LGCnt<LMesh->NumOfPolyGroups;LGCnt++)
       {
	LPolyGroup=LPolyGroups[LGCnt];

// See if we need texture coordinates
//
	LSaveSTs=FALSE;
	if(LPolyGroup->Material==NULL) LMaterial=E3d_ModelInheritMaterial(LModel);
	else LMaterial=LPolyGroup->Material;
        if(LMaterial->NumOf2DTextures>0) LSaveSTs=TRUE;


	if(LPolyGroup->Name==NULL) fprintf(LOutFile, "\ng default\n");
	else fprintf(LOutFile, "\ng %s\n", LPolyGroup->Name);

	if(LMaterial!=&E3d_DefaultMaterial) fprintf(LOutFile, "usemtl %s\n", LMaterial->Name);

	if((LPolyCnt=LPolyGroup->NumOfPolygons)>0)
	{
	 LPolygon=LPolyGroup->Polygons;

// Should we save vertex-normal indices?
//
	 if(LPolyGroup->VertexNormalType!=E3dNormalNONE)
	 {

	  if(LSaveSTs)
	  {
	   do
	   {
	    if((LVertexNode=LPolygon->VertexNodes)!=NULL)
	    {
	     LVN=LPolygon->NumOfExteriorVertices-1;

	     fprintf(LOutFile, "f ");

	     for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
	     {
	      fprintf(LOutFile, "%d/%d/%d ", LVertexNode->VertexID+LVertexIDOffset, LVertexSTID, LVertexNormalID);
	      LVertexSTID++;LVertexNormalID++;
	     }
	     fprintf(LOutFile, "%d/%d/%d\n", LVertexNode->VertexID+LVertexIDOffset, LVertexSTID, LVertexNormalID);
	     LVertexSTID++;LVertexNormalID++;
	    }
	    LPolygon++;
	   } while(--LPolyCnt);
	  }
	  else
	  {
	   do
	   {
	    if((LVertexNode=LPolygon->VertexNodes)!=NULL)
	    {
	     LVN=LPolygon->NumOfExteriorVertices-1;

	     fprintf(LOutFile, "f ");

	     for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
	     {
	      fprintf(LOutFile, "%d//%d ", LVertexNode->VertexID+LVertexIDOffset, LVertexNormalID);
	      LVertexNormalID++;
	     }
	     fprintf(LOutFile, "%d//%d\n", LVertexNode->VertexID+LVertexIDOffset, LVertexNormalID);
	     LVertexNormalID++;
	    }
	    LPolygon++;
	   } while(--LPolyCnt);

	  }
	 }
	 else
	 {
// No vertex normals
//
	  if(LSaveSTs)
	  {
	   do
	   {
	    if((LVertexNode=LPolygon->VertexNodes)!=NULL)
	    {
	     LVN=LPolygon->NumOfExteriorVertices-1;

	     fprintf(LOutFile, "f ");

	     for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
	     {
	      fprintf(LOutFile, "%d/%d/ ", LVertexNode->VertexID+LVertexIDOffset, LVertexSTID);
	      LVertexSTID++;
	     }
	     fprintf(LOutFile, "%d/%d/\n", LVertexNode->VertexID+LVertexIDOffset, LVertexSTID);
	     LVertexSTID++;
	    }
	    LPolygon++;
	   } while(--LPolyCnt);
	  }
	  else
	  {
	   do
	   {
	    if((LVertexNode=LPolygon->VertexNodes)!=NULL)
	    {
	     LVN=LPolygon->NumOfExteriorVertices-1;

	     fprintf(LOutFile, "f ");

	     for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
	     {
	      fprintf(LOutFile, "%d ", LVertexNode->VertexID+LVertexIDOffset);
	     }
	     fprintf(LOutFile, "%d\n", LVertexNode->VertexID+LVertexIDOffset);
	    }
	    LPolygon++;
	   } while(--LPolyCnt);

	  }
	 }

	}

       }
       LVertexIDOffset+=LMesh->NumOfVertices;
      break;
     }
    }
   }

  }


  fclose(LOutFile);
  return(EIO_SUCCESS);
 }
 return(EIO_ERROR);
}


//========================================
// Entry point of the plugi
//========================================
int Plugin_Init(void* LPIID)
{
 E3d_FileFormat=E3d_3DModelIOFilterAdd("Wavefront OBJ", E3d_ModelIsOBJFile, E3d_ModelReadFromOBJFile, E3d_ModelWriteToOBJFile, E3d_FileNameExtensions);
 return(0);
}


//========================================
// Exit method of the plugin
//========================================
int Plugin_Exit()
{
 E3d_3DModelIOFilterDeactivate(E3d_FileFormat);

 return(0);
}
