/*======================================================================*/
/* 3DLib								*/
/*									*/
/* Model-related functions						*/
/*									*/
/* AUTHOR:	Gabor Nagy						*/
/* DATE:	1996-Dec-06 23:14:15					*/
/*									*/
/* 3DLib(TM) Copyright (C) 1995 by Gabor Nagy. All rights reserved.	*/
/*======================================================================*/
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdarg.h>		// For varargs
#include <stdio.h>

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

#include <E3D/E3D.h>

#include <E3D/Face.h>

#include <E3D/Matrix.h>

#include <E3D/Mesh.h>

#include <E3D/Scene.h>


E3dModelClass**	E3d_CustomModelClasses=NULL;
int		E3d_NumOfCustomModelClasses=0;


//================================================================================
// Compute the bounding box of a Model in it's local coordinate system
//
// Arguments
//  E3dModel*      LModel      Pointer to the Model structure
//  E3d3DPosition* LBBMin      First corner of bounding box
//  E3d3DPosition* LBBMax      Opposite corner of bounding box
//
// Description
//  Computes the local-relative bounding box of a Model and returns it in
//  LBBMin and LBBMax.
//
// Return value
//  TRUE if there is a valid bounding box, FALSE if there isn't one (e.g. the
//  Model has no Geometry attached to it).
//================================================================================
EBool E3d_ModelGetBoundingBox(E3dModel* LModel, E3d3DPosition* LBBMin, E3d3DPosition* LBBMax, unsigned int LWhatToInclude)
{
 unsigned int	LGmN, LTWhatToInclude;
 EBool		LResult=FALSE;

 LGmN=LModel->NumOfGeometries;
 if(LGmN>0)
 {
  E3dGeometry**	LGeometries;
  E3dGeometry*	LGeometry;
  E3d3DPosition	LTBBMin, LTBBMax;
  unsigned int	LGmC;
  EBool		LFirst=TRUE, LGoOn, LModelSelected;


  E3dM_IsModelSelected(LModel, LModelSelected);

  LGeometries=LModel->Geometries;
  for(LGmC=0;LGmC<LGmN;LGmC++)
  {
   if((LGeometry=LGeometries[LGmC])!=NULL)
   {
     LTWhatToInclude=LWhatToInclude;
     switch(LWhatToInclude)
     {
      case E3dBB_ALL:	LGoOn=TRUE;break;

      case E3dBB_SELECTED_GEOMETRY:
       if(LModelSelected) { LTWhatToInclude=E3dBB_ALL;LGoOn=TRUE; }
       else if((LModel->Selection==E3dSEL_GEOMETRY)&&(LGeometry->Selection!=E3dGSEL_NONE)) LGoOn=TRUE;
       else LGoOn=FALSE;
      break;

      default:	LGoOn=FALSE;break;
     }
     if(LGoOn)
     {
       if(LFirst)
       {
	 E3d_GeometryGetBoundingBox(LGeometry, LBBMin, LBBMax, LTWhatToInclude);
         LResult=TRUE;
	 LFirst=FALSE;
       }
       else
       {
	 if(E3d_GeometryGetBoundingBox(LGeometry, &LTBBMin, &LTBBMax, LTWhatToInclude))
	 {
	   if(LTBBMin.X < LBBMin->X) LBBMin->X = LTBBMin.X;
	   if(LTBBMax.X > LBBMax->X) LBBMax->X = LTBBMax.X;

	   if(LTBBMin.Y < LBBMin->Y) LBBMin->Y = LTBBMin.Y;
	   if(LTBBMax.Y > LBBMax->Y) LBBMax->Y = LTBBMax.Y;

	   if(LTBBMin.Z < LBBMin->Z) LBBMin->Z = LTBBMin.Z;
	   if(LTBBMax.Z > LBBMax->Z) LBBMax->Z = LTBBMax.Z;
	   LResult=TRUE;
	 }
       }
     }
   }
  }
 }
 return(LResult);
}


//================================================================================
// Compute the bounding box of a Model in a given coordinate system
//
// Arguments
//  E3dModel*      LModel      Pointer to the Model structure
//  E3dMatrix      LMatrix     Matrix to transform the Geometry with
//  E3d3DPosition* LBBMin      First corner of bounding box
//  E3d3DPosition* LBBMax      Opposite corner of bounding box
//
// Description
//  Computes the bounding box of a Model in a coordinate system defined by
//  a Matrix and returns it in LBBMin and LBBMax.
//
// Return value
//  TRUE if there is a valid bounding box, FALSE if there isn't one (e.g. the
//  Model has no geometry attached to it).
//================================================================================
EBool E3d_ModelGetTransformedBoundingBox(E3dModel* LModel, E3dMatrix LMatrix, E3d3DPosition* LBBMin, E3d3DPosition* LBBMax, unsigned int LWhatToInclude)
{
 unsigned int	LGmN;
 EBool		LHasBB=FALSE;

 LGmN=LModel->NumOfGeometries;
 if(LGmN>0)
 {
  E3dGeometry**	LGeometries;
  E3dGeometry*	LGeometry;
  E3d3DPosition	LTBBMin, LTBBMax;
  unsigned int	LGmC;
  EBool		LFirst=TRUE, LGoOn, LModelSelected;

  switch(LModel->Selection)
  {
   case E3dSEL_NODE:
   case E3dSEL_BRANCH:
   case E3dSEL_BRANCH_ROOT:
    LModelSelected=TRUE;
   break;

   default:	LModelSelected=FALSE;break;
  }

  LGeometries=LModel->Geometries;
  for(LGmC=0;LGmC<LGmN;LGmC++)
  {
   if((LGeometry=LGeometries[LGmC])!=NULL)
   {
     switch(LWhatToInclude)
     {
      case E3dBB_ALL:	LGoOn=TRUE;break;

      case E3dBB_SELECTED_GEOMETRY:
        if(LModelSelected||((LModel->Selection==E3dSEL_GEOMETRY)&&(LGeometry->Selection!=E3dGSEL_NONE))) LGoOn=TRUE;
        else LGoOn=FALSE;
      break;

      default:	LGoOn=FALSE;break;
     }
     if(LGoOn)
     {
       if(LFirst)
       {
	 if(E3d_GeometryGetTransformedBoundingBox(LGeometry, LMatrix, LBBMin, LBBMax, LWhatToInclude))
	 {
	   LHasBB=TRUE;
	   LFirst=FALSE;
	 }
       }
       else
       {
	 if(E3d_GeometryGetTransformedBoundingBox(LGeometry, LMatrix, &LTBBMin, &LTBBMax, LWhatToInclude))
	 {
	   if(LTBBMin.X < LBBMin->X) LBBMin->X = LTBBMin.X;
	   if(LTBBMax.X > LBBMax->X) LBBMax->X = LTBBMax.X;

	   if(LTBBMin.Y < LBBMin->Y) LBBMin->Y = LTBBMin.Y;
	   if(LTBBMax.Y > LBBMax->Y) LBBMax->Y = LTBBMax.Y;

	   if(LTBBMin.Z < LBBMin->Z) LBBMin->Z = LTBBMin.Z;
	   if(LTBBMax.Z > LBBMax->Z) LBBMax->Z = LTBBMax.Z;
	   LHasBB=TRUE;
	 }
       }
     }
   }
  }
 }
 else
 {
  E3dCoordinate		mX=0.0, mY=0.0, mZ=0.0;
  E3dCoordinate		LX, LY, LZ;

  E3dM_MatrixTransform3x4(LMatrix, LX, LY, LZ);
  LBBMax->X=LBBMin->X=LX;
  LBBMax->Y=LBBMin->Y=LY;
  LBBMax->Z=LBBMin->Z=LZ;

  if(LModel->Type==E3dMDL_JOINT)
  {
   E3dModel*	LChildModel=LModel->Child;

   if(LChildModel)
   {
    mX=LChildModel->Translation.X;
    mY=LChildModel->Translation.Y;
    mZ=LChildModel->Translation.Z;
    E3dM_MatrixTransform3x4(LMatrix, LX, LY, LZ);

    if(LX < LBBMin->X) LBBMin->X = LX;
    if(LX > LBBMax->X) LBBMax->X = LX;

    if(LY < LBBMin->Y) LBBMin->Y = LY;
    if(LY > LBBMax->Y) LBBMax->Y = LY;

    if(LZ < LBBMin->Z) LBBMin->Z = LZ;
    if(LZ > LBBMax->Z) LBBMax->Z = LZ;
   }
  }
  LHasBB=TRUE;
 }
 return(LHasBB);
}


//========================================================================================
// Reset transformation values of a Model
//
// Argument
//  E3dModel* LModel         Pointer to the Model structure
//
// Description
//  Resets the transformations of the Model node in the following manner:
//  - Scaling[XYZ]     = 1,1,1
//  - Rotation[XYZ]    = 0,0,0
//  - Translation[XYZ] = 0,0,0
// Also loads identity matrices into LocalToWorldMatrix and NormalLocalToWorldMatrix
//========================================================================================
void E3d_ModelResetTransforms(E3dModel* LModel)
{
 E3d_3DPositionInit(&(LModel->Translation), 0.0, 0.0, 0.0);
 E3d_3DPositionInit(&(LModel->Scaling), 1.0, 1.0, 1.0);
 E3d_RotationInit(&(LModel->Rotation), 0.0, 0.0, 0.0);
 LModel->RotationOrder=E3dZYX;

// Reset the Orientation quaternion
//
 LModel->Orientation.X=0.0;
 LModel->Orientation.Y=0.0;
 LModel->Orientation.Z=0.0;
 LModel->Orientation.Angle=0.0;
 E3d_MatrixLoadIdentity(LModel->LocalToWorldMatrix);
 E3d_MatrixLoadIdentity(LModel->NormalLocalToWorldMatrix);

 LModel->MatrixNew=TRUE;
}


//========================================
// Initialize a Model structure	
//========================================
void E3d_ModelDefault(E3dModel* LModel, int LType, char* LName)
{
 LModel->RefCnt=0;

 if(LName) LModel->Name=EStrDup(LName);
 else LModel->Name=NULL;

 LModel->FileName=NULL;

 LModel->Visible=TRUE;
 LModel->LockCount=0;
 LModel->Selection=E3dSEL_NONE;
 LModel->Type=LType;

 LModel->NumOfInfoRecords=0;
 LModel->Info=NULL;

 LModel->NumOfAnimations=0;
 LModel->Animations=NULL;

 LModel->NumOfGeometries=0;
 LModel->Geometries=NULL;

 E3d_ModelResetTransforms(LModel);

 E3d_3DPositionInit(&(LModel->BBoxMin), 0.0, 0.0, 0.0);
 E3d_3DPositionInit(&(LModel->BBoxMax), 0.0, 0.0, 0.0);

 E3d_3DPositionInit(&(LModel->SchemPosition), 0.0, 0.0, 0.0);
 E3d_3DPositionInit(&(LModel->SchemTranslation), 0.0, 0.0, 0.0);
 LModel->RotationOrder=E3dZYX;


 LModel->Parent=NULL;LModel->PrevSibling=NULL;LModel->NextSibling=NULL;LModel->Child=NULL;
 LModel->PopN=E3dMDL_AFTER;LModel->PushN=E3dMDL_BEFORE;
 LModel->Prev=NULL;
 LModel->Next=NULL;
 LModel->UserData=NULL;


 if(LModel->Type==E3dMDL_JOINT)
 {
  E3dJointModel*	LJointModel=(E3dJointModel*)LModel;

  LJointModel->Skin=NULL;
  E3d_MatrixLoadIdentity(LJointModel->WorldToDefaultMatrix);
  E3d_MatrixLoadIdentity(LJointModel->SkinningMatrix);
 }
}


//================================================================================
// Allocate memory for a new Model of the given type
//
// Argument
//  char* LName            The name of the new Model
//
// Description
//  This function allocates and initializes an E3dModel structure.
//
// Return value
//  Pointer to the allocated E3dModel structure or NULL in case of an error.
//
// See also
//  E3d_ModelFree
//================================================================================
E3dModel* E3d_NodeAllocate(int LType, char* LName)
{
 E3dModel*	LModel;
 int		LSize;

#ifdef E_MEMDEBUG
 {
  char	LTmpStrM[256];

  sprintf(LTmpStrM, "Node: [%s]", LName);
  E_MemName=LTmpStrM;
 }
#endif // E_MEMDEBUG

 switch(LType)
 {
  default:		LSize=sizeof(E3dModel);break;
  case E3dMDL_JOINT:	LSize=sizeof(E3dJointModel);break;
 }

 if((LModel=(E3dModel*)EMalloc(LSize))!=NULL)
 {
  E3d_ModelDefault(LModel, LType, LName);

  return(LModel);
 }

 return(NULL);
}


//================================================================================
// Allocate memory for a normal Model-Node
//
// Argument
//  char* LName            The name of the new Model
//
// Description
//  This function allocates and initializes an E3dModel structure.
//
// Return value
//  Pointer to the allocated E3dModel structure or NULL in case of an error.
//
// See also
//  E3d_ModelFree
//================================================================================
E3dModel* E3d_ModelAllocate(char* LName)
{
 E3dModel*	LModel;

#ifdef E_MEMDEBUG
 {
  char	LTmpStrM[256];

  sprintf(LTmpStrM, "Node: [%s]", LName);
  E_MemName=LTmpStrM;
 }
#endif // E_MEMDEBUG
 if((LModel=(E3dModel*)EMalloc(sizeof(E3dModel)))!=NULL)
 {
  E3d_ModelDefault(LModel, E3dMDL_NORMAL, LName);

  return(LModel);
 }
 return(NULL);
}



E3dJointModel*	E3d_JointModelAllocate(char* LName)
{
 E3dJointModel*	LModel;


#ifdef E_MEMDEBUG
 {
  char	LTmpStrM[256];

  sprintf(LTmpStrM, "JointNode: [%s]", LName);
  E_MemName=LTmpStrM;
 }
#endif // E_MEMDEBUG
 if((LModel=(E3dJointModel*)EMalloc(sizeof(E3dJointModel)))!=NULL)
 {
  E3d_ModelDefault((E3dModel*)LModel, E3dMDL_JOINT, LName);

  E3d_MatrixLoadIdentity(LModel->JointOrientMatrix);

  LModel->XAngleMin=-360.0;
  LModel->XAngleMax=360.0;
  LModel->YAngleMin=-360.0;
  LModel->YAngleMax=360.0;
  LModel->ZAngleMin=-360.0;
  LModel->ZAngleMax=360.0;
  LModel->XDampening=0.0;
  LModel->YDampening=0.0;
  LModel->ZDampening=0.0;

  LModel->Skin=NULL;
  return(LModel);
 }
 return(NULL);
}


//===============================================================
// Update the SkinningMatrix of a JointModel
//===============================================================
void E3d_JointModelUpdateSkinningMatrix(E3dJointModel* LJointModel)
{
 E3d_MatrixMult3(LJointModel->WorldToDefaultMatrix, LJointModel->LocalToWorldMatrix, LJointModel->SkinningMatrix);
}




#define E3dM_AllocPlusGeometry(geo)\
 if(LModel->Geometries==NULL) LModel->Geometries=LGeometryP=(E3dGeometry**)EMalloc(sizeof(E3dGeometry*));\
 else\
 {\
  if((LGeometryP=(E3dGeometry**)ERealloc(LModel->Geometries, sizeof(E3dGeometry*)*(LModel->NumOfGeometries+1)))!=NULL)\
  {\
   LModel->Geometries=LGeometryP;\
  }\
 }\
 if(LGeometryP)\
 {\
  LGeometryP[LModel->NumOfGeometries]=(E3dGeometry*)(geo);\
  LModel->NumOfGeometries+=1;\
  (geo)->RefCnt+=1;\
  ELst_AddPointerChk((void***)(&((geo)->Models)), &((geo)->NumOfModels), LModel);\
  return((E3dGeometry*)(geo));\
 }


//========================================
// Append a Geometry to a Model
//========================================
E3dGeometry* E3d_ModelAppendGeometry(E3dModel* LModel, E3dGeometry* LGeometry)
{
 E3dGeometry**	LGeometryP;

 E3dM_AllocPlusGeometry(LGeometry);	// If all went well, this macro makes E3d_ModelAddGeometry to return,

 return(NULL);
}


//========================================================================
// Allocate a Geometry of the given type and append it to a Model
//========================================================================
E3dGeometry* E3d_ModelAddGeometry(E3dModel* LModel, int LGeoType, char* LGeoName)
{
 E3dGeometry**	LGeometryP;

 switch(LGeoType)
 {
  case E3dGEO_MESH:
   {
    E3dMesh*	LMesh=E3d_MeshAllocate();

    if(LMesh)
    {
     LMesh->Name=EStrDup(LGeoName);
     E3dM_AllocPlusGeometry(LMesh);		// If all went well, this macro makes E3d_ModelAddGeometry to return,
     E3d_MeshFree(LMesh);			// ... so this will only execute if there was a problem
    }
   }
  break;

  case E3dGEO_SKINMESH:
   {
    E3dSkinMesh*	LSkinMesh=E3d_SkinMeshAllocate();

    if(LSkinMesh)
    {
     LSkinMesh->Name=EStrDup(LGeoName);
     E3dM_AllocPlusGeometry(LSkinMesh);		// If all went well, this macro makes E3d_ModelAddGeometry to return,
     E3d_SkinMeshFree(LSkinMesh);		// ... so this will only execute if there was a problem
    }
   }
  break;

  case E3dGEO_SPLINE:
   {
    E3dSpline*	LSpline=E3d_SplineAllocate(E3dSPL_LINEAR);

    if(LSpline)
    {
     LSpline->Name=EStrDup(LGeoName);
     E3dM_AllocPlusGeometry(LSpline);		// If all went well, this macro makes E3d_ModelAddGeometry to return,
     E3d_GeometryFree((E3dGeometry*)LSpline);	// ... so this will only execute if there was a problem
    }
   }
  break;

  case E3dGEO_FACE:
   {
    E3dFace*	LFace=E3d_FaceAllocate(LGeoName);

    if(LFace)
    {
     LFace->Name=EStrDup(LGeoName);
     E3dM_AllocPlusGeometry(LFace);		// If all went well, this macro makes E3d_ModelAddGeometry to return,
     E3d_GeometryFree((E3dGeometry*)LFace);	// ... so this will only execute if there was a problem
    }
   }
  break;

  case E3dGEO_POINTS:
   {
    E3dPoints*	LPointsGeo=E3d_PointsGeomAllocate();

    if(LPointsGeo)
    {
     LPointsGeo->Name=EStrDup(LGeoName);
     E3dM_AllocPlusGeometry(LPointsGeo);		// If all went well, this macro makes E3d_ModelAddGeometry to return,
     E3d_GeometryFree((E3dGeometry*)LPointsGeo);	// ... so this will only execute if there was a problem
    }
   }
  break;
 }

 return(NULL);
}


//================================================================================
// Remove a Geometry from a Model
//
// Arguments
//  E3dModel*  LModel              Pointer to the Model
//  E3dGeometry* LGeometry         Pointer to the Geometry
//
// Description
//  Removes LGeometry from the given Model's dynamically allocated
//  Geometry list.
//================================================================================
void E3d_ModelRemoveGeometry(E3dModel* LModel, E3dGeometry* LGeometry)
{
 E3dGeometry**	LGeometries;
 unsigned int	LC, LN;

EBool	LFound=FALSE;

 if((LGeometries=LModel->Geometries)==NULL) return;

 LN=LModel->NumOfGeometries;
 for(LC=0;LC<LN;LC++)
 {
  if(LGeometries[LC]==LGeometry)
  {

assert(LFound==0);
   if(LC<(LN-1)) memmove(LGeometries+LC, LGeometries+LC+1, sizeof(E3dGeometry*)*(LN-LC-1));

   ELst_RemovePointer((void***)(&(LGeometry->Models)), &(LGeometry->NumOfModels), LModel);

   LModel->NumOfGeometries-=1;
   if(LModel->NumOfGeometries==0) { EFree(LModel->Geometries);LModel->Geometries=NULL; }
   else LModel->Geometries=(E3dGeometry**)ERealloc(LGeometries, sizeof(E3dGeometry*)*LModel->NumOfGeometries);

   E3d_GeometryFree(LGeometry);
LFound=TRUE;
//   return;
  }
 }
}


//========================================
// Append an Animation to a Model
//========================================
EBool E3d_ModelAppendAnimation(E3dModel* LModel, E3dAnimation* LAnimation)
{
 if(ELst_AddPointer((void***)(&(LModel->Animations)), &(LModel->NumOfAnimations), LAnimation))
 {
  LAnimation->RefCnt+=1;
  return(TRUE);
 }
 else return(FALSE);
}


//========================================
// Remove Animation from a Model
//========================================
int E3d_ModelRemoveAnimation(E3dModel* LModel, E3dAnimation* LAnimation)
{
// Free Class-specific data
//
 if(LModel->Animations)
 {
  unsigned int	LC, LN=LModel->NumOfAnimations;


  for(LC=0;LC<LN;LC++)
  {
   if(LAnimation==LModel->Animations[LC])
   {
    ELst_RemovePointer((void***)(&(LModel->Animations)), &(LModel->NumOfAnimations), LAnimation);
    E3d_AnimationFree(LAnimation);
    return(1);
   }
  }
 }
 return(0);
}


//========================================================================================
// Remove all Animations from a Model
//========================================================================================
int E3d_ModelRemoveAllAnimations(E3dModel* LModel)
{
 if(LModel->Animations)
 {
  unsigned int		LC, LN=LModel->NumOfAnimations;


  for(LC=0;LC<LN;LC++) E3d_AnimationFree(LModel->Animations[LC]);

  EFree(LModel->Animations);LModel->Animations=NULL;LModel->NumOfAnimations=0;
  return(LN);
 }
 return(0);
}


//========================================================================================
// Free a Model's rendering-related data, such as OpenGL vertex arrays etc.
// we usually do this when a Model is deleted from the Scene, but stored for undo.
//========================================================================================
void E3d_ModelFreeRenderData(E3dModel* LModel)
{
 E3dGeometry**	LGeometries=LModel->Geometries;
 unsigned int	LC, LN=LModel->NumOfGeometries;


 for(LC=0;LC<LN;LC++) E3d_GeometryFreeRenderData(LGeometries[LC]);
}


//========================================================================================
// Remove Info record from a Model
//========================================================================================
int E3d_ModelInfoRemove(E3dModel* LModel, E3dModelInfo* LInfo)
{
// Free Class-specific data
//
 if(LModel->Info)
 {
  unsigned int	LC, LN=LModel->NumOfInfoRecords;

  for(LC=0;LC<LN;LC++)
  {
   if(LInfo==LModel->Info[LC])
   {
    E3dModelClass*	LClass=LInfo->Class;

    if(LClass)
    {
     if(LClass->DestroyProc) LClass->DestroyProc(LModel, LInfo);
     EFree(LInfo);
     ELst_RemovePointer((void***)(&(LModel->Info)), &(LModel->NumOfInfoRecords), LInfo);
     return(1);
    }
   }
  }
 }
 return(0);
}


//========================================================================================
// Remove all Info records from a Model	
//========================================================================================
int E3d_ModelInfoRemoveAll(E3dModel* LModel)
{
// Free Class-specific data
//
 if(LModel->Info)
 {
  E3dModelInfo*		LInfo;
  E3dModelClass*	LClass;
  unsigned int		LC, LN=LModel->NumOfInfoRecords;

  for(LC=0;LC<LN;LC++)
  {
   LInfo=LModel->Info[LC];

   LClass=LInfo->Class;

   if(LClass)
   {
    if(LClass->DestroyProc) LClass->DestroyProc(LModel, LInfo);
   }
   EFree(LInfo);
  }
  EFree(LModel->Info);LModel->Info=NULL;LModel->NumOfInfoRecords=0;
  return(LN);
 }
 return(0);
}


//========================================================================================
// Add an Info record of a given Class to a Model
//========================================================================================
E3dModelInfo* E3d_ModelInfoAdd(E3dModel* LModel, E3dModelClass* LClass)
{
 E3dModelInfo*	LInfo=(E3dModelInfo*)EMalloc(LClass->StructSize);

 if(LInfo)
 {
  ELst_AddPointer((void***)(&(LModel->Info)), &(LModel->NumOfInfoRecords), LInfo);
  LInfo->Class=LClass;
 }
 return(LInfo);
}


//========================================================================================
// Free all Geometries of a Model
//
// Arguments
//  E3dModel*  LModel      Pointer to the E3dModel structure
//
// Description
//  Frees all the Geometries of LModel.
//
// See also
//  E3d_ModelAppendGeometry, E3d_ModelAddGeometry, E3d_ModelRemoveGeometry
//========================================================================================
void E3d_ModelRemoveGeometries(E3dModel* LModel)
{
 unsigned int	LN=LModel->NumOfGeometries;

 if(LN)
 {
  E3dGeometry**	LGeometries=LModel->Geometries;
  E3dGeometry*	LGeometry;
  unsigned int	LC;


  for(LC=0;LC<LN;LC++)
  {
   LGeometry=LGeometries[LC];

   ELst_RemovePointer((void***)(&(LGeometry->Models)), &(LGeometry->NumOfModels), LModel);

   E3d_GeometryFree(LGeometry);
  }
  EFree(LModel->Geometries);LModel->Geometries=NULL;LModel->NumOfGeometries=0;
 }
}


//========================================================================================
// Free a Model
//
// Arguments
//  E3dModel*  LModel      Pointer to the E3dModel structure to free
//
// Description
//  This function first decrements the reference count (RefCnt) of the given Model
//  structure (if it's not already zero).
//  After that, if RefCnt is still greater than zero, it means that something other
//  than the function calling E3d_ModelFree() is still referring to this Model,
//  so E3d_ModelFree() will simply return.
//  If RefCnt is zero, E3d_ModelFree() will free all the Geometries and other
//  dynamically allocated structures of the Model and the Model structure itself.
//
// See also
//  E3d_ModelAllocate
//========================================================================================
void E3d_ModelFree(E3dModel* LModel)
{
//printf("E3d_ModelFree() %p [%s] RefCnt %d\n", LModel, LModel->Name, LModel->RefCnt);fflush(stdout);

 if(LModel->RefCnt>0)
 {
  LModel->RefCnt-=1;
  if(LModel->RefCnt>0) return;
 }


 if(LModel->Name) EFree(LModel->Name);

 E3d_ModelRemoveGeometries(LModel);

// Remove Info records
//
 E3d_ModelInfoRemoveAll(LModel);

 E3d_ModelRemoveAllAnimations(LModel);

 if(LModel->Type==E3dMDL_JOINT)
 {
  E3dJointModel*	LJointModel=(E3dJointModel*)LModel;

  if(LJointModel->Skin) E3d_GeometryFree(LJointModel->Skin);
 }

 EFree(LModel);
}


//================================================================================
// Copy the transformation parameters of a Model to another Model
//================================================================================
void E3d_ModelCopyTransform(E3dModel* LSModel, E3dModel* LDModel, EBool LTransformCompensation)
{
 LDModel->Translation=LSModel->Translation;
 LDModel->Scaling=LSModel->Scaling;
 LDModel->Rotation=LSModel->Rotation;
 LDModel->RotationOrder=LSModel->RotationOrder;
 LDModel->Orientation=LSModel->Orientation;
}


//================================================================================
// Get number of selected Geometries of a Model
//================================================================================
int E3d_ModelNumOfSelectedGeometries(E3dModel* LModel)
{
 unsigned int	LC, LN, LNumOfSelectedGeometries=0;

 if(LModel->Selection!=E3dSEL_GEOMETRY) return(0);

 if((LN=LModel->NumOfGeometries)!=0)
 {
  E3dGeometry**	LGeometries;
  E3dGeometry*	LGeometry;

  LGeometries=LModel->Geometries;
  for(LC=0;LC<LN;LC++)
  {
   LGeometry=LGeometries[LC];

   switch(LGeometry->Selection)
   {
    case E3dSEL_GEOMETRY:	LNumOfSelectedGeometries++;break;
   }
  }
 }
 return(LNumOfSelectedGeometries);
}


//========================================================================
// Get the Material for a Model node, using inheritance if needed
//========================================================================
E3dMaterial* E3d_ModelInheritMaterial(E3dModel* LModel)
{
 E3dGeometry*	LGeometry;
 unsigned int	LC, LN;

 while(LModel)
 {
  if(LModel->NumOfGeometries)
  {
   LN=LModel->NumOfGeometries;
   for(LC=0;LC<LN;LC++)
   {
    LGeometry=LModel->Geometries[LC];
    switch(LGeometry->GeoType)
    {
     caseE3dMESH():
      {
       E3dMesh*		LMesh=(E3dMesh*)LGeometry;
       E3dMaterial*	LMaterial;
       unsigned int	LGC, LGN=LMesh->NumOfPolyGroups;

       for(LGC=0;LGC<LGN;LGC++)
       {
	LMaterial=LMesh->PolyGroups[LGC]->Material;
	if(LMaterial) return(LMaterial);
       }
      }
     break;

     case E3dGEO_FACE:
      {
       E3dFace*	LFace=(E3dFace*)LGeometry;

       if(LFace->Material) return(LFace->Material);
      }
     break;
    }

   }
  }
  LModel=LModel->Parent;
 }

 return(&E3d_DefaultMaterial);
}


//================================================================
// Add a child Model to a given Model
//================================================================
void E3d_ModelConnectChild(E3dModel* LParent, E3dModel* LChild)
{
 if(LChild->Parent==NULL)
 {
  E3dScene*	LScene=E3d_Scene;

// Remove this Model/Branch from the Scene as an individual hierarchy, but don't free its RenderData etc.
// This is a part of E3d_SceneRemoveModelHrc()
//
  if(ELst_RemovePointerA((void***)(&(LScene->RootModels)), &(LScene->NumOfRootModels), &(LScene->NumOfRootModelsAllocated), LChild))
  {
   LChild->RefCnt-=1;
  }

  LChild->Parent=LParent;

  if(LParent->Child==NULL) LParent->Child=LChild;
  else
  {
   E3dModel*	LModel=LParent->Child;

   while(LModel->NextSibling) LModel=LModel->NextSibling;

//printf("NextS [%s] %08x\n", LModel->Name, LModel->NextSibling);fflush(stdout);
   LModel->NextSibling=LChild;
  }

  while(LParent->Parent) LParent=LParent->Parent;
  E3d_ModelHrcRefreshHierarchy(LParent);
  E3d_ModelHrcRefreshMatrices(LParent);
 }
}


//========================================================================
// Disconnect a Model node from its hierarchy (parent)
//========================================================================
void E3d_ModelDisconnectFromHierarchy(E3dModel* LModel)
{
 LModel->Parent=NULL;LModel->Child=NULL;LModel->PrevSibling=NULL;LModel->NextSibling=NULL;
 LModel->Prev=NULL;LModel->Next=NULL;
}


//================================================================================
// Get the Material for a Model node, parsing downward (Geometry, SubGeometry)
//================================================================================
E3dMaterial* E3d_ModelGetMaterial(E3dModel* LModel, EBool LSelectedSubGeo)
{
 E3dGeometry*	LGeometry;
 E3dGeometry**	LGeometries;
 unsigned int	LGmC, LGmN;


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

// If Geometries are selected in the Model, first check the selected Geometries for a non-NULL Material
//
 if(LModel->Selection==E3dSEL_GEOMETRY)
 {
  for(LGmC=0;LGmC<LGmN;LGmC++)
  {
   LGeometry=LGeometries[LGmC];
   switch(LGeometry->GeoType)
   {
     caseE3dMESH():
      {
	unsigned int	LPGC, LPGN;
	E3dMesh*		LMesh=(E3dMesh*)LGeometry;
	E3dPolyGroup*	LPolyGroup;
	E3dPolyGroup**	LPolyGroups;

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

	switch(LMesh->Selection)
	{
	 case E3dGSEL_GEOMETRY:	// Get Material from 1st PolyGroup
	  for(LPGC=0;LPGC<LPGN;LPGC++)
	  {
	    LPolyGroup=LPolyGroups[LPGC];
	    if(LPolyGroup->Material) return(LPolyGroup->Material);
	  }
	 break;

	 case E3dGSEL_POLYGROUP:	// Get Material from 1st selected PolyGroup
	  for(LPGC=0;LPGC<LPGN;LPGC++)
	  {
	    LPolyGroup=LPolyGroups[LPGC];
	    if(LPolyGroup->Selected)
	    {
	     if(LPolyGroup->Material) return(LPolyGroup->Material);
	    }
	  }
	 break;
	}
      }
     break;

     case E3dGEO_FACE:
      {
        E3dFace*	LFace=(E3dFace*)LGeometry;

        if(LFace->Material) return(LFace->Material);
      }
     break;
   }
  }
 }


// No Material found in selected Geometries, try all Geometries now
//
 for(LGmC=0;LGmC<LGmN;LGmC++)
 {
  LGeometry=LGeometries[LGmC];
  switch(LGeometry->GeoType)
  {
    caseE3dMESH():
     {
       unsigned int	LPGC, LPGN;
       E3dMesh*		LMesh=(E3dMesh*)LGeometry;
       E3dPolyGroup*	LPolyGroup;
       E3dPolyGroup**	LPolyGroups;

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

       for(LPGC=0;LPGC<LPGN;LPGC++)
       {
	 LPolyGroup=LPolyGroups[LPGC];
	 if(LPolyGroup->Material) return(LPolyGroup->Material);
       }
     }
    break;

    case E3dGEO_FACE:
     {
      E3dFace*	LFace=(E3dFace*)LGeometry;

      if(LFace->Material) return(LFace->Material);
     }
    break;
  }
 }

 return(NULL);
}


//================================================================================
// Remove the given Material from a Model node and its Geometries/SubGeometries
//================================================================================
int E3d_ModelRemoveMaterial(E3dModel* LModel, E3dMaterial* LMaterial)
{
 E3dGeometry*	LGeometry;
 E3dGeometry**	LGeometries;
 int		LNumOfReferemces=0;
 unsigned int	LGmC, LGmN;


 if(LMaterial==NULL) return(0);

 LGeometries=LModel->Geometries;
 for(LGmC=0,LGmN=LModel->NumOfGeometries;LGmC<LGmN;LGmC++)
 {
  LGeometry=LGeometries[LGmC];
  switch(LGeometry->GeoType)
  {
   caseE3dMESH():
    {
     unsigned int	LPGC, LPGN;
     E3dMesh*		LMesh=(E3dMesh*)LGeometry;
     E3dPolyGroup*	LPolyGroup;
     E3dPolyGroup**	LPolyGroups;

     LPolyGroups=LMesh->PolyGroups;LPGN=LMesh->NumOfPolyGroups;
     for(LPGC=0;LPGC<LPGN;LPGC++)
     {
      LPolyGroup=LPolyGroups[LPGC];
      if(LPolyGroup->Material==LMaterial)
      {
       LPolyGroup->Material=NULL;LMaterial->RefCnt-=1;
       E3d_UpdateDrawingMaterial(LGeometry, LPolyGroup->Material, &(LPolyGroup->DrawingMaterial));

       LNumOfReferemces++;
      }
     }
    }
   break;

   case E3dGEO_FACE:
    {
     E3dFace*	LFace=(E3dFace*)LGeometry;

     if(LFace->Material==LMaterial)
     {
      LFace->Material=NULL;LMaterial->RefCnt-=1;
      E3d_UpdateDrawingMaterial(LGeometry, LFace->Material, &(LFace->DrawingMaterial));

      LNumOfReferemces++;
     }
    }
   break;
  }
 }
 return(LNumOfReferemces);
}


//================================================
// Collect Materials used in a Model
//================================================
E3dMaterial** E3d_ModelCollectMaterials(E3dModel* LModel, unsigned int* LNumOfMaterialsPtr)
{
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 E3dMaterial**	LMaterials=NULL;
 unsigned int	LNumOfMaterials=0,
		LGmN, LGmC;


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

 for(LGmC=0;LGmC<LGmN;LGmC++)
 {
  if((LGeometry=LGeometries[LGmC])!=NULL)
  {
   switch(LGeometry->GeoType)
   {
    caseE3dMESH():
     {
      unsigned int	LGCnt, LGNum;
      E3dMesh*		LMesh;
      E3dPolyGroup*	LPolyGroup;
      E3dPolyGroup**	LPolyGroups;

      LMesh=(E3dMesh*)LGeometry;

      if(LMesh->NumOfPolyGroups==0) break;		// Break for the 'case'
      LPolyGroups=LMesh->PolyGroups;
      LGNum=LMesh->NumOfPolyGroups;

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

       if(LPolyGroup->Material) ELst_AddPointerChk((void***)(&LMaterials), &LNumOfMaterials, LPolyGroup->Material);
      }
     }
    break;

    case E3dGEO_FACE:
     {
      E3dFace*	LFace=(E3dFace*)LGeometry;

      if(LFace->Material) ELst_AddPointerChk((void***)(&LMaterials), &LNumOfMaterials, LFace->Material);
     }
    break;
   }
  }
 }

 *LNumOfMaterialsPtr=LNumOfMaterials;
 return(LMaterials);
}


//========================================================================
// Create/update TextureMappers for all the Geometries of a Model
//========================================================================
int E3d_ModelMap2DTextures(E3dModel* LModel)
{
 unsigned int	LGmN, LTotalMapped=0;

 if((LGmN=LModel->NumOfGeometries)!=0)
 {
  unsigned int	LGmC;
  E3dGeometry**	LGeometries=LModel->Geometries;

  for(LGmC=0;LGmC<LGmN;LGmC++)
  {
   LTotalMapped+=E3d_GeometryMap2DTextures(LGeometries[LGmC]);
  }
 }
 return(LTotalMapped);
}


//================================================
// Find Info record of a Model by its Class
//================================================
E3dModelInfo* E3d_ModelInfoByClass(E3dModel* LModel, E3dModelClass* LClass)
{
 if(LModel->Info)
 {
  unsigned int		LC, LN=LModel->NumOfInfoRecords;

  for(LC=0;LC<LN;LC++)
  {
   if(LModel->Info[LC]->Class==LClass) return(LModel->Info[LC]);
  }
 }
 return(NULL);
}


//================================================
// Find a Model class by its name
//================================================
E3dModelClass* E3d_ModelClassFindByName(char* LName)
{
 E3dModelClass**	LClasses=E3d_CustomModelClasses;

 if(LClasses)
 {
  E3dModelClass*	LClass;
  unsigned int		LC, LN;

  for(LC=0, LN=E3d_NumOfCustomModelClasses;LC<LN;LC++)
  {
   LClass=LClasses[LC];
   if(EStr_StringsEqual(LClass->Name, LName)) return(LClass);
  }
 }
 return(NULL);
}


//================================================
// Register a new Model class
//================================================
E3dModelClass* E3d_ModelClassRegister(E3dModelClass* LClassTemplate)
{
 E3dModelClass**	LClasses=E3d_CustomModelClasses;
 E3dModelClass*		LClass;
 unsigned int		LC, LN;

 if(LClasses)
 {
  for(LC=0, LN=E3d_NumOfCustomModelClasses;LC<LN;LC++)
  {
   LClass=LClasses[LC];
   if(EStr_StringsEqual(LClass->Name, LClassTemplate->Name))
   {
    LClass->StructSize=LClassTemplate->StructSize;
    LClass->EditProc=LClassTemplate->EditProc;
    LClass->RemoveProc=LClassTemplate->RemoveProc;
    LClass->DestroyProc=LClassTemplate->DestroyProc;
    LClass->Resources=LClassTemplate->Resources;

    return(LClass);
   }
  }
 }

 if(LClasses==NULL) E3d_CustomModelClasses=LClasses=(E3dModelClass**)EMalloc(sizeof(E3dModelClass*));
 else
 {
  if((LClasses=(E3dModelClass**)ERealloc(E3d_CustomModelClasses, sizeof(E3dModelClass*)*(E3d_NumOfCustomModelClasses+1)))!=NULL) E3d_CustomModelClasses=LClasses;
 }

 if(LClasses)
 {
  if((LClass=(E3dModelClass*)EMalloc(sizeof(E3dModelClass)))!=NULL)
  {
   LClasses[E3d_NumOfCustomModelClasses]=LClass;

   LClass->Name=EStrDup(LClassTemplate->Name);
   LClass->StructSize=LClassTemplate->StructSize;
   LClass->EditProc=LClassTemplate->EditProc;
   LClass->RemoveProc=LClassTemplate->RemoveProc;
   LClass->DestroyProc=LClassTemplate->DestroyProc;
   LClass->Resources=LClassTemplate->Resources;

   E3d_NumOfCustomModelClasses++;
  }
  return(LClass);
 }
 else return(NULL);
}


//================================================
// Deactivate a Model class
//================================================
void E3d_ModelClassDeactivate(E3dModelClass* LClass)
{
 E3dModelClass**	LClasses=E3d_CustomModelClasses;

 if(LClasses)
 {
  unsigned int	LC, LN;

  for(LC=0, LN=E3d_NumOfCustomModelClasses;LC<LN;LC++)
  {
   if(LClasses[LC]==LClass)
   {
    LClass->EditProc=NULL;
    LClass->DestroyProc=NULL;
    LClass->Resources=NULL;
   }
  }
 }
}


//================================================
// Remove a Model class
//================================================
void E3d_ModelClassRemove(E3dModelClass* LClass)
{
 E3dModelClass**	LClasses=E3d_CustomModelClasses;

 if(LClasses)
 {
  unsigned int	LC, LN;

  for(LC=0, LN=E3d_NumOfCustomModelClasses;LC<LN;LC++)
  {
   if(LClasses[LC]==LClass)
   {
    if(LClass->Name) EFree(LClass->Name);
    EFree(LClass);
    if(LC<(LN-1)) memmove(LClass, LClass+1, sizeof(E3dModelClass*)*(LN-LC-1));
    E3d_NumOfCustomModelClasses-=1;
    if(E3d_NumOfCustomModelClasses==0) { EFree(E3d_CustomModelClasses);E3d_CustomModelClasses=NULL; }
    else E3d_CustomModelClasses=(E3dModelClass**)ERealloc(E3d_CustomModelClasses, sizeof(E3dModelClass*)*E3d_NumOfCustomModelClasses);
    return;
   }
  }
 }
}


//========================================
// Get the global position of a Model
//========================================
E3d3DPosition E3d_ModelGetGlobalPosition(E3dModel* LModel)
{
 E3d3DPosition	LResult;

 LResult.X=(LModel->LocalToWorldMatrix)[M30];
 LResult.Y=(LModel->LocalToWorldMatrix)[M31];
 LResult.Z=(LModel->LocalToWorldMatrix)[M32];
 return(LResult);
}


//========================================
// Tesselate a Model's Geometries
//========================================
EBool E3d_ModelTesselate(E3dModel* LModel, EBool LDoConvex)
{
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 unsigned int	LGmC, LGmN;

// Do triangulation of Polygons and store result in the 'Renderable Polygons'
// structures on each Polygon
//
 LGmN=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
 for(LGmC=0;LGmC<LGmN;LGmC++)
 {
  LGeometry=LGeometries[LGmC];

  E3d_GeometryTesselate(LGeometry, LDoConvex);

  if(LGeometry->NumOfLODs)
  {
   E3dGeometry*	LLOD;
   int		LLODC, LLODN=LGeometry->NumOfLODs;

   for(LLODC=0;LLODC<LLODN;LLODC++)
   {
    LLOD=LGeometry->LODs[LLODC];
    E3d_GeometryTesselate(LLOD, LDoConvex);
   }
  }
 }

 return(TRUE);
}


//========================================================
// Reorient geometry or transformations of a Model
//========================================================
void E3d_ReorientModel(E3dModel* LModel, E3dVector* LFromVector, E3dVector* LToVector, EBool LTransformGeometry)
{
}


//========================================================
// Get local transformation matrices for a Model
//========================================================
void E3d_ModelGetLocalMatrices(E3dModel* LModel, E3dMatrix LMatrix, E3dMatrix LNormalMatrix)
{
 E3d_MatrixLoadIdentity(LMatrix);

 E3d_MatrixTranslate(LMatrix, LModel->Translation.X, LModel->Translation.Y, LModel->Translation.Z);

 switch(LModel->RotationOrder)
 {
  case E3dXYZ:
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
  break;

  case E3dXZY:
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
  break;

  case E3dYXZ:
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
  break;

  case E3dYZX:
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
  break;

  case E3dZYX:
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
  break;

  case E3dZXY:
   E3d_MatrixRotate(LMatrix, 'z', LModel->Rotation.Z);
   E3d_MatrixRotate(LMatrix, 'x', LModel->Rotation.X);
   E3d_MatrixRotate(LMatrix, 'y', LModel->Rotation.Y);
  break;
 }


// Use Matrix without scaling for transforming normals
//
 E3d_MatrixCopy(LMatrix, LNormalMatrix);
 LNormalMatrix[M30]=0.0;LNormalMatrix[M31]=0.0;LNormalMatrix[M32]=0.0;

 E3d_MatrixScale(LMatrix, LModel->Scaling.X, LModel->Scaling.Y, LModel->Scaling.Z);
 E3d_MatrixCopy(LMatrix, LModel->LocalToWorldMatrix);
}


//================================================================================
// Refresh Material data of a Model node
//
// - For custom displayer: notify display routine that the Material(s) changed
//================================================================================
void E3d_ModelUpdateMaterialsForDisplay(E3dModel* LModel)
{
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 E3dMaterial**	LMaterials=NULL;
 unsigned int	LNumOfMaterials=0;
 unsigned int	LC, LN;

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

 for(LC=0;LC<LN;LC++)
 {
  if((LGeometry=LGeometries[LC])!=NULL)
  {
#ifdef USEOpenGL
   switch(LGeometry->GeoType)
   {
    caseE3dMESH():
     {
      unsigned int	LGCnt;
      E3dMesh*		LMesh=(E3dMesh*)LGeometry;
      E3dPolyGroup*	LPolyGroup;
      E3dPolyGroup**	LPolyGroups;

      LPolyGroups=LMesh->PolyGroups;

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

       E3d_UpdateDrawingMaterial(LGeometry, LPolyGroup->Material, &(LPolyGroup->DrawingMaterial));

// Collect DrawingMaterials first to make sure we update each one of them only once
//
       ELst_AddPointerChk((void***)(&LMaterials), &LNumOfMaterials, LPolyGroup->DrawingMaterial);
      }
      E3d_MeshRemoveGLDisplayLists(LMesh);
     }
    break;

    case E3dGEO_FACE:
     {
      E3dFace*	LFace=(E3dFace*)LGeometry;

      E3d_UpdateDrawingMaterial(LGeometry, LFace->Material, &(LFace->DrawingMaterial));

      ELst_AddPointerChk((void***)(&LMaterials), &LNumOfMaterials, LFace->DrawingMaterial);
     }
    break;
   }
#endif // USEOpenGL
  }
 }

// Now update the collected DrawingMaterials
//
 if(LMaterials)
 {
  for(LC=0;LC<LNumOfMaterials;LC++)
  {
//printf("GLMat [%s]\n", LMaterials[LC]->Name);fflush(stdout);
   E3d_MaterialConvertToGL(LMaterials[LC]);
  }
  EFree(LMaterials);
 }
}


//========================================================================================
// Update the DrawingMaterial of a PolyGroup or a Face by inheriting if necessary
//========================================================================================
void E3d_UpdateDrawingMaterial(E3dGeometry* LGeometry, E3dMaterial* LMaterial, E3dMaterial** LMaterialPtr)
{
 if(LMaterial==NULL) 
 {
  E3dMaterial*	LDrawingMaterial;

  if(LGeometry->Models) LDrawingMaterial=E3d_ModelInheritMaterial(LGeometry->Models[0]);
  else LDrawingMaterial=&E3d_DefaultMaterial;

  if(*LMaterialPtr!=LDrawingMaterial)
  {
   if(*LMaterialPtr) E3d_MaterialFree(*LMaterialPtr);

   *LMaterialPtr=LDrawingMaterial;LDrawingMaterial->RefCnt+=1;
  }
 }
 else
 {
  if(*LMaterialPtr!=LMaterial)
  {
   if(*LMaterialPtr) E3d_MaterialFree(*LMaterialPtr);

   *LMaterialPtr=LMaterial;LMaterial->RefCnt+=1;
  }
 }
}


//================================================================================
// Refresh rendering data of a Model node
//
// - For OpenGL: re-create OpenGL-optimized data sets
// - For custom displayer: notify display routine that the object(s) changed
//================================================================================
void E3d_ModelUpdateForDisplay(E3dModel* LModel, unsigned int LFlags)
{
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 unsigned int	LGmC, LGmN, LLODC, LLODN;

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

 for(LGmC=0;LGmC<LGmN;LGmC++)
 {
  LGeometry=LGeometries[LGmC];

  E3d_GeometryUpdateForDisplay(LGeometry, LFlags);
  LLODN=LGeometry->NumOfLODs;
  for(LLODC=0;LLODC<LLODN;LLODC++) E3d_GeometryUpdateForDisplay(LGeometry->LODs[LLODC], LFlags);
 }
}


//========================================================================
// Remove cached OpenGL display lists from the Geometries of a Model
//========================================================================
void E3d_ModelRemoveGLDisplayLists(E3dModel* LModel)
{
 E3dGeometry**		LGeometries;
 unsigned int		LGmC, LGmN;

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

 for(LGmC=0;LGmC<LGmN;LGmC++)
 {
  switch(LGeometries[LGmC]->GeoType)
  {
   caseE3dMESH():
    E3d_MeshRemoveGLDisplayLists((E3dMesh*)(LGeometries[LGmC]));
   break;
  }
 }
}


//================================================================
// Get the first Geometry of a Model with a matching GeoType
//================================================================
E3dGeometry* E3d_ModelGetGeometryByType(E3dModel* LModel, int LGeoType)
{
 E3dGeometry*	LGeometry;	
 unsigned int	LC, LN=LModel->NumOfGeometries;

 for(LC=0;LC<LN;LC++)
 {
  LGeometry=LModel->Geometries[LC];
  if(LGeometry->GeoType==LGeoType) return(LGeometry);
 }
 return(NULL);
}


//========================================================
// Add lock to a Model
//========================================================
void E3d_ModelAddLock(E3dModel* LModel)
{
 LModel->LockCount+=1;
 if(LModel->LockCount==1) E3d_ModelUpdateForDisplay(LModel, E3dGF_LOCKUNLOCK);
}


//========================================================
// Remove lock from a Model
//========================================================
void E3d_ModelDelLock(E3dModel* LModel)
{
 LModel->LockCount-=1;
 if(LModel->LockCount==0) E3d_ModelUpdateForDisplay(LModel, E3dGF_LOCKUNLOCK);
}


//========================================================
// Add lock to a Model
//========================================================
void E3d_ModelsAddLock(E3dModel** LModels, unsigned int LNumOfModels)
{
 E3dModel*	LModel;
 unsigned int	LC;

 for(LC=0;LC<LNumOfModels;LC++)
 {
  LModel=LModels[LC];
  LModel->LockCount+=1;
  if(LModel->LockCount==1) E3d_ModelUpdateForDisplay(LModel, E3dGF_LOCKUNLOCK);
 }
}


//========================================================
// Remove lock from a Model
//========================================================
void E3d_ModelsDelLock(E3dModel** LModels, unsigned int LNumOfModels)
{
 E3dModel*	LModel;
 unsigned int	LC;

 for(LC=0;LC<LNumOfModels;LC++)
 {
  LModel=LModels[LC];
  LModel->LockCount-=1;
  if(LModel->LockCount==0) E3d_ModelUpdateForDisplay(LModel, E3dGF_LOCKUNLOCK);
 }
}
