/*===========================================================================
 *
 * File:	Df3dobj.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Wednesday, June 20, 2001
 *
 * Implements the CDF3dObject class for handling 3D object files.
 *
 *=========================================================================*/

	/* Include Files */
#include "df3dobj.h"
#include "common/dl_time.h"
#include <memory.h>
//#include "common/utility/profile.h"


/*===========================================================================
 *
 * Begin Local Variable Definitions
 *
 *=========================================================================*/
  DEFINE_FILE();
/*===========================================================================
 *		End of Local Variable Definitions
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Constructor
 *
 *=========================================================================*/
CDF3dObject::CDF3dObject () {
  //DEFINE_FUNCTION("CDF3dObject::CDF3dObject()");
  m_FileSize    = 0;
  m_StartOffset = 0;
  m_Version     = DF3D_VERSION_27;

	/* Initialize sub-object arrays */
  m_pPoints  = NULL;
  m_pFaces   = NULL;
  m_pNormals = NULL;
  m_pData1   = NULL;
  m_pData2   = NULL;

	/* Clear header */
  memset(&m_Header, 0, sizeof(df3dheader_t));
 }
/*===========================================================================
 *		End of Class CDF3dObject Constr uctor
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - void Destroy (void);
 *
 *=========================================================================*/
void CDF3dObject::Destroy (void) {
  //DEFINE_FUNCTION("CDF3dObject::Destroy()");

	/* Unallocate arrays */
  DestroyArrayPointer(m_pPoints);
  DestroyArrayPointer(m_pFaces);
  DestroyArrayPointer(m_pNormals);
  DestroyArrayPointer(m_pData1);
  DestroyArrayPointer(m_pData2);

 }
/*===========================================================================
 *		End of Class Method CDF3dObject::Destroy()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - void ClearFaceTags (void);
 *
 * Reset the tag for each face in the object.  Function is considered const
 * as the Tag member data is not truely part of the face data.
 *
 *=========================================================================*/
void CDF3dObject::ClearFaceTags (void) const {
  DEFINE_FUNCTION("CDF3dObject::ClearFaceTags()");
  long FaceIndex;

	/* Clear the tag for each face in object */
  for (FaceIndex = 0; FaceIndex < GetNumFaces(); FaceIndex++) {
    m_pFaces[FaceIndex].Tag = 0;
   }

 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ClearFaceTags()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - int ForEachFace (fnCallBack, pCallbackData);
 *
 * Calls the given callback function for each face in the object, along 
 * with the custom callback data.  Returns a non-zero value on any error.
 *
 *=========================================================================*/
int CDF3dObject::ForEachFace (PFNDF3D_FOREACHFACE fnCallBack, void* pCallbackData) {
  DEFINE_FUNCTION("CDF3dObject::ForEachFace()");
  int  Result;
  long FaceIndex;

	/* Ensure valid input */
  ASSERT(fnCallBack != NULL);

	/* Perform the operation for each face in object */
  for (FaceIndex = 0; FaceIndex < GetNumFaces(); FaceIndex++) {
    Result = fnCallBack(m_pFaces[FaceIndex], pCallbackData);
    if (Result != 0) return (Result);
   }

	/* Return success */
  return (0);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ForEachFace()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - int ForEachFaceC (fnCallBack, pCallbackData) const;
 *
 * Calls the given callback function for each face in the object, along 
 * with the custom callback data.  Returns a non-zero value on any error.
 * Constant version of the ForEachFace() method.
 *
 *=========================================================================*/
int CDF3dObject::ForEachFaceC (PFNDF3D_FOREACHFACEC fnCallBack, void* pCallbackData) const {
  DEFINE_FUNCTION("CDF3dObject::ForEachFaceC()");
  int  Result;
  long FaceIndex;

	/* Ensure valid input */
  ASSERT(fnCallBack != NULL);

	/* Perform the operation for each face in object */
  for (FaceIndex = 0; FaceIndex < GetNumFaces(); FaceIndex++) {
    Result = fnCallBack(m_pFaces[FaceIndex], pCallbackData);
    if (Result != 0) return (Result);
   }

	/* Return success */
  return (0);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ForEachFaceC()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - int ForEachPoint (fnCallBack, pCallbackData);
 *
 * Calls the given callback function for each point in the object, along 
 * with the custom callback data.  Returns a non-zero value on any error.
 *
 *=========================================================================*/
int CDF3dObject::ForEachPoint (PFNDF3D_FOREACHPOINT fnCallBack, void* pCallbackData) {
  DEFINE_FUNCTION("CDF3dObject::ForEachPoint()");
  int  Result;
  long PointIndex;

	/* Ensure valid input */
  ASSERT(fnCallBack != NULL);

	/* Perform the operation for each point in object */
  for (PointIndex = 0; PointIndex < GetNumPoints(); PointIndex++) {
    Result = fnCallBack(m_pPoints[PointIndex], pCallbackData);
    if (Result != 0) return (Result);
   }

	/* Return success */
  return (0);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ForEachPoint()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - int ForEachPointC (fnCallBack, pCallbackData) const;
 *
 * Calls the given callback function for each point in the object, along 
 * with the custom callback data.  Returns a non-zero value on any error.
 * Constant version of the ForEachPoint() method.
 *
 *=========================================================================*/
int CDF3dObject::ForEachPointC (PFNDF3D_FOREACHPOINTC fnCallBack, void* pCallbackData) const {
  DEFINE_FUNCTION("CDF3dObject::ForEachPointC()");
  int  Result;
  long PointIndex;

	/* Ensure valid input */
  ASSERT(fnCallBack != NULL);

	/* Perform the operation for each point in object */
  for (PointIndex = 0; PointIndex < GetNumPoints(); PointIndex++) {
    Result = fnCallBack(m_pPoints[PointIndex], pCallbackData);
    if (Result != 0) return (Result);
   }

	/* Return success */
  return (0);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ForEachPointC()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - long GetMaxY (void) const;
 *			      long GetMinY (void) const;
 *
 * Returns the maximum/minimum Y point coordinate.
 *
 *=========================================================================*/
long CDF3dObject::GetMaxY (void) const {
  int  Index;
  long MaxValue;

  if (m_Header.NumPoints == 0) return (0);
  MaxValue = m_pPoints[0].Y;

  for (Index = 1; Index < m_Header.NumPoints; Index++) {
    if (m_pPoints[Index].Y > MaxValue) MaxValue = m_pPoints[Index].Y;
   }

  return (MaxValue);
 }

long CDF3dObject::GetMinY (void) const {
  int  Index;
  long MinValue;

  if (m_Header.NumPoints == 0) return (0);
  MinValue = m_pPoints[0].Y;

  for (Index = 1; Index < m_Header.NumPoints; Index++) {
    if (m_pPoints[Index].Y < MinValue) MinValue = m_pPoints[Index].Y;
   }

  return (MinValue);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::GetMaxY()
 *=========================================================================*/

/*===========================================================================
 *
 * Class CDF3dObject Method - boolean Read (pFileHandle);
 *
 * Attempt to read the 3D object from the current position in the given file
 * stream.  Returns FALSE on any error.
 *
 *=========================================================================*/
boolean CDF3dObject::Read (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::Read()");
  long    EndPointOffset;
  long    EndFaceOffset;
  long    EndNormalOffset;
  long    EndData1Offset;
  long    EndData2Offset;
  long    EndOffset;
  boolean Result;

	/* Ensure valid input */
  ASSERT(pFileHandle != NULL);
  ASSERT(!feof(pFileHandle) && !ferror(pFileHandle));

	/* Destroy the current object contents */
  Destroy();
  m_StartOffset = ftell(pFileHandle);

	/* Read header information */
  Result = ReadHeader(pFileHandle);
  if (!Result) return (FALSE);

	/* Move to points data and input */
  fseek(pFileHandle, m_StartOffset + m_Header.PointOffset, SEEK_SET);
  Result = ReadPoints(pFileHandle);
  if (!Result) return (FALSE);
  EndPointOffset = ftell(pFileHandle);
  
  	/* Move to face data and input */
  fseek(pFileHandle, m_StartOffset + m_Header.FaceOffset, SEEK_SET);
  Result = ReadFaces(pFileHandle);
  if (!Result) return (FALSE);
  EndFaceOffset = ftell(pFileHandle);

  	/* Move to normal data and input */
  fseek(pFileHandle, m_StartOffset + m_Header.NormalOffset, SEEK_SET);
  Result = ReadNormals(pFileHandle);
  if (!Result) return (FALSE);
  EndNormalOffset = ftell(pFileHandle);
  
  	/* Move to data1 section and input */
  fseek(pFileHandle, m_StartOffset + m_Header.Data1Offset, SEEK_SET);
  Result = ReadData1(pFileHandle);
  if (!Result) return (FALSE);
  EndData1Offset = ftell(pFileHandle);
  
  	/* Move to data2 section and input if required */
  fseek(pFileHandle, m_StartOffset + m_Header.Data2Offset, SEEK_SET);
  Result = ReadData2(pFileHandle);
  if (!Result) return (FALSE);
  EndData2Offset = ftell(pFileHandle);

	/* Check offsets for validity */
  EndOffset = EndPointOffset;
  if (EndFaceOffset   > EndOffset) EndOffset = EndFaceOffset;
  if (EndNormalOffset > EndOffset) EndOffset = EndNormalOffset;
  if (EndData1Offset  > EndOffset) EndOffset = EndData1Offset;
  if (EndData2Offset  > EndOffset) EndOffset = EndData2Offset;

	/* Ensure we didn't read past end of data */
  if (EndOffset > m_StartOffset + m_FileSize) {
    ErrorHandler.AddError(DFERR_3D_READPASTEND, "%ld bytes", EndOffset - m_StartOffset - m_FileSize);
    return (FALSE);
   }
  else if (EndOffset < m_StartOffset + (long)sizeof(df3dheader_t)) {
    ErrorHandler.AddError(DFERR_3D_READPASTSTART, "%ld bytes", EndOffset - m_StartOffset);
    return (FALSE);
   }

	/* Move to end of read data */
  fseek(pFileHandle, EndOffset, SEEK_SET);

	/* Check offsets in debug builds */
  #if defined(_DEBUG) 
    if (m_Header.PointOffset  < m_Header.FaceOffset)   ASSERT(EndPointOffset  < EndFaceOffset)   else ASSERT(EndPointOffset  > EndFaceOffset);
    if (m_Header.PointOffset  < m_Header.NormalOffset) ASSERT(EndPointOffset  < EndNormalOffset) else ASSERT(EndPointOffset  > EndNormalOffset);
    if (m_Header.PointOffset  < m_Header.Data1Offset)  ASSERT(EndPointOffset  < EndData1Offset)  else ASSERT(EndPointOffset  > EndData1Offset);
    if (m_Header.FaceOffset   < m_Header.NormalOffset) ASSERT(EndFaceOffset   < EndNormalOffset) else ASSERT(EndFaceOffset   > EndNormalOffset);
    if (m_Header.FaceOffset   < m_Header.Data1Offset)  ASSERT(EndFaceOffset   < EndData1Offset)  else ASSERT(EndFaceOffset   > EndData1Offset);
    if (m_Header.NormalOffset < m_Header.Data1Offset)  ASSERT(EndNormalOffset < EndData1Offset)  else ASSERT(EndNormalOffset > EndData1Offset);

    if (m_Header.NumData2 > 0) {
      if (m_Header.PointOffset  < m_Header.Data2Offset) ASSERT(EndPointOffset  < EndData2Offset) else ASSERT(EndPointOffset  > EndData2Offset);
      if (m_Header.FaceOffset   < m_Header.Data2Offset) ASSERT(EndFaceOffset   < EndData2Offset) else ASSERT(EndFaceOffset   > EndData2Offset);
      if (m_Header.NormalOffset < m_Header.Data2Offset) ASSERT(EndNormalOffset < EndData2Offset) else ASSERT(EndNormalOffset > EndData2Offset);
      if (m_Header.Data1Offset  < m_Header.Data2Offset) ASSERT(EndData1Offset  < EndData2Offset) else ASSERT(EndData1Offset  > EndData2Offset);
     }

  #endif

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::Read()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadHeader (pFileHandle);
 *
 * Protected class method to input the header data from the current position
 * in the file stream.  Returns FALSE on any error.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadHeader (FILE* pFileHandle) {
  size_t Result;
  
	/* Attempt to read the header all at once */
  Result = fread(&m_Header, 1, sizeof(df3dheader_t), pFileHandle);

	/* Ensure header was read successfully */
  if (Result != sizeof(df3dheader_t)) {
    ErrorHandler.AddError(ERR_SYSTEM, errno);
    ErrorHandler.AddError(DFERR_3D_BADHEADER);
    return (FALSE);
   }

	/* Compute the integral version value */
  if (IsVersion("v2.5")) 
    m_Version = DF3D_VERSION_25;
  else if (IsVersion("v2.6")) 
    m_Version = DF3D_VERSION_26;
  else if (IsVersion("v2.7")) 
    m_Version = DF3D_VERSION_27;
  else {
    m_Version = DF3D_VERSION_UNKNOWN;
    ErrorHandler.AddError(DFERR_3D_BADVERSION, "Version='%4.4s'", m_Header.Version);
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadHeader()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadPoints (pFileHandle);
 *
 * Protected class method to input the points data from the current position
 * in the file stream.  Returns FALSE on any error.  The points array is
 * destroyed and allocated as required.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadPoints (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::ReadPoints()");
  size_t Result; 

	/* Delete the previous array, if any */
  DestroyArrayPointer(m_pPoints);

	/* Reallocate points array */
  ASSERT(GetNumPoints() > 0);
  CreateArrayPointer(m_pPoints, df3dpoint_t, GetNumPoints());
  
	/* Attempt to read the points all at once */
  Result = fread(m_pPoints, sizeof(df3dpoint_t), GetNumPoints(), pFileHandle);

	/* Ensure points were read successfully */
  if (Result != (size_t) GetNumPoints()) {
    ErrorHandler.AddError(ERR_SYSTEM, errno);
    ErrorHandler.AddError(DFERR_3D_BADPOINTS);
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadPoints()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadFaces (pFileHandle);
 *
 * Protected class method to input the face data from the current position
 * in the file stream.  Returns FALSE on any error.  The face array is
 * destroyed and allocated as required.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadFaces (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::ReadFaces()");
  size_t Result;
  size_t Index;

	/* Delete the previous array, if any */
  DestroyArrayPointer(m_pFaces);

	/* Reallocate face array */
  ASSERT(GetNumFaces() > 0);
  CreateArrayPointer(m_pFaces, df3dface_t, GetNumFaces());

	/* Read in faces one by one */
  for (Index = 0; Index < (size_t)GetNumFaces(); Index++) {

		/* Read in the first 8 bytes of face data */
    Result = fread (&(m_pFaces[Index]), 1, DF3D_FACE_RECORDSIZE, pFileHandle);
    if (Result != DF3D_FACE_RECORDSIZE) goto READFACE_ERROR;

		/* Read in remaining face data */
    Result = fread(&(m_pFaces[Index].FaceData[0]), sizeof(df3dfacedata_t), m_pFaces[Index].NumPoints, pFileHandle);
    if (Result != m_pFaces[Index].NumPoints) goto READFACE_ERROR;
   }

	/* Return success */  
  return (TRUE);

	/* Error handling code for method */
READFACE_ERROR:
  ErrorHandler.AddError(ERR_SYSTEM, errno);
  ErrorHandler.AddError(DFERR_3D_BADFACES);
  return (FALSE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadFaces()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadNormals (pFileHandle);
 *
 * Protected class method to input the normal data from the given position
 * in the file stream.  Returns FALSE on any error.  The normals array is
 * destroyed and allocated as required.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadNormals (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::ReadNormals()");
  size_t Result;

	/* Delete the previous array, if any */
  DestroyArrayPointer(m_pNormals);

	/* Reallocate normals array */
  ASSERT(GetNumFaces() > 0);
  CreateArrayPointer(m_pNormals, df3dpoint_t, GetNumFaces());
  
	/* Attempt to read the normals all at once */
  Result = fread(m_pNormals, sizeof(df3dpoint_t), GetNumFaces(), pFileHandle);

	/* Ensure normals were read successfully */
  if (Result != (size_t) GetNumFaces()) {
    ErrorHandler.AddError(ERR_SYSTEM, errno);
    ErrorHandler.AddError(DFERR_3D_BADNORMALS);
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadNormals()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadData1 (pFileHandle);
 *
 * Protected class method to input the data1 section from the current position
 * in the file stream.  Returns FALSE on any error.  The data1 array is
 * destroyed and allocated as required.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadData1 (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::ReadData1()");
  size_t Result;

	/* Delete the previous array, if any */
  DestroyArrayPointer(m_pData1);

	/* Reallocate data1 array */
  ASSERT(GetNumFaces() > 0);
  CreateArrayPointer(m_pData1, df3ddata1_t, GetNumFaces());
  
	/* Attempt to read the data1 records all at once */
  Result = fread(m_pData1, sizeof(df3ddata1_t), GetNumFaces(), pFileHandle);

	/* Ensure normals were read successfully */
  if (Result != (size_t) GetNumFaces()) {
    ErrorHandler.AddError(ERR_SYSTEM, errno);
    ErrorHandler.AddError(DFERR_3D_BADDATA1);
    return (FALSE);
   }

  return (TRUE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadData1()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - boolean ReadData2 (pFileHandle);
 *
 * Protected class method to input the data2 section from the current position
 * in the file stream.  Returns FALSE on any error.  The data2 array is
 * destroyed and allocated as required.
 *
 *=========================================================================*/
boolean CDF3dObject::ReadData2 (FILE* pFileHandle) {
  DEFINE_FUNCTION("CDF3dObject::ReadData2()");
  //DEFINE_STATIC_PROFILE(ReadData2Profile);
  size_t Result;
  int    SeekResult;
  long   Offset;
  size_t Index;

	/* Delete the previous array, if any */
  DestroyArrayPointer(m_pData2);

	/* Ignore if no data2 records to load */
  if (GetNumData2() == 0) return (TRUE);

	/* Reallocate data2 array */
  CreateArrayPointer(m_pData2, df3ddata2_t, GetNumData2());
  
	/* Read in data2 records one by one */
  for (Index = 0; Index < (size_t)GetNumData2(); Index++) {

		/* Read in the first 18 bytes of data2 record */
    Result = fread (&(m_pData2[Index]), 1, DF3D_DATA2_RECORDSIZE, pFileHandle);
    if (Result != DF3D_DATA2_RECORDSIZE) goto READDATA2_ERROR;

		/* Compute the size of the data2 subrecords, ensuring its valid */
    Offset = m_pData2[Index].NumSubRecords * (long)sizeof(df3ddata2_subrecord_t);

    if (Offset < 0 || Offset + ftell(pFileHandle) > m_StartOffset + m_FileSize) {
      ErrorHandler.AddError(DFERR_3D_DATA2SUBRECORD);
      return (FALSE);
     }

		/* TODO: Skip the remaining subrecords for now */
    SeekResult = fseek(pFileHandle, Offset, SEEK_CUR);
    if (SeekResult != 0) goto READDATA2_ERROR;
   }

  return (TRUE);

	/* Error handling code for method */
READDATA2_ERROR:
  ErrorHandler.AddError(ERR_SYSTEM, errno);
  ErrorHandler.AddError(DFERR_3D_BADDATA2);
  return (FALSE);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::ReadData2()
 *=========================================================================*/


/*===========================================================================
 *
 * Class CDF3dObject Method - int TagFaceTexture (TextureIndex, ImageIndex);
 *
 * Tag all faces that have the same texture and image indices.  Returns 
 * the number of faces tagged.  Is considered a const method as the tag
 * data is not considered part of the DF face data.
 *
 *=========================================================================*/
int CDF3dObject::TagFaceTexture (const int TextureIndex, const int ImageIndex) const {
  DEFINE_FUNCTION("CDF3dObject::TagFaceTexture()");
  int Count = 0;
  int FaceIndex;

  for (FaceIndex = 0; FaceIndex < GetNumFaces(); FaceIndex++) {
    if (m_pFaces[FaceIndex].TextureIndex == TextureIndex &&
        m_pFaces[FaceIndex].SubImageIndex == ImageIndex) {
      m_pFaces[FaceIndex].Tag = 1;
      Count++;
     }
   }

  return (Count);
 }
/*===========================================================================
 *		End of Class Method CDF3dObject::TagFaceTexture()
 *=========================================================================*/