/*===========================================================================
 *
 * File:	DF_3DSTex.CPP
 * Author:	Dave Humphrey (uesp@m0use.net)
 * Created On:	Friday, March 22, 2002
 *
 * Implements 3DS specific texture and material related export functions 
 * for Daggerfall 3D objects.
 *
 *=========================================================================*/

	/* Include Files */
#include "df3dexpo.h"
#include "bsa/df3dutil.h"
#include "dagger/image/dftexdat.h"


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

	/* Local type for matrix conversion parameters */
  typedef struct {
    float  X[4],  Y[4],  Z[4];
    float  U[4],  V[4];
   } df3duvparams_lt;

/*===========================================================================
 *		End of Local Variable Definitions
 *=========================================================================*/


/*===========================================================================
 *
 * Local Function - boolean l_ComputeDFUVMatrixXY (Matrix, Params);
 *
 * Computes the UV conversion parameters from the given input based on
 * the formula:
 *			U = AX + BY + D
 *
 * Returns FALSE on any error.  For use on faces with 0 Z-coordinates.
 *
 *=========================================================================*/
boolean l_ComputeDFUVMatrixXY (df3duvmatrix_t& Matrix, const df3duvparams_lt& Params) {
  float Determinant;
  float Xi[3], Yi[3], Zi[3];

	/* Compute the determinant of the coefficient matrix */
  Determinant = Params.X[0]*Params.Y[1] + Params.Y[0]*Params.X[2] + 
		Params.X[1]*Params.Y[2] - Params.Y[1]*Params.X[2] - 
		Params.Y[0]*Params.X[1] - Params.X[0]*Params.Y[2];

	/* Check for a singular matrix indicating no valid solution */
  if (Determinant == 0) {
    ErrorHandler.AddError(ERR_CUSTOM, "Cannot compute the UV conversion matrix, singular XY determinant!");
    return (FALSE);
   }

	/* Compute parameters of the the inverted XYZ matrix */
  Xi[0] = ( Params.Y[1] - Params.Y[2]) / Determinant;
  Xi[1] = (-Params.X[1] + Params.X[2]) / Determinant;
  Xi[2] = ( Params.X[1]*Params.Y[2] - Params.X[2]*Params.Y[1]) / Determinant;

  Yi[0] = (-Params.Y[0] + Params.Y[2]) / Determinant;
  Yi[1] = ( Params.X[0] - Params.X[2]) / Determinant;
  Yi[2] = (-Params.X[0]*Params.Y[2] + Params.X[2]*Params.Y[0]) / Determinant;

  Zi[0] = ( Params.Y[0] - Params.Y[1]) / Determinant;
  Zi[1] = (-Params.X[0] + Params.X[1]) / Determinant;
  Zi[2] = ( Params.X[0]*Params.Y[1] - Params.X[1]*Params.Y[0]) / Determinant;

	/* Compute the UV conversion parameters */
  Matrix.UA = (Params.U[0]*Xi[0] + Params.U[1]*Yi[0] + Params.U[2]*Zi[0]);
  Matrix.UB = (Params.U[0]*Xi[1] + Params.U[1]*Yi[1] + Params.U[2]*Zi[1]);
  Matrix.UC = (float) 0.0;
  Matrix.UD = (Params.U[0]*Xi[2] + Params.U[1]*Yi[2] + Params.U[2]*Zi[2]);;

  Matrix.VA = (Params.V[0]*Xi[0] + Params.V[1]*Yi[0] + Params.V[2]*Zi[0]);
  Matrix.VB = (Params.V[0]*Xi[1] + Params.V[1]*Yi[1] + Params.V[2]*Zi[1]);
  Matrix.VC = (float) 0.0;
  Matrix.VD = (Params.V[0]*Xi[2] + Params.V[1]*Yi[2] + Params.V[2]*Zi[2]);

  return (TRUE);
 }
/*===========================================================================
 *		End of Function l_ComputeDFUVMatrixXY()
 *=========================================================================*/


/*===========================================================================
 *
 * Local Function - boolean l_ComputeDFUVMatrixXZ (Matrix, Params);
 *
 * Computes the UV conversion parameters from the given input based on
 * the formula:
 *			U = AX + CZ + D
 *
 * Returns FALSE on any error.  For use on faces with 0 Y-coordinates.
 *
 *=========================================================================*/
boolean l_ComputeDFUVMatrixXZ (df3duvmatrix_t& Matrix, const df3duvparams_lt& Params) {
  float Determinant;
  float Xi[3], Yi[3], Zi[3];

	/* Compute the determinant of the coefficient matrix */
  Determinant = Params.X[0]*Params.Z[2] + Params.Z[1]*Params.X[2] +
		Params.Z[0]*Params.X[1] - Params.Z[0]*Params.X[2] - 
		Params.X[1]*Params.Z[2] - Params.X[0]*Params.Z[1];

	/* Check for a singular matrix indicating no valid solution */
  if (Determinant == 0) {
    ErrorHandler.AddError(ERR_CUSTOM, "Cannot compute the UV conversion matrix, singular XZ determinant!");
    return (FALSE);
   }

	/* Compute parameters of the the inverted XYZ matrix */
  Xi[0] = ( Params.Z[2] - Params.Z[1]) / Determinant;
  Xi[1] = (-Params.X[1]*Params.Z[2] + Params.X[2]*Params.Z[1]) / Determinant;
  Xi[2] = ( Params.X[1] - Params.X[2]) / Determinant;

  Yi[0] = (-Params.Z[2] + Params.Z[0]) / Determinant;
  Yi[1] = ( Params.X[0]*Params.Z[2] - Params.X[2]*Params.Z[0]) / Determinant;
  Yi[2] = (-Params.X[0] + Params.X[2]) / Determinant;

  Zi[0] = ( Params.Z[1] - Params.Z[0]) / Determinant;
  Zi[1] = (-Params.X[0]*Params.Z[1] + Params.X[1]*Params.Z[0]) / Determinant;
  Zi[2] = ( Params.X[0] - Params.X[1]) / Determinant;

	/* Compute the UV conversion parameters */
  Matrix.UA = (Params.U[0]*Xi[0] + Params.U[1]*Yi[0] + Params.U[2]*Zi[0]);
  Matrix.UB = (float) 0.0;
  Matrix.UC = (Params.U[0]*Xi[2] + Params.U[1]*Yi[2] + Params.U[2]*Zi[2]);
  Matrix.UD = (Params.U[0]*Xi[1] + Params.U[1]*Yi[1] + Params.U[2]*Zi[1]);

  Matrix.VA = (Params.V[0]*Xi[0] + Params.V[1]*Yi[0] + Params.V[2]*Zi[0]);
  Matrix.VB = (float) 0.0;
  Matrix.VC = (Params.V[0]*Xi[2] + Params.V[1]*Yi[2] + Params.V[2]*Zi[2]);
  Matrix.VD = (Params.V[0]*Xi[1] + Params.V[1]*Yi[1] + Params.V[2]*Zi[1]);

  return (TRUE);
 }
/*===========================================================================
 *		End of Function l_ComputeDFUVMatrixXZ()
 *=========================================================================*/


/*===========================================================================
 *
 * Local Function - boolean l_ComputeDFUVMatrixYZ (Matrix, Params);
 *
 * Computes the UV conversion parameters from the given input based on
 * the formula:
 *			U = BY + CZ + D
 *
 * Returns FALSE on any error.  For use on faces with 0 X-coordinates.
 *
 *=========================================================================*/
boolean l_ComputeDFUVMatrixYZ (df3duvmatrix_t& Matrix, const df3duvparams_lt& Params) {
  float Determinant;
  float Xi[3], Yi[3], Zi[3];

	/* Compute the determinant of the coefficient matrix */
  Determinant = Params.Y[1]*Params.Z[2] + Params.Y[0]*Params.Z[1] + 
		Params.Z[0]*Params.Y[2] - Params.Z[0]*Params.Y[1] - 
		Params.Y[0]*Params.Z[2] - Params.Z[1]*Params.Y[2];

	/* Check for a singular matrix indicating no valid solution */
  if (Determinant == 0) {
    ErrorHandler.AddError(ERR_CUSTOM, "Cannot compute the UV conversion matrix, singular YZ determinant!");
    return (FALSE);
   }

	/* Compute parameters of the the inverted XYZ matrix */
  Xi[0] = ( Params.Y[1]*Params.Z[2] - Params.Y[2]*Params.Z[1]) / Determinant;
  Xi[1] = (-Params.Z[2] + Params.Z[1]) / Determinant;
  Xi[2] = ( Params.Y[2] - Params.Y[1]) / Determinant;

  Yi[0] = (-Params.Y[0]*Params.Z[2] + Params.Y[2]*Params.Z[0]) / Determinant;
  Yi[1] = ( Params.Z[2] - Params.Z[0]) / Determinant;
  Yi[2] = (-Params.Y[2] + Params.Y[0]) / Determinant;

  Zi[0] = ( Params.Y[0]*Params.Z[1] - Params.Y[1]*Params.Z[0]) / Determinant;
  Zi[1] = (-Params.Z[1] + Params.Z[0]) / Determinant;
  Zi[2] = ( Params.Y[1] - Params.Y[0]) / Determinant;

	/* Compute the UV conversion parameters */
  Matrix.UA = (float) 0.0;
  Matrix.UB = (Params.U[0]*Xi[1] + Params.U[1]*Yi[1] + Params.U[2]*Zi[1]);
  Matrix.UC = (Params.U[0]*Xi[2] + Params.U[1]*Yi[2] + Params.U[2]*Zi[2]);
  Matrix.UD = (Params.U[0]*Xi[0] + Params.U[1]*Yi[0] + Params.U[2]*Zi[0]);

  Matrix.VA = (float) 0.0;
  Matrix.VB = (Params.V[0]*Xi[1] + Params.V[1]*Yi[1] + Params.V[2]*Zi[1]);
  Matrix.VC = (Params.V[0]*Xi[2] + Params.V[1]*Yi[2] + Params.V[2]*Zi[2]);
  Matrix.VD = (Params.V[0]*Xi[0] + Params.V[1]*Yi[0] + Params.V[2]*Zi[0]);

  return (TRUE);
 }
/*===========================================================================
 *		End of Function l_ComputeDFUVMatrixYZ()
 *=========================================================================*/


/*===========================================================================
 *
 * Local Function - boolean l_ComputeDFUVMatrixXYZ (Matrix, Params);
 *
 * Computes the UV conversion parameters from the given input based on
 * the formula:
 *			U = AX + BY + CZ
 *
 * Returns FALSE on any error.
 *
 *=========================================================================*/
boolean l_ComputeDFUVMatrixXYZ (df3duvmatrix_t& Matrix, const df3duvparams_lt& Params) {
  float Determinant;
  float Xi[3], Yi[3], Zi[3];

	/* Compute the determinant of the coefficient matrix */
  Determinant = Params.X[0]*Params.Y[1]*Params.Z[2] + 
		Params.Y[0]*Params.Z[1]*Params.X[2] + 
		Params.Z[0]*Params.X[1]*Params.Y[2] - 
		Params.Z[0]*Params.Y[1]*Params.X[2] - 
		Params.Y[0]*Params.X[1]*Params.Z[2] - 
		Params.X[0]*Params.Z[1]*Params.Y[2];

	/* Check for a singular matrix indicating no valid solution */
  if (Determinant == 0) {
    ErrorHandler.AddError(ERR_CUSTOM, "Cannot compute the UV conversion matrix, singular XYZ determinant!");
    return (FALSE);
   }

	/* Compute values of the the inverted XYZ matrix */
  Xi[0] = ( Params.Y[1]*Params.Z[2] - Params.Y[2]*Params.Z[1]) / Determinant;
  Xi[1] = (-Params.X[1]*Params.Z[2] + Params.X[2]*Params.Z[1]) / Determinant;
  Xi[2] = ( Params.X[1]*Params.Y[2] - Params.X[2]*Params.Y[1]) / Determinant;

  Yi[0] = (-Params.Y[0]*Params.Z[2] + Params.Y[2]*Params.Z[0]) / Determinant;
  Yi[1] = ( Params.X[0]*Params.Z[2] - Params.X[2]*Params.Z[0]) / Determinant;
  Yi[2] = (-Params.X[0]*Params.Y[2] + Params.X[2]*Params.Y[0]) / Determinant;

  Zi[0] = ( Params.Y[0]*Params.Z[1] - Params.Y[1]*Params.Z[0]) / Determinant;
  Zi[1] = (-Params.X[0]*Params.Z[1] + Params.X[1]*Params.Z[0]) / Determinant;
  Zi[2] = ( Params.X[0]*Params.Y[1] - Params.X[1]*Params.Y[0]) / Determinant;

	/* Compute the UV conversion parameters */
  Matrix.UA = (Params.U[0]*Xi[0] + Params.U[1]*Yi[0] + Params.U[2]*Zi[0]);
  Matrix.UB = (Params.U[0]*Xi[1] + Params.U[1]*Yi[1] + Params.U[2]*Zi[1]);
  Matrix.UC = (Params.U[0]*Xi[2] + Params.U[1]*Yi[2] + Params.U[2]*Zi[2]);
  Matrix.UD = (float) 0.0;

  Matrix.VA = (Params.V[0]*Xi[0] + Params.V[1]*Yi[0] + Params.V[2]*Zi[0]);
  Matrix.VB = (Params.V[0]*Xi[1] + Params.V[1]*Yi[1] + Params.V[2]*Zi[1]);
  Matrix.VC = (Params.V[0]*Xi[2] + Params.V[1]*Yi[2] + Params.V[2]*Zi[2]);
  Matrix.VD = (float) 0.0;

  return (TRUE);
 }
/*===========================================================================
 *		End of Function l_ComputeDFUVMatrixXYZ()
 *=========================================================================*/


/*===========================================================================
 *
 * Local Function - boolean l_ComputeDFUVMatrixXYZ1 (Matrix, Params);
 *
 * Computes the UV conversion parameters from the given input based on
 * the formula:
 *			U = AX + BY + CZ + D
 *
 * Returns FALSE on any error.  Only valid for faces that have more than
 * 3 points.
 *
 *=========================================================================*/
boolean l_ComputeDFUVMatrixXYZ1 (df3duvmatrix_t& Matrix, const df3duvparams_lt& Params) {
  float Determinant;
  float Xi[4], Yi[4], Zi[4], Ai[4];

	/* Compute the determinant of the coefficient matrix */
  Determinant =  Params.X[0]*Params.Y[1]*Params.Z[2]
		-Params.X[0]*Params.Y[1]*Params.Z[3]
		+Params.X[0]*Params.Z[1]*Params.Y[3]
		-Params.X[0]*Params.Z[1]*Params.Y[2]
		+Params.X[0]*Params.Y[2]*Params.Z[3]
		-Params.X[0]*Params.Z[2]*Params.Y[3]
		-Params.Y[0]*Params.Z[1]*Params.X[3]
		+Params.Y[0]*Params.Z[1]*Params.X[2]
		-Params.Y[0]*Params.X[2]*Params.Z[3]
		+Params.Y[0]*Params.Z[2]*Params.X[3]
		-Params.Y[0]*Params.X[1]*Params.Z[2]
		+Params.Y[0]*Params.X[1]*Params.Z[3]
		+Params.Z[0]*Params.X[2]*Params.Y[3]
		-Params.Z[0]*Params.Y[2]*Params.X[3]
		+Params.Z[0]*Params.X[1]*Params.Y[2]
		-Params.Z[0]*Params.X[1]*Params.Y[3]
		+Params.Z[0]*Params.Y[1]*Params.X[3]
		-Params.Z[0]*Params.Y[1]*Params.X[2]
		-Params.X[1]*Params.Y[2]*Params.Z[3]
		+Params.X[1]*Params.Z[2]*Params.Y[3]
		-Params.Y[1]*Params.Z[2]*Params.X[3]
		+Params.Y[1]*Params.X[2]*Params.Z[3]
		-Params.Z[1]*Params.X[2]*Params.Y[3]
		+Params.Z[1]*Params.Y[2]*Params.X[3];

	/* Check for a singular matrix indicating no valid solution */
  if (Determinant == 0) {
    ErrorHandler.AddError(ERR_CUSTOM, "Cannot compute the UV conversion matrix, singular XYZ1 determinant!");
    return (FALSE);
   }

	/* Compute values of the the inverted XYZ matrix */
  Xi[0] =  Params.Y[1]*Params.Z[2] + Params.Z[1]*Params.Y[3] + Params.Y[2]*Params.Z[3] -
	   Params.Y[3]*Params.Z[2] - Params.Y[2]*Params.Z[1] - Params.Y[1]*Params.Z[3];
  Xi[1]	= -Params.X[1]*Params.Z[2] - Params.Z[1]*Params.X[3] - Params.X[2]*Params.Z[3] + 
	   Params.X[3]*Params.Z[2] + Params.X[2]*Params.Z[1] + Params.X[1]*Params.Z[3];
  Xi[2] =  Params.X[1]*Params.Y[2] + Params.Y[1]*Params.X[3] + Params.X[2]*Params.Y[3] - 
	   Params.X[3]*Params.Y[2] - Params.X[2]*Params.Y[1] - Params.X[1]*Params.Y[3];
  Xi[3] = -Params.X[1]*Params.Y[2]*Params.Z[3] - Params.Y[1]*Params.Z[2]*Params.X[3] - Params.Z[1]*Params.X[2]*Params.Y[3] +
	   Params.X[3]*Params.Y[2]*Params.Z[1] + Params.X[2]*Params.Y[1]*Params.Z[3] + Params.X[1]*Params.Y[3]*Params.Z[2];
  Yi[0] = -Params.Y[0]*Params.Z[2] - Params.Z[0]*Params.Y[3] - Params.Y[2]*Params.Z[3] + 
	   Params.Y[3]*Params.Z[2] + Params.Y[2]*Params.Z[0] + Params.Y[0]*Params.Z[3];
  Yi[1] =  Params.X[0]*Params.Z[2] + Params.Z[0]*Params.X[3] + Params.X[2]*Params.Z[3] -
	   Params.X[3]*Params.Z[2] - Params.X[2]*Params.Z[0] - Params.X[0]*Params.Z[3];
  Yi[2] = -Params.X[0]*Params.Y[2] - Params.Y[0]*Params.X[3] - Params.X[2]*Params.Y[3] + 
	   Params.X[3]*Params.Y[2] + Params.X[2]*Params.Y[0] + Params.X[0]*Params.Y[3];
  Yi[3] =  Params.X[0]*Params.Y[2]*Params.Z[3] + Params.Y[0]*Params.Z[2]*Params.X[3] + Params.Z[0]*Params.X[2]*Params.Y[3] - 
	   Params.X[3]*Params.Y[2]*Params.Z[0] - Params.X[2]*Params.Y[0]*Params.Z[3] - Params.X[0]*Params.Y[3]*Params.Z[2];
  Zi[0] =  Params.Y[0]*Params.Z[1] + Params.Z[0]*Params.Y[3] + Params.Y[1]*Params.Z[3]
	  -Params.Y[3]*Params.Z[1] - Params.Y[0]*Params.Z[0] - Params.Y[0]*Params.Z[3];
  Zi[1] = -Params.X[0]*Params.Z[1] - Params.Z[0]*Params.Y[3] - Params.X[1]*Params.Z[3] +
	   Params.X[3]*Params.Z[1] + Params.X[1]*Params.Z[0] + Params.X[0]*Params.Z[3];
  Zi[2] =  Params.X[0]*Params.Y[1] + Params.Y[0]*Params.X[3] + Params.X[1]*Params.Y[3] -
	   Params.X[3]*Params.Y[1] - Params.X[1]*Params.Y[0] - Params.X[0]*Params.Y[3];
  Zi[3] = -Params.X[0]*Params.Y[1]*Params.Z[3] - Params.Y[0]*Params.Z[1]*Params.X[3] - Params.Z[0]*Params.X[1]*Params.Y[3] +
	   Params.X[3]*Params.Y[1]*Params.Z[0] + Params.Z[1]*Params.Y[0]*Params.Z[3] + Params.X[0]*Params.Y[3]*Params.Z[1];
  Ai[0] = -Params.Y[0]*Params.Z[1] - Params.Z[0]*Params.Y[2] - Params.Y[1]*Params.Z[2] +
	   Params.Y[2]*Params.Z[1] + Params.Y[1]*Params.Z[0] + Params.Y[0]*Params.Z[2];
  Ai[1] =  Params.X[0]*Params.Z[1] + Params.Z[0]*Params.X[2] + Params.X[1]*Params.Z[2] -
	   Params.X[1]*Params.Z[1] - Params.X[1]*Params.Z[0] - Params.X[0]*Params.Z[2];
  Ai[2] = -Params.X[0]*Params.Y[1] - Params.Y[0]*Params.X[2] - Params.X[1]*Params.Y[2] +
	   Params.X[2]*Params.Y[1] + Params.X[1]*Params.Y[0] + Params.X[0]*Params.Y[2];
  Ai[3] =  Params.X[0]*Params.Y[1]*Params.Z[2] + Params.Y[0]*Params.Z[1]*Params.X[2] + Params.Z[0]*Params.X[1]*Params.Y[2] -
	   Params.X[2]*Params.Y[1]*Params.Z[0] - Params.X[1]*Params.Y[0]*Params.Z[2] - Params.X[0]*Params.Y[2]*Params.Z[1];

	/* Compute the UV conversion parameters */
  Matrix.UA = (Params.U[0]*Xi[0] + Params.U[1]*Yi[0] + Params.U[2]*Zi[0] + Params.U[3]*Ai[0]) / Determinant;
  Matrix.UB = (Params.U[0]*Xi[1] + Params.U[1]*Yi[1] + Params.U[2]*Zi[1] + Params.U[3]*Ai[1]) / Determinant;
  Matrix.UC = (Params.U[0]*Xi[2] + Params.U[1]*Yi[2] + Params.U[2]*Zi[2] + Params.U[3]*Ai[2]) / Determinant;
  Matrix.UD = (Params.U[0]*Xi[3] + Params.U[1]*Yi[3] + Params.U[2]*Zi[3] + Params.U[3]*Ai[3]) / Determinant;

  Matrix.VA = (Params.V[0]*Xi[0] + Params.V[1]*Yi[0] + Params.V[2]*Zi[0] + Params.V[3]*Ai[0]) / Determinant;
  Matrix.VB = (Params.V[0]*Xi[1] + Params.V[1]*Yi[1] + Params.V[2]*Zi[1] + Params.V[3]*Ai[1]) / Determinant;
  Matrix.VC = (Params.V[0]*Xi[2] + Params.V[1]*Yi[2] + Params.V[2]*Zi[2] + Params.V[3]*Ai[2]) / Determinant;
  Matrix.VD = (Params.V[0]*Xi[3] + Params.V[1]*Yi[3] + Params.V[2]*Zi[3] + Params.V[3]*Ai[3]) / Determinant;

  return (TRUE);
 }
/*===========================================================================
 *		End of Function l_ComputeDFUVMatrixXYZ1()
 *=========================================================================*/



/*===========================================================================
 *
 * Function - boolean ComputeDFFaceTextureUVMatrix (Matrix, Face, Object);
 *
 * Calculates the transformation parameters to convert the face XYZ coordinates
 * to texture UV coordinates for the given face, storing in the given matrix.
 * Returns FALSE on any error.  The function computes the A, B...F parameters
 * for the equations:
 *			U = AX + BY + CZ
 *			V = DX + EY + FZ
 *
 *=========================================================================*/
boolean ComputeDFFaceTextureUVMatrix (df3duvmatrix_t& Matrix, const df3dface_t& Face,
				      const CDF3dObject& Object) {
  DEFINE_FUNCTION("ComputeDFFaceTextureUVMatrix()");
  df3dpoint_t*	  pPoint;
  df3duvparams_lt Params;
  boolean	  Result;

	/* Ensure valid input */
  ASSERT(Face.NumPoints >= 3);

	/* Initialize the conversion matrix */
  Matrix.UA = (float) 1.0;
  Matrix.UB = (float) 0.0;
  Matrix.UC = (float) 0.0;
  Matrix.UD = (float) 0.0;
  Matrix.VA = (float) 0.0;
  Matrix.VB = (float) 1.0;
  Matrix.VC = (float) 0.0;
  Matrix.UD = (float) 0.0;

	/* Store the first 3 points of texture coordinates */
  Params.U[0] = (float) Face.FaceData[0].TextureU;
  Params.U[1] = (float) Face.FaceData[1].TextureU + Params.U[0];
  Params.U[2] = (float) Face.FaceData[2].TextureU + Params.U[1];
  Params.V[0] = (float) Face.FaceData[0].TextureV;
  Params.V[1] = (float) Face.FaceData[1].TextureV + Params.V[0];
  Params.V[2] = (float) Face.FaceData[2].TextureV + Params.V[1];

	/* Get and store the 1st point coordinates in face */
  pPoint = ((CDF3dObject &)Object).GetFacePoint(Face.FaceData[0].PointOffset);
  Params.X[0] = (float) pPoint->X;
  Params.Y[0] = (float) pPoint->Y;
  Params.Z[0] = (float) pPoint->Z;

	/* Get and store the 2nd point coordinates in face */
  pPoint = ((CDF3dObject &)Object).GetFacePoint(Face.FaceData[1].PointOffset);
  Params.X[1] = (float) pPoint->X;
  Params.Y[1] = (float) pPoint->Y;
  Params.Z[1] = (float) pPoint->Z;

	/* Get and store the 3rd point coordinates in face */
  pPoint = ((CDF3dObject &)Object).GetFacePoint(Face.FaceData[2].PointOffset);
  Params.X[2] = (float) pPoint->X;
  Params.Y[2] = (float) pPoint->Y;
  Params.Z[2] = (float) pPoint->Z;

	/* get and store the 4th store coordinates, if any */
  if (Face.NumPoints > 3) {
    pPoint = ((CDF3dObject &)Object).GetFacePoint(Face.FaceData[3].PointOffset);
    Params.X[3] = (float) pPoint->X;
    Params.Y[3] = (float) pPoint->Y;
    Params.Z[3] = (float) pPoint->Z;
    Params.U[3] = (float) Face.FaceData[3].TextureU;
    Params.V[3] = (float) Face.FaceData[3].TextureV;

    	/* Attempt to use a 4x4 matrix solution if the previous ones failed */
    //Result = l_ComputeDFUVMatrixXYZ1(Matrix, Params);
    //SystemLog.Printf ("\tMatrixXYZ1 = %d", Result);
    //if (Result) return (TRUE);
   }
  else {
    Params.X[3] = (float) 0.0;
    Params.Y[3] = (float) 0.0;
    Params.Z[3] = (float) 0.0;
    Params.U[3] = (float) 0.0;
    Params.V[3] = (float) 0.0;
   }

	/* Compute the solution using an XY linear equation */
  if (Params.X[0] == Params.Z[0] && Params.X[1] == Params.Z[1] && Params.X[2] == Params.Z[2]) {
    Result = l_ComputeDFUVMatrixXY(Matrix, Params); 
   }
	/* Compute the solution using an XZ linear equation */
  else if (Params.Y[0] == Params.Y[1] && Params.Y[1] == Params.Y[2]) {
    Result = l_ComputeDFUVMatrixXZ(Matrix, Params); 
   }
	/* Compute the solution using an XY linear equation */
  else if (Params.Z[0] == Params.Z[1] && Params.Z[1] == Params.Z[2]) {
    Result = l_ComputeDFUVMatrixXY(Matrix, Params); 
   }
	/* Compute the solution using an YZ linear equation */
  else if (Params.X[0] == Params.X[1] && Params.X[1] == Params.X[2]) {
    Result = l_ComputeDFUVMatrixYZ(Matrix, Params); 
   }
	/* Compute the solution using an XYZ linear equation */
  else {
    Result = l_ComputeDFUVMatrixXYZ(Matrix, Params);
   }

	/* On failure try different solutions */
  if (!Result) {
    Result = l_ComputeDFUVMatrixXYZ(Matrix, Params);
    if (!Result) Result = l_ComputeDFUVMatrixXY(Matrix, Params); 
    if (!Result) Result = l_ComputeDFUVMatrixXZ(Matrix, Params); 
    if (!Result) Result = l_ComputeDFUVMatrixYZ(Matrix, Params); 
   }

	/* Attempt to use a 4x4 matrix solution if the previous ones failed */
	/*
  if (!Result) {
    Result = l_ComputeDFUVMatrixXYZ1(Matrix, Params);

    #if defined(_DEBUG)
      SystemLog.Printf ("\tMatrixXYZ1 = %d", Result);
    #endif
   } //*/

  #if defined(_DEBUG)
    //SystemLog.Printf ("\tMatrixU = %f, %f, %f", Matrix.UA, Matrix.UB, Matrix.UC);
    //SystemLog.Printf ("\tMatrixV = %f, %f, %f", Matrix.VA, Matrix.VB, Matrix.VC);

    if (!Result) {
      SystemLog.Printf ("\tNumPoints = %d,   Texture = %d", Face.NumPoints, Face.TextureIndex);
      SystemLog.Printf ("\t%6g, %6g, %6g = %6g, %6g", Params.X[0], Params.Y[0], Params.Z[0], Params.U[0], Params.V[0]);
      SystemLog.Printf ("\t%6g, %6g, %6g = %6g, %6g", Params.X[1], Params.Y[1], Params.Z[1], Params.U[1], Params.V[1]);
      SystemLog.Printf ("\t%6g, %6g, %6g = %6g, %6g", Params.X[2], Params.Y[2], Params.Z[2], Params.U[2], Params.V[2]);
      SystemLog.Printf ("\t%6g, %6g, %6g = %6g, %6g", Params.X[3], Params.Y[3], Params.Z[3], Params.U[3], Params.V[3]);
     }
  #endif

  return (Result);
 }
/*===========================================================================
 *		End of Function ComputeDFFaceTextureUVMatrix()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - char* CreateDF3DSMaterialName (TextureIndex, ImageIndex);
 *
 * Creates a stadard material name from the given texture indices. Returns
 * a valid static string which is overwritten on subsequent calls.
 *
 *=========================================================================*/
char* CreateDF3DSMaterialName (const int TextureIndex, const int ImageIndex) {
  static char s_MatName[64];

  sprintf (s_MatName, "DF%03d_%03d", TextureIndex, ImageIndex);
  return (s_MatName);
 }
/*===========================================================================
 *		End of Function CreateDF3DSMaterialName()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - char* CreateDF3DSTextureName (TextureIndex, ImageIndex);
 *
 * Creates a standard texture name from the given texture indices. Returns
 * a valid static string which is overwritten on subsequent calls.  The
 * texture name should conform to the standard 8.3 DOS filename format.
 *
 *=========================================================================*/
char* CreateDF3DSTextureName (const int TextureIndex, const int ImageIndex) {
  static char s_TextureName[64];

  sprintf (s_TextureName, "df%03d%03d.bmp", TextureIndex, ImageIndex);
  return (s_TextureName);
 }
/*===========================================================================
 *		End of Function CreateDF3DSTextureName()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - int l_ExportDFMaterialTo3DS (DFFace, pData);
 *
 * Export the material for the given face to the 3DS file (contained in the
 * callback data pointer).  Only outputs textures not previously output by
 * checking the face tag.  Returns a negative value on any error.  Used
 * as a callback function for the CDF3dObject::ForEachFace() method.
 *
 *=========================================================================*/
int l_ExportDFMaterialTo3DS (df3dface_t& DFFace, void* pData) {
  DEFINE_FUNCTION("l_ExportDFMaterialTo3DS()");
  df3dexport3ds_t* pCallbackData = (df3dexport3ds_t *) pData;
  boolean Result;
  char*   pMatName;
  char*   pTextureName;

	/* Ignore any tagged faces */
  if (DFFace.Tag != 0) return (0);

	/* Create the material and texture names */
  pMatName	= CreateDF3DSMaterialName(DFFace.TextureIndex, DFFace.SubImageIndex);
  pTextureName	= CreateDF3DSTextureName (DFFace.TextureIndex, DFFace.SubImageIndex);

	/* Check for the special case of a solid color texture */
  if (IsDFTextureSolidColor(DFFace.TextureIndex)) {
    rgbpalraw_t RawColor = GetDFTextureSolidColor(DFFace.TextureIndex, DFFace.SubImageIndex);

    Result = pCallbackData->pFile3DS->OutputMatEntry(pMatName, ConvertRawRGBPal(RawColor));
   }
	/* Export a textured material */
  else {
    Result = pCallbackData->pFile3DS->OutputMatEntry(pMatName, pTextureName); 
   }
   
	/* Tag the face and all faces with the same texture to ensure
	 * that we don't duplicate materials */
  ((CDF3dObject *)pCallbackData->pObject)->TagFaceTexture(DFFace.TextureIndex, DFFace.SubImageIndex);

  if (!Result) return (-1);
  return (0);
 }
/*===========================================================================
 *		End of Function l_ExportDFMaterialTo3DS()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - int l_ExportDFMaterialGroupTo3DS (DFFace, pData);
 *
 * Callback functionm for the CDF3dObject::ForEachFace() method.  Outputs
 * the material face groups to the 3DS file.  Returns a negative value
 * on any error.
 *
 *=========================================================================*/
int l_ExportDFMaterialGroupTo3DS (const df3dface_t& DFFace, void* pData) {
  DEFINE_FUNCTION("l_ExportDFMaterialGroupTo3DS()");
  df3dexport3ds_t* pCallbackData = (df3dexport3ds_t *) pData;
  df3dface_t* pFace;
  boolean     Result;
  char*       pMatName;
  short       FaceGroupCount;
  int         Count;
  int         SubFaceIndex;
  long        FaceIndex;
  long        FaceCount;
  long	      CountOffset;
  long        SaveOffset;

	/* Ignore any tagged faces as they have already been processed */
  if (DFFace.Tag != 0) return (0);

	/* Get the number of faces in the group and material name */
  Count = pCallbackData->pObject->TagFaceTexture(DFFace.TextureIndex, DFFace.SubImageIndex);
  pMatName = CreateDF3DSMaterialName(DFFace.TextureIndex, DFFace.SubImageIndex);
  FaceCount = 0;
  FaceGroupCount = 0;

	/* Output the material group chunk header */
  Result  = pCallbackData->pFile3DS->StartMatGroupChunk(pMatName, Count);
  CountOffset = pCallbackData->pFile3DS->Tell() - 2;
  if (!Result) return (-1);

	/* Output the array of face indices */
  for (FaceIndex = 0; FaceIndex < pCallbackData->pObject->GetNumFaces(); FaceIndex++) {
    pFace = ((CDF3dObject *) pCallbackData->pObject)->GetFace(FaceIndex);

		/* Output only the faces with the same texture indices */
    if (pFace->TextureIndex  == DFFace.TextureIndex && 
        pFace->SubImageIndex == DFFace.SubImageIndex) {

		/* Output all sub-faces in the DF face (triangle fan) */
      for (SubFaceIndex = 0; SubFaceIndex < pFace->NumPoints - 2; SubFaceIndex++) {
        Result &= pCallbackData->pFile3DS->WriteShort(FaceCount + SubFaceIndex);
	FaceGroupCount++;
       }
     }

    FaceCount += pFace->NumPoints - 2;
   }

	/* Update the face array count */
  SaveOffset = pCallbackData->pFile3DS->Tell();
  Result &= pCallbackData->pFile3DS->Seek(CountOffset, SEEK_SET);
  Result &= pCallbackData->pFile3DS->WriteShort(FaceGroupCount);
  Result &= pCallbackData->pFile3DS->Seek(SaveOffset, SEEK_SET);

	/* End the current material group chunk */
  Result &= pCallbackData->pFile3DS->EndMatGroupChunk();

  if (!Result) return (-1);
  return (0);
 }
/*===========================================================================
 *		End of Function l_ExportDFMaterialGroupTo3DS()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - int l_ExportDFTexVertTo3DS (DFFace, pData);
 *
 * Outputs one texture vertex to the 3DS file.  Returns negative on any error.
 * Used as a callback for the CDF3dObject::ForEachFaceC() method.
 *
 *=========================================================================*/
int l_ExportDFTexVertTo3DS (const df3dface_t& DFFace, void* pData) {
  DEFINE_FUNCTION("l_ExportDFTexVertTo3DS()");
  df3dexport3ds_t* pCallbackData = (df3dexport3ds_t *) pData;
  df3duvmatrix_t   UVMatrix;
  df3dpoint_t*     pPoint;
  boolean	   Result;
  int		   FaceIndex;
  float		   X, Y;
  float		   U, V;
  int		   Width;
  int		   Height;
  int		   ReturnValue = 0;

	/* Get the texture width/height and ensure they are valid */
  Width  = GetDFTextureWidth(DFFace.TextureIndex, DFFace.SubImageIndex);
  Height = GetDFTextureHeight(DFFace.TextureIndex, DFFace.SubImageIndex);
  if (Width <= 0)  Width  = 64;
  if (Height <= 0) Height = 64;

	/* Compute the XYZ-UV conversion matrix for the face if required */
  if (DFFace.TextureIndex >= 2 && DFFace.NumPoints > 4) {
    Result = ComputeDFFaceTextureUVMatrix(UVMatrix, DFFace, *pCallbackData->pObject);
    if (!Result) ReturnValue = 1;
   }

	/* Output all the texture points in the face */
  for (FaceIndex = 0; FaceIndex < DFFace.NumPoints; FaceIndex++) {
    pPoint = ((CDF3dObject *) pCallbackData->pObject)->GetFacePoint(DFFace.FaceData[FaceIndex].PointOffset);
    ASSERT(pPoint != NULL);
    pCallbackData->PointIndex++;

		/* Convert DF XYZ coordinates to standard texture UV */
    if (DFFace.TextureIndex <= 1) {
      X = 0;
      Y = 0;
     }
    else if (FaceIndex > 3) {
      X = UVMatrix.UA * (float) pPoint->X + UVMatrix.UB * (float) pPoint->Y + UVMatrix.UC * (float) pPoint->Z + UVMatrix.UD;
      Y = UVMatrix.VA * (float) pPoint->X + UVMatrix.VB * (float) pPoint->Y + UVMatrix.VC * (float) pPoint->Z + UVMatrix.VD;
     }
    else if (FaceIndex == 0) {
      X = (float) DFFace.FaceData[0].TextureU;
      Y = (float) DFFace.FaceData[0].TextureV;
     }
    else if (FaceIndex == 1) {
      X = (float) DFFace.FaceData[0].TextureU + DFFace.FaceData[1].TextureU;
      Y = (float) DFFace.FaceData[0].TextureV + DFFace.FaceData[1].TextureV;
     }
    else if (FaceIndex == 2) {
      X = (float) DFFace.FaceData[0].TextureU + DFFace.FaceData[1].TextureU + DFFace.FaceData[2].TextureU;
      Y = (float) DFFace.FaceData[0].TextureV + DFFace.FaceData[1].TextureV + DFFace.FaceData[2].TextureV;
     }
    else if (FaceIndex == 3) {
      X = (float) DFFace.FaceData[3].TextureU;
      Y = (float) DFFace.FaceData[3].TextureV;
     }
    else {
      X = 0;
      Y = 0;
     }
  
	/* Convert from DF to standard UV coordinats */
    U = (float) (X / 16.0 / (float) Width);
    V = (float) (Y / 16.0 / (float) Height);

    /*SystemLog.Printf ("\t\t%02d: XYZ(%ld, %ld, %ld),  UV(%f, %f),  DfUV(%d, %d),  3DSUV(%f, %f)", FaceIndex,
			pPoint->X, pPoint->Y, pPoint->Z, X, Y, 
			DFFace.FaceData[FaceIndex].TextureU, DFFace.FaceData[FaceIndex].TextureV, 
			U, V);  //*/

		/* Output the texture vertex */
    Result = pCallbackData->pFile3DS->WriteTexVert(U, V);
    if (!Result) return (-1);
   }

  return (ReturnValue);
 }
/*===========================================================================
 *		End of Function l_ExportDFTexVertTo3DS()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - int ExportDFMaterialGroupsTo3DS (File3DS, DF3dObject);
 *
 * Creates the material groups in the 3DS file in the face array chunk after
 * the main face array (0x4120).  Each group of faces with the same material
 * has a 0x4130 chunk created with the material name and the list of face
 * indices.  Returns a negative value on any error.
 *
 *=========================================================================*/
int ExportDFMaterialGroupsTo3DS (C3dsFile& File3DS, const CDF3dObject& DF3dObject) {
  //DEFINE_FUNCTION("ExportDFMaterialGroupsTo3DS()");
  df3dexport3ds_t CallbackData;
  int Result;

	/* Initialize the object and callback data */
  DF3dObject.ClearFaceTags();
  CallbackData.ObjectVersion = 0;
  CallbackData.pFile3DS = &File3DS;
  CallbackData.pMeshMatrix = NULL;
  CallbackData.pObject = (CDF3dObject *) &DF3dObject;
  CallbackData.PointIndex = 0;

	/* Output all the material groups */
  Result = DF3dObject.ForEachFaceC(l_ExportDFMaterialGroupTo3DS, (void*) &CallbackData);
  return (Result);
 }
/*===========================================================================
 *		End of Function ExportDFMaterialGroupsTo3DS()
 *=========================================================================*/


/*===========================================================================
 *
 * Function - boolean ExportDFMaterialsTo3DS (File3DS, DF3dObject);
 *
 * Export the material section of the 3DS file, creating all materials 
 * required to display the given object, both textured and solid colors.
 * Returns FALSE on any error.
 *
 *=========================================================================*/
boolean ExportDFMaterialsTo3DS (C3dsFile& File3DS, const CDF3dObject& DF3dObject) {
  DEFINE_FUNCTION("ExportDFMaterialsTo3DS()");
  df3dexport3ds_t CallbackData;
  int		  Result;

	/* Initialize the callback data and 3D object */
  CallbackData.pFile3DS      = &File3DS;
  CallbackData.pObject       = (CDF3dObject *) &DF3dObject;
  CallbackData.pMeshMatrix   = NULL;
  CallbackData.PointIndex    = 0;
  CallbackData.ObjectVersion = 0;
  DF3dObject.ClearFaceTags();

	/* Output all materials required by the object */
  Result = ((CDF3dObject&)DF3dObject).ForEachFace(l_ExportDFMaterialTo3DS, (void *) &CallbackData);

  if (Result < 0) return (FALSE);
  return (TRUE);
 }
/*===========================================================================
 *		End of Function ExportDFMaterialsTo3DS()
 *=========================================================================*/