/*======================================================================*/
/* 3DLib								*/
/*									*/
/* Polygon and Mesh related functions					*/
/*									*/
/* AUTHOR:	Gabor Nagy						*/
/* DATE:	1996-Dec-17 23:57:01					*/
/*									*/
/* 3DLib(TM) Copyright (C) 1995 by Gabor Nagy. All rights reserved.	*/
/*======================================================================*/
#include <assert.h>
#include <math.h>
#include <stdarg.h>		// For varargs
#include <stdio.h>
#include <stdlib.h>

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

#include <E3D/E3D.h>

#include <E3D/Math.h>

#include <E3D/Matrix.h>

#ifndef _E3DMesh_h
#include <E3D/Mesh.h>
#endif

#ifndef _E3DPanel_h
#include <E3D/Panel.h>
#endif

#ifndef _E3DScene_h
#include <E3D/Scene.h>
#endif



char*	(*E3d_AGPMallocProc)(unsigned int, void**)=NULL;
void	(*E3d_AGPFreeProc)(void*)=NULL;
void	(*E3dNV_AGPMemAllocateProc)()=NULL;


int	(*E3d_PolyGroupCreateShadedVertexArrayProc)(E3dPolyGroup*, E3dVertex*)=NULL;
int	(*E3d_PolyGroupCreateShadedSTVertexArrayProc)(E3dPolyGroup*, E3dVertex*)=NULL;

EBool	E3dNV_AGPInitialized=FALSE;

//#include <GL/glx.h>


#define E3dRPI	(1.0/180.0*E3dPI)


//----------------------------------------------------------------------------------------
// Vertex functions
//----------------------------------------------------------------------------------------

//========================================
// Initialize a Vertex
//========================================
void E3d_VertexInit(E3dVertex* LVertex, double LX, double LY, double LZ)
{
 LVertex->X=LX;LVertex->Y=LY;LVertex->Z=LZ;
 LVertex->Flags=0;
}


//========================================
// Copy a Vertex
//========================================
void E3d_VertexCopy(E3dVertex* LSrcVertex, E3dVertex* LDstVertex)
{
 LDstVertex->X=LSrcVertex->X;
 LDstVertex->Y=LSrcVertex->Y;
 LDstVertex->Z=LSrcVertex->Z;
 LDstVertex->Flags=LSrcVertex->Flags;
}


//================================================================================
// Allocate memory for an array of Vertices
//
// Arguments
//  unsigned int LNumOfVertices    Number of vertices to allocate
//
// Description
//  Allocates enough space for the specified number of Vertices.
//
// Return value
//  A pointer to the allocated array
//
// See also
//  E3d_MeshFreeVertices
//================================================================================
E3dVertex* E3d_VerticesAllocate(unsigned int LNumOfVertices, EBool LInitialize)
{
 E3dVertex*	LVertices;

#ifdef E_MEMDEBUG
 E_MemName="Vertices by E3d_VerticesAllocate";
#endif // E_MEMDEBUG

 LVertices=(E3dVertex*)EMalloc(sizeof(E3dVertex)*LNumOfVertices);
 if((LVertices!=NULL)&&LInitialize)
 {
  E3dVertex*	LVertex=LVertices;
  unsigned int	LVC;

  for(LVC=0;LVC<LNumOfVertices;LVC++, LVertex++)
  {
   LVertex->Flags=0;
  }
 }

 return(LVertices);
}


//========================================
// Re-allocate Vertex array
//========================================
E3dVertex* E3d_VerticesReallocate(E3dVertex* LOldVertices, unsigned int LNumOfVertices, EBool LInitialize)
{
 E3dVertex*	LVertices=(E3dVertex*)ERealloc(LOldVertices, sizeof(E3dVertex)*LNumOfVertices);

#ifdef E_MEMDEBUG
 E_MemName="Vertices by E3d_VerticesReallocate";
#endif // E_MEMDEBUG

 if((LVertices!=NULL)&&LInitialize)
 {
  E3dVertex*	LVertex=LVertices;
  unsigned int	LVC;

  for(LVC=0;LVC<LNumOfVertices;LVC++, LVertex++)
  {
   LVertex->Flags=0;
  }
 }

 return(LVertices);
}


//================================================================================
// Allocate memory for an array of SkinVertices
//
// Arguments
//  unsigned int LNumOfVertices    Number of SkinVertices to allocate
//
// Description
//  Allocates memory for the specified number of SkinVertices.
//
// Return value
//  A pointer to the allocated array
//
// See also
//  E3d_MeshFreeVertices
//================================================================================
E3dSkinVertex* E3d_SkinVerticesAllocate(unsigned int LNumOfVertices, EBool LInitialize)
{
 E3dSkinVertex*	LSkinVertices;
 E3dSkinVertex*	LSkinVertex;

#ifdef E_MEMDEBUG
 E_MemName="SkinVertices by E3d_SkinVerticesAllocate";
#endif // E_MEMDEBUG
 LSkinVertices=(E3dSkinVertex*)EMalloc(sizeof(E3dSkinVertex)*LNumOfVertices);

 if(LSkinVertices)
 {
  unsigned int	LVC;

  if(LInitialize)
  {
   LSkinVertex=LSkinVertices;

   for(LVC=0;LVC<LNumOfVertices;LVC++, LSkinVertex++)
   {
    LSkinVertex->NumOfBones=0;
    LSkinVertex->Bones=NULL;
    LSkinVertex->Weights=NULL;
    LSkinVertex->Flags=0;
   }
  }
 }
 return(LSkinVertices);
}


//========================================
// Normal vector of a triangle
//========================================
void E3d_VertexNormal(E3dVector* LNormal, E3dVertex* LPoint0, E3dVertex* LPoint1, E3dVertex* LPoint2)
{
 double	LX0, LY0, LZ0, LX1, LY1, LZ1, LX2, LY2, LZ2;
 double	i, p, q;
 double	LR;

 LX0=LPoint0->X;LY0=LPoint0->Y;LZ0=LPoint0->Z;
 LX1=LPoint1->X;LY1=LPoint1->Y;LZ1=LPoint1->Z;
 LX2=LPoint2->X;LY2=LPoint2->Y;LZ2=LPoint2->Z;

 i=-((LY2-LY1)*(LZ2-LZ0)-(LZ2-LZ1)*(LY2-LY0));
 p=(LX2-LX1)*(LZ2-LZ0)-(LZ2-LZ1)*(LX2-LX0);
 q=-((LX2-LX1)*(LY2-LY0)-(LY2-LY1)*(LX2-LX0));
 LR=sqrt(i*i+p*p+q*q);if(LR>0.0) LR=1.0/LR;
 LNormal->X=i*LR;LNormal->Y=p*LR;LNormal->Z=q*LR;
 LNormal->Length=1.0;
}


//================================================
// Transform a Vertex with a given Matrix
//================================================
void E3d_VertexTransformByMatrix(E3dMatrix LMatrix, E3dVertex* LVertex)
{
 E3dCoordinate	LX, LY, LZ;

 LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
 LVertex->X=LX*LMatrix[M00]+LY*LMatrix[M10]+LZ*LMatrix[M20]+LMatrix[M30];
 LVertex->Y=LX*LMatrix[M01]+LY*LMatrix[M11]+LZ*LMatrix[M21]+LMatrix[M31];
 LVertex->Z=LX*LMatrix[M02]+LY*LMatrix[M12]+LZ*LMatrix[M22]+LMatrix[M32];
}






//================================================
// Count tagged Vertices on a Mesh
//================================================
unsigned int E3d_MeshNumOfTaggedVertices(E3dMesh* LMesh)
{
 E3dVertex*	LVertex=LMesh->Vertices;
 unsigned int	LC, LN=LMesh->NumOfVertices, LNumOfTaggedVertices=0;

 for(LC=0;LC<LN;LC++, LVertex++) if(LVertex->Flags&E3dVTXF_TAGGED) LNumOfTaggedVertices++;

 return(LNumOfTaggedVertices);
}


//========================================
// Add a new Polygon to a Mesh
//========================================
E3dPolygon* E3d_MeshAddPolygon(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dPolygon*	LPolygon;

 LPolygon=NULL;
 if(LPolyGroup->Polygons==NULL)
 {
  if((LPolyGroup->Polygons=LPolygon=E3d_PolygonsAllocate(1))!=NULL) LPolyGroup->NumOfPolygons=1;
 }
 else
 {
  if((LPolygon=(E3dPolygon*)ERealloc(LPolyGroup->Polygons, sizeof(E3dPolygon)*(LPolyGroup->NumOfPolygons+1)))!=NULL)
  {
   LPolyGroup->Polygons=LPolygon;
   LPolygon+=LPolyGroup->NumOfPolygons;
   LPolyGroup->NumOfPolygons++;
  }
 }

 if(LPolygon) E3d_PolygonDefault(LPolygon);

 return(LPolygon);
}


//========================================
// Add new Polygons to a Mesh
//========================================
E3dPolygon* E3d_MeshAddPolygons(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, unsigned int LNumOfPolys)
{
 E3dPolygon*	LPolygon;
 E3dPolygon*	LIPolygon;
 unsigned int	LC;


 LPolygon=NULL;
 if(LPolyGroup->Polygons==NULL)
 {
  if((LPolyGroup->Polygons=LPolygon=E3d_PolygonsAllocate(LNumOfPolys))!=NULL) LPolyGroup->NumOfPolygons=LNumOfPolys;
 }
 else
 {
  if((LPolygon=(E3dPolygon*)ERealloc(LPolyGroup->Polygons, sizeof(E3dPolygon)*(LPolyGroup->NumOfPolygons+LNumOfPolys)))!=NULL)
  {
   LPolyGroup->Polygons=LPolygon;
   LPolygon+=LPolyGroup->NumOfPolygons;
   LPolyGroup->NumOfPolygons+=LNumOfPolys;
  }
 }
 if(LPolygon)
 {
  for(LC=0, LIPolygon=LPolygon;LC<LNumOfPolys;LC++, LIPolygon++)
  {
   E3d_PolygonDefault(LIPolygon);
  }
 }
 return(LPolygon);
}


//================================================
// Add a Polygon to a Mesh and initialize it
//================================================
E3dPolygon* E3d_MeshAddAndInitPolygon(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, int LFlags, unsigned int LNumOfVertexNodes, ...)
{
 E3dPolygon*	LPolygon;
 va_list	LVertices;
 int		LVertexID;
 unsigned int	LVCnt, LNumOfVertices, LNumOfExteriorVertices;
 EBool		LIncExtVN;


 LPolygon=NULL;
 if(LPolyGroup->Polygons==NULL)
 {
  if((LPolyGroup->Polygons=LPolygon=E3d_PolygonsAllocate(1))==NULL) return(NULL);
  else LPolyGroup->NumOfPolygons=1;
 }
 else
 {
  if((LPolygon=(E3dPolygon*)ERealloc(LPolyGroup->Polygons, sizeof(E3dPolygon)*(LPolyGroup->NumOfPolygons+1)))==NULL) return(NULL);
  else { LPolyGroup->Polygons=LPolygon;LPolygon+=LPolyGroup->NumOfPolygons;LPolyGroup->NumOfPolygons++; }
 }

 if(LPolygon)
 {
  E3d_PolygonDefault(LPolygon);
  if(LNumOfVertexNodes>0)
  {
   if((LPolygon->VertexNodes=(E3dVertexNode*)EMalloc(sizeof(E3dVertexNode)*LNumOfVertexNodes))!=NULL)
   {
    va_start(LVertices, LNumOfVertexNodes);
     for(LVCnt=0, LNumOfVertices=0, LNumOfExteriorVertices=0, LIncExtVN=TRUE;LVCnt<LNumOfVertexNodes;LVCnt++)
     {
      LVertexID=va_arg(LVertices, int);
      LPolygon->VertexNodes[LVCnt].VertexID=LVertexID;
      if(LVertexID<=-1) LIncExtVN=FALSE;
      else LNumOfVertices++;
      if(LIncExtVN) LNumOfExteriorVertices++;
     }
    va_end(LVertices);
    LPolygon->NumOfVertices=LNumOfVertices;
    LPolygon->NumOfVertexNodes=LNumOfVertexNodes;
    LPolygon->NumOfExteriorVertices=LNumOfExteriorVertices;
    return(LPolygon);
   }
  }
  return(LPolygon);
 }
 return(NULL);
}


//================================================
// Remove a Polygon from a Mesh/PolyGroup
//================================================
void E3d_MeshRemovePolygon(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, E3dPolygon* LPolygon)
{
 E3dPolygon*	LPolygons;
 unsigned int	LN, LC;

 LPolygons=LPolyGroup->Polygons;
 LN=LPolyGroup->NumOfPolygons;

 if((LPolygon>LPolygons)&&(LPolygon<(LPolygons+LN)))
 {
  E3dM_PolygonFree(LPolygon);

  LC=LPolygon-LPolygons;

  if(LC<(LN-1)) memmove(LPolygon, LPolygon+1, sizeof(E3dPolygon)*(LN-LC-1));

  LPolyGroup->NumOfPolygons-=1;
 }
}


//================================================================================
// Add a TriangleStrip to a Mesh
//
// Arguments
//  E3dMesh*      LMesh        Pointer to the E3dMesh
//  E3dPolyGroup* LPolyGroup   The PolyGroup to which the strip will be added
//
// Description
//  Allocates space for and initializes a new TriangleStrip in the given Mesh
//  and PolyGroup.
//
// Return value
//  A pointer to the new strip or NULL if unsuccessful
//
// See also
//  E3d_TriangleStripsAllocate, E3d_MeshAddTriangleStrips
//================================================================================
E3dTriangleStrip* E3d_MeshAddTriangleStrip(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dTriangleStrip*	LTriangleStrip;

 LTriangleStrip=NULL;
 if(LPolyGroup->TriangleStrips==NULL)
 {
  if((LPolyGroup->TriangleStrips=LTriangleStrip=E3d_TriangleStripsAllocate(1))!=NULL) LPolyGroup->NumOfTriangleStrips=1;
 }
 else
 {
#ifdef E_MEMDEBUG
  E_MemName="TriangleStrips by E3d_MeshAddTriangleStrip";
#endif // E_MEMDEBUG

  if((LTriangleStrip=(E3dTriangleStrip*)ERealloc(LPolyGroup->TriangleStrips, sizeof(E3dTriangleStrip)*(LPolyGroup->NumOfTriangleStrips+1)))!=NULL)
  {
   LPolyGroup->TriangleStrips=LTriangleStrip;
   LTriangleStrip+=LPolyGroup->NumOfTriangleStrips;
   LPolyGroup->NumOfTriangleStrips+=1;
  }
 }
 if(LTriangleStrip)
 {
  LTriangleStrip->NumOfVertices=0;
  LTriangleStrip->VertexNodes=NULL;
  LTriangleStrip->Flags=0;
 }
 return(LTriangleStrip);
}


//================================================================================
// Add TriangleStrips to a Mesh
//
// Arguments
//  E3dMesh*      LMesh         Pointer to the Mesh
//  E3dPolyGroup* LPolyGroup    The polygroup to which the strip will be added
//  unsigned int  LNumOfStrips  Number of triangle strips to add
//
// Description
//  Allocates space for and initializes the specified number of TriangleStrips
//  in the given Mesh and PolyGroup.
//
// Return value
//  A pointer to the new strips or NULL if unsuccessful
//
// See also
//  E3d_TriangleStripsAllocate, E3d_MeshAddTriangleStrip
//================================================================================
E3dTriangleStrip* E3d_MeshAddTriangleStrips(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup,
                                             unsigned int LNumOfStrips)
{
 E3dTriangleStrip*	LTriangleStrip;
 E3dTriangleStrip*	LITriangleStrip;
 unsigned int		LC;


 LTriangleStrip=NULL;
 if(LPolyGroup->TriangleStrips==NULL)
 {
  if((LPolyGroup->TriangleStrips=LTriangleStrip=E3d_TriangleStripsAllocate(LNumOfStrips))!=NULL) LPolyGroup->NumOfTriangleStrips=LNumOfStrips;
 }
 else
 {
  if((LTriangleStrip=(E3dTriangleStrip*)ERealloc(LPolyGroup->TriangleStrips, sizeof(E3dTriangleStrip)*(LPolyGroup->NumOfTriangleStrips+LNumOfStrips)))!=NULL)
  {
   LPolyGroup->TriangleStrips=LTriangleStrip;
   LTriangleStrip+=LPolyGroup->NumOfTriangleStrips;
   LPolyGroup->NumOfTriangleStrips+=LNumOfStrips;
  }
 }
 if(LTriangleStrip)
 {
  for(LC=0, LITriangleStrip=LTriangleStrip;LC<LNumOfStrips;LC++, LITriangleStrip++)
  {
   LITriangleStrip->NumOfVertices=0;
   LITriangleStrip->VertexNodes=NULL;
   LITriangleStrip->Flags=0;
  }
 }
 return(LTriangleStrip);
}


//========================================
// Free vertices of a polygon Mesh
//========================================
void E3d_MeshFreeVertices(E3dMesh* LMesh)
{
 if(LMesh->Vertices)
 {
  EFree(LMesh->Vertices);
  LMesh->Vertices=NULL;LMesh->NumOfVertices=0;
 }
}


//=======================================
// Append a PolygGroup to a Mesh
//=======================================
E3dPolyGroup* E3d_MeshAppendPolyGroup(E3dMesh* LMesh,  E3dPolyGroup* LPolyGroup)
{
 E3dPolyGroup**	LPolyGroups;

 if(LMesh->PolyGroups==NULL)
 {
  if((LPolyGroups=(E3dPolyGroup**)EMalloc(sizeof(E3dPolyGroup*)))!=NULL) LMesh->PolyGroups=LPolyGroups;
 }
 else
 {
  if((LPolyGroups=(E3dPolyGroup**)ERealloc(LMesh->PolyGroups, sizeof(E3dPolyGroup*)*(LMesh->NumOfPolyGroups+1)))!=NULL)
  {
   LMesh->PolyGroups=LPolyGroups;
  }
 }
 if(LPolyGroups)
 {
  LPolyGroups[LMesh->NumOfPolyGroups]=LPolyGroup;

  LPolyGroup->RefCnt+=1;

  LMesh->NumOfPolyGroups+=1;
 }
 return(LPolyGroup);
}


//========================================================
// Allocate a new PolygGroup and add it to a Mesh
//========================================================
E3dPolyGroup* E3d_MeshAddPolyGroup(E3dMesh* LMesh)
{
 E3dPolyGroup*	LPolyGroup=NULL;
 E3dPolyGroup**	LPolyGroups;

 if(LMesh->PolyGroups==NULL)
 {
  if((LPolyGroups=(E3dPolyGroup**)EMalloc(sizeof(E3dPolyGroup*)))!=NULL) LMesh->PolyGroups=LPolyGroups;
 }
 else
 {
  if((LPolyGroups=(E3dPolyGroup**)ERealloc(LMesh->PolyGroups, sizeof(E3dPolyGroup*)*(LMesh->NumOfPolyGroups+1)))!=NULL)
  {
   LMesh->PolyGroups=LPolyGroups;
  }
 }

 if(LPolyGroups)
 {
  if((LPolyGroup=E3d_PolyGroupAllocate())!=NULL)
  {
   LPolyGroups[LMesh->NumOfPolyGroups]=LPolyGroup;

   LPolyGroup->RefCnt=1;

   LMesh->NumOfPolyGroups+=1;
  }
 }
 return(LPolyGroup);
}


//================================================================================
// Remove a PolyGroup from a Mesh
//
// Arguments
//  E3dMesh*  LMesh                Pointer to the Mesh
//  E3dPolyGroup* LPolyGroup       Pointer to the PolyGroup
//
// Description
//  Removes the given PolyGroup from the given Mesh's dynamically allocated
//  PolyGroups array.
//================================================================================
void E3d_MeshRemovePolyGroup(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dPolyGroup**	LPolyGroups;
 unsigned int	LC, LN;


 if((LPolyGroups=LMesh->PolyGroups)==NULL) return;

 LN=LMesh->NumOfPolyGroups;
 for(LC=0;LC<LN;LC++)
 {
  if(LPolyGroups[LC]==LPolyGroup)
  {
// FIXME: Store for Undo
//
   if(LC<(LN-1)) memmove(LPolyGroups+LC, LPolyGroups+LC+1, sizeof(E3dPolyGroup*)*(LN-LC-1));

   LMesh->NumOfPolyGroups-=1;
   if(LMesh->NumOfPolyGroups==0) { EFree(LMesh->PolyGroups);LMesh->PolyGroups=NULL; }
   else LMesh->PolyGroups=(E3dPolyGroup**)ERealloc(LPolyGroups, sizeof(E3dPolyGroup*)*LMesh->NumOfPolyGroups);
   break;
  }
 }
}


//================================================================================
// Remove a PolyGroup from a Mesh and free it
//
// Arguments
//  E3dMesh*      LMesh            Pointer to the Mesh
//  E3dPolyGroup* LPolyGroup       Pointer to the PolyGroup
//
// Description
//  Removes the given PolyGroup from the given Mesh's dynamically allocated
//  PolyGroups array and frees all memory assiciated with the PolyGroup.
//================================================================================
void E3d_MeshRemoveAndFreePolyGroup(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dPolyGroup**	LPolyGroups;
 unsigned int	LC, LN;


 if((LPolyGroups=LMesh->PolyGroups)==NULL) return;

 LN=LMesh->NumOfPolyGroups;
 for(LC=0;LC<LN;LC++)
 {
  if(LPolyGroups[LC]==LPolyGroup)
  {
   if(LC<(LN-1)) memmove(LPolyGroups+LC, LPolyGroups+LC+1, sizeof(E3dPolyGroup*)*(LN-LC-1));

// FIXME: Store for Undo
//
   E3d_PolyGroupFree(LPolyGroup);
   LMesh->NumOfPolyGroups-=1;
   if(LMesh->NumOfPolyGroups==0) { EFree(LMesh->PolyGroups);LMesh->PolyGroups=NULL; }
   else LMesh->PolyGroups=(E3dPolyGroup**)ERealloc(LPolyGroups, sizeof(E3dPolyGroup*)*LMesh->NumOfPolyGroups);
   break;
  }
 }
}



//================================================================================
// Initialize a Mesh
//
// Description
//  Initializes a Mesh structure.
//
// See also
//  E3d_MeshAllocate, E3d_MeshFree
//================================================================================
void E3d_MeshDefault(E3dMesh* LMesh)
{
 E3dM_GeometryDefault((E3dGeometry*)LMesh);
 LMesh->GeoType=E3dGEO_MESH;
 LMesh->NumOfVertices=0;
 LMesh->Vertices=NULL;
 LMesh->VertexUsageList=NULL;
 LMesh->UVs=NULL;
 LMesh->NumOfEdges=0;
 LMesh->Edges=NULL;
 LMesh->NumOfPolyGroups=0;
 LMesh->PolyGroups=NULL;

#ifdef USEOpenGL
 LMesh->GLDisplayListShaded=0;
 LMesh->GLDisplayListShaded2DTextured=0;
 LMesh->GLEdgeVertexIndexArray=NULL;

 LMesh->GLArrayVertex=NULL;
#endif // USEOpenGL
}


//================================================================================
// Allocate memory for and initialize a Mesh
//
// Description
//  Allocates memory a new Mesh structure and initializes it.
//
// Return value
//  A pointer to the new Mesh or NULL if unsuccessful.
//
// See also
//  Geometry/E3d_GeometryFree
//================================================================================
E3dMesh* E3d_MeshAllocate()
{
 E3dMesh*	LMesh;

 if((LMesh=(E3dMesh*)EMalloc(sizeof(E3dMesh)))!=NULL)
 {
  E3d_MeshDefault(LMesh);

  return(LMesh);
 }
 return(NULL);
}


//========================================================================
// Re-map Vertex indices of Polygons in a Mesh according to a table
//========================================================================
void E3d_MeshRemapVertexIndices(E3dMesh* LMesh, int* LInToNewVertexIndexTable)
{
 E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
 unsigned int	LC, LN=LMesh->NumOfPolyGroups;


 for(LC=0;LC<LN;LC++)
 {
  E3d_PolyGroupRemapVertexIndices(LPolyGroups[LC], LInToNewVertexIndexTable);
 }

 {
  E3dEdge*	LEdge=LMesh->Edges;

  LN=LMesh->NumOfEdges;
  for(LC=0;LC<LN;LC++, LEdge++)
  {
   LEdge->Start=LInToNewVertexIndexTable[LEdge->Start];
   LEdge->End=LInToNewVertexIndexTable[LEdge->End];
  }
 }
}


//========================================
// Free Polygons of a Mesh
//========================================
void E3d_MeshFreePolygons(E3dMesh* LMesh)
{
 E3dPolyGroup*	LPolyGroup;
 E3dPolyGroup**	LPolyGroups;
 unsigned int	LC, LN;


 for(LC=0, LN=LMesh->NumOfPolyGroups, LPolyGroups=LMesh->PolyGroups;LC<LN;LC++, LPolyGroup++)
 {
  LPolyGroup=LPolyGroups[LC];
  E3d_PolyGroupFreePolygons(LPolyGroup);
 }
}


//========================================================================================
// Free a Mesh's rendering-related data, such as OpenGL vertex arrays etc.
// we usually do this when a Mesh is deleted from the Scene, but stored for undo.
//========================================================================================
void E3d_MeshFreeRenderData(E3dMesh* LMesh)
{
 if(LMesh->PolyGroups)
 {
  E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
  E3dPolyGroup*		LPolyGroup;
  unsigned int		LC, LN=LMesh->NumOfPolyGroups;

  for(LC=0;LC<LN;LC++)
  {
   LPolyGroup=LPolyGroups[LC];
#ifdef USEOpenGL
   if(LPolyGroup->GLEdgeVertexIndexArray) { EFree(LPolyGroup->GLEdgeVertexIndexArray);LPolyGroup->GLEdgeVertexIndexArray=NULL; }
   if(LPolyGroup->GLTriangleVertexIndexArray) { EFree(LPolyGroup->GLTriangleVertexIndexArray);LPolyGroup->GLTriangleVertexIndexArray=NULL; }

// Free AGP arrays
//
   if(E3d_AGPFreeProc)
   {
    if(LPolyGroup->GLArrayVertexRGBA)
    {
     E3d_AGPFreeProc(LPolyGroup->GLArrayVertexRGBA);
     LPolyGroup->GLArrayVertexRGBA=NULL;LPolyGroup->GLArraySizeVertexRGBA=0;
    }
    if(LPolyGroup->GLArrayVertexRGBAST)
    {
     E3d_AGPFreeProc(LPolyGroup->GLArrayVertexRGBAST);
     LPolyGroup->GLArrayVertexRGBAST=NULL;LPolyGroup->GLArraySizeVertexRGBAST=0;
    }
    if(LPolyGroup->GLArrayVertexNormal)
    {
     E3d_AGPFreeProc(LPolyGroup->GLArrayVertexNormal);
     LPolyGroup->GLArrayVertexNormal=NULL;LPolyGroup->GLArraySizeVertexNormal=0;
    }
    if(LPolyGroup->GLArrayVertexNormalST)
    {
     E3d_AGPFreeProc(LPolyGroup->GLArrayVertexNormalST);
     LPolyGroup->GLArrayVertexNormalST=NULL;LPolyGroup->GLArraySizeVertexNormalST=0;
    }
   }

#endif // USEOpenGL
  }
 }

#ifdef USEOpenGL
 E3d_MeshRemoveGLDisplayLists(LMesh);

 if(LMesh->GLEdgeVertexIndexArray) { EFree(LMesh->GLEdgeVertexIndexArray);LMesh->GLEdgeVertexIndexArray=NULL; }

 if(LMesh->GLArrayVertex) { EFree(LMesh->GLArrayVertex);LMesh->GLArrayVertex=NULL; }	// In AGP memory
#endif // USEOpenGL
}


//================================================================================
// Free a Mesh
//
// Arguments
//  E3dMesh* LMesh             Pointer to the E3dMesh to be freed
//
// description
//  Frees the memory assicoiated with the given Mesh.
//  This is a low-level function. For proper reference-counting, applications
//  should call E3d_GeometryFree().
//
// See also
//  E3d_MeshAllocate, E3d_GeometryFree
//================================================================================
void E3d_MeshFree(E3dMesh* LMesh)
{
 E3dPolyGroup**	LPolyGroups;
 unsigned int	LC, LN;


 if(LMesh->Vertices) EFree(LMesh->Vertices);
 E3d_MeshFreeVertexUsageList(LMesh);
 if(LMesh->UVs) EFree(LMesh->UVs);
 if(LMesh->Edges) EFree(LMesh->Edges);

// Free PolyGroups
//
 if(LMesh->PolyGroups)
 {
  for(LC=0, LN=LMesh->NumOfPolyGroups, LPolyGroups=LMesh->PolyGroups;LC<LN;LC++)
  {
   E3d_PolyGroupFree(LPolyGroups[LC]);
  }
  EFree(LMesh->PolyGroups);
 }

#ifdef USEOpenGL
 E3d_MeshRemoveGLDisplayLists(LMesh);

 if(LMesh->GLEdgeVertexIndexArray) EFree(LMesh->GLEdgeVertexIndexArray);

 if(LMesh->GLArrayVertex) EFree(LMesh->GLArrayVertex);	// In AGP memory
#endif // USEOpenGL

 EFree(LMesh);
}



//========================================================================
// Clone a Mesh
//
// Argument
//  E3dMesh* LMesh         Pointer to the Mesh structure to be cloned
//  int      LFlags        Flags determining what to clone / share
//
// Description
//  This function creates and exact copy of the given Mesh.
//  If LFlags has the flag E3dCLONE_MATERIALS set, all the Materials of
//  the PolyGroups will be cloned as well, otherwise the Materials will
//  be shared between the original Mesh and the clone.
//
// Return value
//  Pointer to the new Mesh structure, or NULL in case of an error
//
// See also
//  E3d_MeshAllocate, E3d_MeshFree
//========================================================================
E3dMesh* E3d_MeshClone(E3dMesh* LMesh, int LFlags)
{
 E3dMesh* 	LDMesh=E3d_MeshAllocate();

 if(LDMesh)
 {
  E3dVertex*		LDVertices;
  E3dPolyGroup*		LPolyGroup;
  E3dPolyGroup**	LPolyGroups;
  E3dPolyGroup*		LDPolyGroup=NULL;
  unsigned int		LGC, LGN;

  if((LDMesh->Vertices=LDVertices=E3d_VerticesAllocate(LDMesh->NumOfVertices=LMesh->NumOfVertices, FALSE))!=NULL)
  {
   memcpy(LDVertices, LMesh->Vertices, sizeof(E3dVertex)*LMesh->NumOfVertices);
   LDMesh->NumOfVertices=LMesh->NumOfVertices;
  }

  LGN=LMesh->NumOfPolyGroups;

// Clone PolyGroups
//
  LPolyGroups=LMesh->PolyGroups;
  for(LGC=0;LGC<LGN;LGC++)
  {
   LPolyGroup=LPolyGroups[LGC];

   if((LDPolyGroup=E3d_PolyGroupClone(LPolyGroup, LFlags))!=NULL)
   {
    E3d_MeshAppendPolyGroup(LDMesh, LDPolyGroup);
   }
  }
 }
 return(LDMesh);
}


//================================================
// Recalculate Polygon normals of a Mesh
//================================================
void E3d_MeshRefreshPolygonNormals(E3dMesh* LMesh)
{
 E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
 E3dPolyGroup*	LPolyGroup;
 unsigned int	LC, LN=LMesh->NumOfPolyGroups;

 for(LC=0;LC<LN;LC++)
 {
  LPolyGroup=LPolyGroups[LC];
  E3d_MeshPolyGroupRefreshPolygonNormals(LMesh, LPolyGroup);
 }
}


//========================================================
// Calculate Polygon and Vertex normals of a Mesh
//========================================================
void E3d_MeshRefreshNormals(E3dMesh* LMesh, EBool LRefreshPolygonNormals)
{
 E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
 E3dPolyGroup*	LPolyGroup;
 unsigned int	LC, LN=LMesh->NumOfPolyGroups;


// To avoid repeated calls to E3d_MeshGetVertexUsageList(LMesh) by E3d_MeshPolyGroupRefreshNormals()...
//
 E3d_MeshGetVertexUsageList(LMesh);

 for(LC=0;LC<LN;LC++)
 {
  LPolyGroup=LPolyGroups[LC];
  E3d_MeshPolyGroupRefreshNormals(LMesh, LPolyGroup, LPolyGroup->VertexNormalType, LRefreshPolygonNormals);
 }

 E3d_MeshFreeVertexUsageList(LMesh);
}


//================================================================================
// Create Edge list for the given Mesh from its Polygon and Triangle strip data
// If LJustThisPolyGroup is NULL, all the PolyGroup Edges will be updated
//================================================================================
void E3d_MeshCreateEdgesFromPolyData(E3dMesh* LMesh, E3dPolyGroup* LJustThisPolyGroup)
{
 E3dPolyGroup*		LPolyGroup;
 E3dPolyGroup**		LPolyGroups=LMesh->PolyGroups;
 E3dPolygon*		LPolygon;
 E3dTriangleStrip*	LTriangleStrip;
 E3dVertexNode*		LPolyVertNodes;
 E3dEdge*		LEdges;
 E3dEdge*		LNextEdge;
 E3dVertexLink*		LVertexLinks=NULL;
 unsigned int*		LVLinks;
 int			LGCnt, LGNum=LMesh->NumOfPolyGroups,
			LPCStart, LPNum;
 int			LCVCnt, LVCnt, LVNum, LPCnt, LNEdgeNum=0, LECnt, LEdgeNum, LActStartV, LActEndV, LTmpV, LStartV, LEndV;
 int			LMeshVertexNum=LMesh->NumOfVertices;
 unsigned short		LLNum;
 EBool			LEFound, LEdgesOk;


// Add up maximum possible number of Edges
//
 for(LGCnt=0;LGCnt<LGNum;LGCnt++)
 {
  LPolyGroup=LPolyGroups[LGCnt];

  if((LPolygon=LPolyGroup->Polygons)!=NULL)
  {
   LPNum=LPolyGroup->NumOfPolygons;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++) LNEdgeNum+=LPolygon->NumOfVertices;
  }

  if((LTriangleStrip=LPolyGroup->TriangleStrips)!=NULL)
  {
   LPNum=LPolyGroup->NumOfTriangleStrips;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++) LNEdgeNum+=LTriangleStrip->NumOfVertices*2-3;
  }
 }

 if(LNEdgeNum==0) return;

 if(LMesh->Edges) { EFree(LMesh->Edges);LMesh->Edges=NULL;LMesh->NumOfEdges=0; }

// Allocate maximum possible number of edges (all Polygons, all TriangleStrips all Edges of the Mesh)
//
 LMesh->Edges=E3d_EdgesAllocate(LNEdgeNum);
 LEdgesOk=TRUE;
 LEdgeNum=0;
 LEdges=LMesh->Edges;LNextEdge=LEdges;


// Initialize these separately, so the old-fashioned method can pick up at the
// PolyGroup and Polygon/TriangleStrip where the optimized method gave up
//
 LGCnt=0;LPolyGroups=LMesh->PolyGroups;LPCnt=0;


// Try to allocate Vertex link-list for fast edge conversion
//
 if((LVertexLinks=(E3dVertexLink*)EMalloc(sizeof(E3dVertexLink)*LMeshVertexNum))!=NULL)
 {
  for(LVCnt=0;LVCnt<LMeshVertexNum;LVCnt++) LVertexLinks[LVCnt].LinkedToNum=0;

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

// Edges on Polygons/TriangleStrips
//
   if((LPolygon=LPolyGroup->Polygons)!=NULL)
   {
    LPNum=LPolyGroup->NumOfPolygons;

    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
    {
     LPolyVertNodes=LPolygon->VertexNodes;
     if(LPolyVertNodes)
     {
      LVNum=LPolygon->NumOfExteriorVertices;
//------------------------------------------------
// First (exterior) contour of the Polygon
//------------------------------------------------
      for(LVCnt=0;LVCnt<(LVNum-1);LVCnt++)		//  First n-1 Edges of the exterior contour
      {
       LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LVCnt+1].VertexID;
       if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
       if((LLNum=LVertexLinks[LActStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
       LVLinks=LVertexLinks[LActStartV].Vertices;
       for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
       {
	if(LVLinks[LECnt]==LActEndV) { LEFound=TRUE;break; }
       }
       if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
      }
      if(!LEdgesOk) break;

// Last edge: Last vertex->first vertex
//
      LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[0].VertexID;
      if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
      if((LLNum=LVertexLinks[LActStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
      LVLinks=LVertexLinks[LActStartV].Vertices;
      for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
      {
       if(LVLinks[LECnt]==LActEndV) { LEFound=TRUE;break; }
      }
      if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }

      LVNum=LPolygon->NumOfVertexNodes;
//------------------------------------------------
// Other contours of the Polygon (holes)
//------------------------------------------------
      LVCnt+=2;
      while(LVCnt<(LVNum-1))
      {
       for(LCVCnt=LVCnt;(LVCnt<(LVNum-1))&&(LPolyVertNodes[LVCnt+1].VertexID>-1);LVCnt++)
       {
	LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LVCnt+1].VertexID;
	if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Make LActStartV always be the lower one
	if((LLNum=LVertexLinks[LActStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
	LVLinks=LVertexLinks[LActStartV].Vertices;
	for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
	{
	 if(LVLinks[LECnt]==LActEndV) { LEFound=TRUE;break; }
	}
	if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
       }
       if(!LEdgesOk) break;

// Last edge: Last vertex->first vertex
//
       LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LCVCnt].VertexID;
       if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
       if((LLNum=LVertexLinks[LActStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
       LVLinks=LVertexLinks[LActStartV].Vertices;
       for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
       {
	if(LVLinks[LECnt]==LActEndV) { LEFound=TRUE;break; }
       }
       if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
       if(LVCnt<(LVNum-1)) LVCnt+=2;
      }
     }
    }
   }

// Do Triangle strips
//
   if((LTriangleStrip=LPolyGroup->TriangleStrips)!=NULL)
   {
    LPNum=LPolyGroup->NumOfTriangleStrips;

    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
    {
     LPolyVertNodes=LTriangleStrip->VertexNodes;
     if(LPolyVertNodes)
     {
      LVNum=LTriangleStrip->NumOfVertices;

// First edge of the TriangleStrip
//
      LActStartV=LPolyVertNodes[0].VertexID;LActEndV=LPolyVertNodes[1].VertexID;

// Make LStartV always the lower one
//
      if(LActStartV>LActEndV) { LStartV=LActEndV;LEndV=LActStartV; }
      else { LStartV=LActStartV;LEndV=LActEndV; }

      if((LLNum=LVertexLinks[LStartV].LinkedToNum)>=MAX_VLINKNUM) LEdgesOk=FALSE;
      else
      {
       LVLinks=LVertexLinks[LStartV].Vertices;
       for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
       {
        if(LVLinks[LECnt]==LEndV) { LEFound=TRUE;break; }
       }
       if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
      }


      for(LVCnt=2;LVCnt<LVNum;LVCnt++)
      {
       LActStartV=LPolyVertNodes[LVCnt-2].VertexID;LActEndV=LPolyVertNodes[LVCnt].VertexID;

// Make LStartV always the lower one
//
       if(LActStartV>LActEndV) { LStartV=LActEndV;LEndV=LActStartV; }
       else { LStartV=LActStartV;LEndV=LActEndV; }

       if((LLNum=LVertexLinks[LStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
       LVLinks=LVertexLinks[LStartV].Vertices;
       for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
       {
	if(LVLinks[LECnt]==LEndV) { LEFound=TRUE;break; }
       }
       if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }

       LActStartV=LPolyVertNodes[LVCnt-1].VertexID;

// Make LStartV always the lower one
//
       if(LActStartV>LActEndV) { LStartV=LActEndV;LEndV=LActStartV; }
       else { LStartV=LActStartV;LEndV=LActEndV; }

       if((LLNum=LVertexLinks[LStartV].LinkedToNum)>=MAX_VLINKNUM) { LEdgesOk=FALSE;break; }
       LVLinks=LVertexLinks[LStartV].Vertices;
       for(LECnt=0, LEFound=FALSE;LECnt<LLNum;LECnt++)
       {
	if(LVLinks[LECnt]==LEndV) { LEFound=TRUE;break; }
       }
       if(!LEFound) { LVLinks[LLNum]=LActEndV;LVertexLinks[LActStartV].LinkedToNum++;LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
      }
      if(!LEdgesOk) break;
     }
    }
   }


   if(!LEdgesOk) break;	// If there was an error, quit the PolyGroup loop
  }
 }


// If the optimized function above could not proceed (either because we couldn't allocate the vertex
// link array or because there were vertices with more than MAX_VLINKNUM edges connected to them),
// do it the old-fashioned way
//

// Continue with the Polygon where we left off
//
 LPCStart=LPCnt;
 for(;LGCnt<LGNum;LGCnt++)
 {
  LPolyGroup=LPolyGroups[LGCnt];
  LPolygon=LPolyGroup->Polygons+LPCStart;LPNum=LPolyGroup->NumOfPolygons;

  for(LPCnt=LPCStart;LPCnt<LPNum;LPCnt++, LPolygon++)
  {
   LPolyVertNodes=LPolygon->VertexNodes;
   if(LPolyVertNodes)
   {
    LVNum=LPolygon->NumOfExteriorVertices;
//------------------------------------------------
// First (exterior) contour of the Polygon
//------------------------------------------------
    for(LVCnt=0;LVCnt<(LVNum-1);LVCnt++)		//  First n-1 edges of the exterior contour
    {
     LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LVCnt+1].VertexID;
     if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
     for(LECnt=0, LEFound=FALSE;LECnt<LEdgeNum;LECnt++)
     {
      if((LEdges[LECnt].Start==LActStartV)&&(LEdges[LECnt].End==LActEndV)) { LEFound=TRUE;break; }
     }
     if(!LEFound) { LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
    }

// Last edge: Last vertex->first vertex
//
    LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[0].VertexID;
    if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
    for(LECnt=0, LEFound=FALSE;LECnt<LEdgeNum;LECnt++)
    {
     if((LEdges[LECnt].Start==LActStartV)&&(LEdges[LECnt].End==LActEndV)) { LEFound=TRUE;break; }
    }
    if(!LEFound) { LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }

    LVNum=LPolygon->NumOfVertexNodes;
//------------------------------------------------
// Other contours of the Polygon (holes)
//------------------------------------------------
    LVCnt+=2;
    while(LVCnt<(LVNum-1))
    {
     for(LCVCnt=LVCnt;(LVCnt<(LVNum-1))&&(LPolyVertNodes[LVCnt+1].VertexID>-1);LVCnt++)
     {
      LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LVCnt+1].VertexID;
      if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Make LActStartV always be the lower one
      for(LECnt=0, LEFound=FALSE;LECnt<LEdgeNum;LECnt++)
      {
       if((LEdges[LECnt].Start==LActStartV)&&(LEdges[LECnt].End==LActEndV)) { LEFound=TRUE;break; }
      }
      if(!LEFound) { LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
     }
     LActStartV=LPolyVertNodes[LVCnt].VertexID;LActEndV=LPolyVertNodes[LCVCnt].VertexID;
     if(LActStartV>LActEndV) { LTmpV=LActStartV;LActStartV=LActEndV;LActEndV=LTmpV; }	// Let LActStartV always be the lower one
     for(LECnt=0, LEFound=FALSE;LECnt<LEdgeNum;LECnt++)
     {
      if((LEdges[LECnt].Start==LActStartV)&&(LEdges[LECnt].End==LActEndV)) { LEFound=TRUE;break; }
     }
     if(!LEFound) { LNextEdge->Start=LActStartV;LNextEdge->End=LActEndV;LEdgeNum++;LNextEdge++; }
     if(LVCnt<(LVNum-1)) LVCnt+=2;
    }
   }
  }
  LPCStart=0;
 }
 LMesh->NumOfEdges=LEdgeNum;
 if(LEdgeNum<LNEdgeNum) LMesh->Edges=(E3dEdge*)ERealloc(LMesh->Edges, sizeof(E3dEdge)*LNEdgeNum);	// Free unnecessary Edges


// Create Edge lists for the PolyGroups
//
 if(LJustThisPolyGroup)
 {
// If the Mesh has only one PolyGroup, that can use the Edges of the Mesh!
//
  if(LMesh->NumOfPolyGroups>1)
  {
   for(LVCnt=0;LVCnt<LMeshVertexNum;LVCnt++) LVertexLinks[LVCnt].LinkedToNum=0;
   E3d_PolyGroupCreateEdgesFromPolyData(LJustThisPolyGroup, LVertexLinks);
  }
  else if(LJustThisPolyGroup->Edges) { EFree(LJustThisPolyGroup->Edges);LJustThisPolyGroup->Edges=NULL;LJustThisPolyGroup->NumOfEdges=0; }
 }
 else
 {
  for(LGCnt=0;LGCnt<LGNum;LGCnt++)
  {
   LPolyGroup=LPolyGroups[LGCnt];

// If the Mesh has only one PolyGroup, that can use the Edges of the Mesh!
//
   if(LMesh->NumOfPolyGroups>1)
   {
    for(LVCnt=0;LVCnt<LMeshVertexNum;LVCnt++) LVertexLinks[LVCnt].LinkedToNum=0;
    E3d_PolyGroupCreateEdgesFromPolyData(LPolyGroup, LVertexLinks);
   }
   else if(LPolyGroup->Edges) { EFree(LPolyGroup->Edges);LPolyGroup->Edges=NULL;LPolyGroup->NumOfEdges=0; }
  }
 }

 if(LVertexLinks) EFree(LVertexLinks);
}


//========================================================================================
// Mark Vertices of a Mesh that pass the given filter criteria
//
// Argument
//  E3dMesh*       LMesh              Pointer to the Mesh structure
//  unsigned int   LWhatToInclude     The filter criteria (OR-ed together bits)
//
// Description
//  This function examines the given Mesh and marks the Vertices that pass the given
//  filter criteria by setting their E3dVTX_PASSED flag.
//
// Example
//  E3d_MeshFilterVertices(LMesh, E3dBB_SELECTED_POLYGROUPS);<BR>
//  This will mark the Vertices PASSED, that are referenced from the selected PolyGroups of
//  the Mesh.
//  For improved efficiency, if all the Vertices pass, no Vertex flags are set, but the
//  returned value will be equal to LMesh->NumOfVertices.
//
// Return value
//  The number of Vertices that passed the test.
//
// See also
//  E3d_MeshGetBoundingBox
//========================================================================================
unsigned int E3d_MeshFilterVertices(E3dMesh* LMesh, unsigned int LWhatToInclude)
{
 if(LWhatToInclude==E3dBB_ALL) return(LMesh->NumOfVertices);
 else
 {
  E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
  E3dPolyGroup*		LPolyGroup;
  E3dPolygon*		LPolygon;
  E3dVertexNode*	LVertexNode;
  E3dVertex*		LVertices=LMesh->Vertices;
  E3dVertex*		LVertex;
  unsigned int		LPGCnt, LNumOfPolyGroups=LMesh->NumOfPolyGroups,
			LPCnt, LVCnt,
			LNumOfVerticesPassed, LNumOfVertices=LMesh->NumOfVertices;


// If the whole Geometry is selected, all Vertices pass
//
  if(LWhatToInclude&E3dBB_SELECTED_GEOMETRY)
  {
   if(LMesh->Selection==E3dGSEL_GEOMETRY) return(LNumOfVertices);
  }

// Clear Vertex E3dVTX_PASSED flags
//
  LVertex=LVertices;
  LVCnt=LNumOfVertices;
  do
  {
   LVertex->Flags&=(E3dVTXF_ALL-E3dVTXF_PASSED);
   LVertex++;
  } while(--LVCnt);


  LNumOfVerticesPassed=0;

  if(LWhatToInclude&E3dBB_SELECTED_GEOMETRY)
  {
   for(LPGCnt=0;LPGCnt<LNumOfPolyGroups;LPGCnt++)
   {
    LPolyGroup=LPolyGroups[LPGCnt];

    if(LPolyGroup->Selected)
    {
     LPolygon=LPolyGroup->Polygons;
     LPCnt=LPolyGroup->NumOfPolygons;
     if(E3d_PolyGroupNumOfSelectedPolygons(LPolyGroup)>0)
     {
      do
      {
       if(LPolygon->Flags&E3dPolyFlagSELECTED)
       {
	LVertexNode=LPolygon->VertexNodes;
	LVCnt=LPolygon->NumOfExteriorVertices;

	do
	{
	 LVertex=LVertices+LVertexNode->VertexID;
	 if((LVertex->Flags&E3dVTXF_PASSED)==0) { LVertex->Flags|=E3dVTXF_PASSED;LNumOfVerticesPassed++; }
	 LVertexNode++;
	} while(--LVCnt);
       }
       LPolygon++;
      } while(--LPCnt);
     }
     else
     {
      do
      {
       LVertexNode=LPolygon->VertexNodes;
       LVCnt=LPolygon->NumOfExteriorVertices;

       do
       {
	LVertex=LVertices+LVertexNode->VertexID;
	if((LVertex->Flags&E3dVTXF_PASSED)==0) { LVertex->Flags|=E3dVTXF_PASSED;LNumOfVerticesPassed++; }
	LVertexNode++;
       } while(--LVCnt);

       LPolygon++;
      } while(--LPCnt);
     }
    }
   }
  }


  return(LNumOfVerticesPassed);
 }
}


//========================================================================================
// Get the local bounding box of a Mesh
//
// Argument
//  E3dMesh*       LMesh       Pointer to the Mesh structure
//  E3d3DPosition* LBBMin      Returned bounding box point 0
//  E3d3DPosition* LBBMax      Returned bounding box point 1
//
// Description
//  This function returns the minimum and maximum XYZ values of the bounding box of the
//  given Mesh.
//
// Return value
//  TRUE if successful, FALSE in case of an error.
//
// See also
//  E3d_MeshGetTransformedBoundingBox
//========================================================================================
EBool E3d_MeshGetBoundingBox(E3dMesh* LMesh,
                              E3d3DPosition* LBBMin, E3d3DPosition* LBBMax,
                              unsigned int LWhatToInclude)
{
 if(LMesh->NumOfVertices>0)
 {
  E3dVertex*	LVertex;
  unsigned int	LC, LN, LNVPassed;


  LNVPassed=E3d_MeshFilterVertices(LMesh, LWhatToInclude);

  if(LNVPassed<1) return(FALSE);

  LVertex=LMesh->Vertices;

  LN=LMesh->NumOfVertices;

  if(LNVPassed==LN)
  {
// Must initialize BBox on first Vertex
//
   LBBMin->X = LBBMax->X = LVertex->X;
   LBBMin->Y = LBBMax->Y = LVertex->Y;
   LBBMin->Z = LBBMax->Z = LVertex->Z;
   LVertex++;

   for(LC=1;LC<LN;LC++)
   {
    if(LVertex->X < LBBMin->X) LBBMin->X = LVertex->X;
    if(LVertex->X > LBBMax->X) LBBMax->X = LVertex->X;

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

    if(LVertex->Z < LBBMin->Z) LBBMin->Z = LVertex->Z;
    if(LVertex->Z > LBBMax->Z) LBBMax->Z = LVertex->Z;
    LVertex++;
   }
   return(TRUE);
  }
  else
  {
// Must initialize BBox on first PASSED Vertex
//
   for(LC=0;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     LBBMin->X = LBBMax->X = LVertex->X;
     LBBMin->Y = LBBMax->Y = LVertex->Y;
     LBBMin->Z = LBBMax->Z = LVertex->Z;

// Don't test this Vertex again
//
     LC++;LVertex++;
     break;
    }
    LVertex++;
   }

   for(;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     if(LVertex->X < LBBMin->X) LBBMin->X = LVertex->X;
     if(LVertex->X > LBBMax->X) LBBMax->X = LVertex->X;

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

     if(LVertex->Z < LBBMin->Z) LBBMin->Z = LVertex->Z;
     if(LVertex->Z > LBBMax->Z) LBBMax->Z = LVertex->Z;
    }
    LVertex++;
   }
   return(TRUE);
  }

 }

 return(FALSE);
}


//========================================================================================
// Get the bounding box of a Mesh aligned to an arbitrary coordinate system
//
// Argument
//  E3dMesh*       LMesh       Pointer to the Mesh structure
//  E3dMatrix      LMatrix     The Matrix that defines the coordinate system
//  E3d3DPosition* LBBMin      Returned bounding box point 0
//  E3d3DPosition* LBBMax      Returned bounding box point 1
//
// Description
//  This function transforms each Vertex of the given Mesh with the given Matrix and
//  returns the minimum and maximum XYZ values of the resulting bounding box.
//
// Example
//  E3d_MeshGetTransformedBoundingBox(LMesh, LModel->LocalToWorldMatrix, LBBMin, LBBMax);
//  This will return the "world-aligned" bounding box of the LMesh (assuming that
//  LMesh is a Geometry of LModel).
//
// Return value
//  TRUE if successful, FALSE in case of an error.
//
// See also
//  E3d_MeshGetBoundingBox
//========================================================================================
EBool E3d_MeshGetTransformedBoundingBox(E3dMesh* LMesh,
                                         E3dMatrix LMatrix,
                                         E3d3DPosition* LBBMin, E3d3DPosition* LBBMax,
                                         unsigned int LWhatToInclude)
{
 if(LMesh->NumOfVertices>0)
 {
  E3dVertex*	LVertex;
  E3dCoordinate	mX, mY, mZ, LFX, LFY, LFZ;
  unsigned int	LC, LN, LNVPassed;
 

  LNVPassed=E3d_MeshFilterVertices(LMesh, LWhatToInclude);

  if(LNVPassed<1) return(FALSE);

  LVertex=LMesh->Vertices;

  LN=LMesh->NumOfVertices;

  if(LNVPassed==LN)
  {
// Must initialize BBox on first Vertex
//
   mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
   E3dM_MatrixTransform3x4(LMatrix, LBBMax->X, LBBMax->Y, LBBMax->Z);
   LBBMin->X = LBBMax->X;
   LBBMin->Y = LBBMax->Y;
   LBBMin->Z = LBBMax->Z;
   LVertex++;


   for(LC=1;LC<LN;LC++)
   {
    mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
    E3dM_MatrixTransform3x4(LMatrix, LFX, LFY, LFZ);
    if(LFX < LBBMin->X) LBBMin->X = LFX;
    if(LFX > LBBMax->X) LBBMax->X = LFX;

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

    if(LFZ < LBBMin->Z) LBBMin->Z = LFZ;
    if(LFZ > LBBMax->Z) LBBMax->Z = LFZ;
    LVertex++;
   }
  }
  else
  {
// Must initialize BBox on first PASSED Vertex
//
   for(LC=0;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
     E3dM_MatrixTransform3x4(LMatrix, LBBMax->X, LBBMax->Y, LBBMax->Z);
     LBBMin->X = LBBMax->X;
     LBBMin->Y = LBBMax->Y;
     LBBMin->Z = LBBMax->Z;

// Don't test this Vertex again
//
     LC++;LVertex++;
     break;
    }
    LVertex++;
   }


   for(;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
     E3dM_MatrixTransform3x4(LMatrix, LFX, LFY, LFZ);
     if(LFX < LBBMin->X) LBBMin->X = LFX;
     if(LFX > LBBMax->X) LBBMax->X = LFX;

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

     if(LFZ < LBBMin->Z) LBBMin->Z = LFZ;
     if(LFZ > LBBMax->Z) LBBMax->Z = LFZ;
    }
    LVertex++;
   }
  }


  return(TRUE);
 }

 return(FALSE);
}


//================================================================================
// Compute the perspective-pjojected bounding box (bounding frustum) of a Mesh
//
// LMaxProjectedXYAbsValPoint will have the non-transformed XYZ
// of the Vertex with largest projected absolute value of
// X or Y.
// The W will have that maximum X or Y absolute value.
//================================================================================
EBool E3d_MeshGetPerspectiveProjectedBoundingBox(E3dMesh* LMesh, E3dMatrix LMatrix, E3d2DPosition* LBBMin, E3d2DPosition* LBBMax, E3dHPosition* LMaxProjectedXYAbsValPoint, unsigned int LWhatToInclude)
{
 if(LMesh->NumOfVertices>0)
 {
  E3dCoordinate	mX, mY, mZ, LXF, LYF, LZF, LWF,
		LXOfMaxXYAbsVal=0.0, LYOfMaxXYAbsVal=0.0, LZOfMaxXYAbsVal=0.0, LMaxXYAbsVal=0.0;
  E3dVertex*	LVertex;
  unsigned int	LC, LN, LNVPassed;


  LNVPassed=E3d_MeshFilterVertices(LMesh, LWhatToInclude);

  if(LNVPassed<1) return(FALSE);

  LVertex=LMesh->Vertices;

  LN=LMesh->NumOfVertices;

  if(LNVPassed==LN)
  {
// Must initialize BBox on first Vertex
//
   mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;

   E3dM_MatrixTransformHPosition(LMatrix, LXF, LYF, LZF, LWF);LWF=1.0/LWF;
   LXF*=LWF;LYF*=LWF;

   LBBMin->X = LBBMax->X = LXF;
   LBBMin->Y = LBBMax->Y = LYF;

   LXOfMaxXYAbsVal = LVertex->X;
   LYOfMaxXYAbsVal = LVertex->Y;
   LZOfMaxXYAbsVal = LVertex->Z;
   if(E3dM_ABS(LXF)>E3dM_ABS(LYF)) LMaxXYAbsVal=E3dM_ABS(LXF);
   else LMaxXYAbsVal=E3dM_ABS(LYF);

   LVertex++;

   for(LC=1;LC<LN;LC++)
   {
    mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;

    E3dM_MatrixTransformHPosition(LMatrix, LXF, LYF, LZF, LWF);LWF=1.0/LWF;
    LXF*=LWF;LYF*=LWF;

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

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

    if(E3dM_ABS(LXF)>E3dM_ABS(LYF))
    {
     if(E3dM_ABS(LXF)>LMaxXYAbsVal)
     {
      LXOfMaxXYAbsVal = LVertex->X;
      LYOfMaxXYAbsVal = LVertex->Y;
      LZOfMaxXYAbsVal = LVertex->Z;
      LMaxXYAbsVal=E3dM_ABS(LXF);
     }
    }
    else
    {
     if(E3dM_ABS(LYF)>LMaxXYAbsVal)
     {
      LXOfMaxXYAbsVal = LVertex->X;
      LYOfMaxXYAbsVal = LVertex->Y;
      LZOfMaxXYAbsVal = LVertex->Z;
      LMaxXYAbsVal=E3dM_ABS(LYF);
     }
    }

    LVertex++;
   }
  }
  else
  {
// Must initialize BBox on first PASSED Vertex
//
   for(LC=0;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;

     E3dM_MatrixTransformHPosition(LMatrix, LXF, LYF, LZF, LWF);LWF=1.0/LWF;
     LXF*=LWF;LYF*=LWF;

     LBBMin->X = LBBMax->X = LXF;
     LBBMin->Y = LBBMax->Y = LYF;

     LXOfMaxXYAbsVal = LVertex->X;
     LYOfMaxXYAbsVal = LVertex->Y;
     LZOfMaxXYAbsVal = LVertex->Z;
     if(E3dM_ABS(LXF)>E3dM_ABS(LYF)) LMaxXYAbsVal=E3dM_ABS(LXF);
     else LMaxXYAbsVal=E3dM_ABS(LYF);
// Don't test this Vertex again
//
     LC++;LVertex++;
     break;
    }
    LVertex++;
   }

   for(;LC<LN;LC++)
   {
    if(LVertex->Flags&E3dVTXF_PASSED)
    {
     mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;

     E3dM_MatrixTransformHPosition(LMatrix, LXF, LYF, LZF, LWF);LWF=1.0/LWF;
     LXF*=LWF;LYF*=LWF;

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

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

     if(E3dM_ABS(LXF)>E3dM_ABS(LYF))
     {
      if(E3dM_ABS(LXF)>LMaxXYAbsVal)
      {
       LXOfMaxXYAbsVal = LVertex->X;
       LYOfMaxXYAbsVal = LVertex->Y;
       LZOfMaxXYAbsVal = LVertex->Z;
       LMaxXYAbsVal=E3dM_ABS(LXF);
      }
     }
     else
     {
      if(E3dM_ABS(LYF)>LMaxXYAbsVal)
      {
       LXOfMaxXYAbsVal = LVertex->X;
       LYOfMaxXYAbsVal = LVertex->Y;
       LZOfMaxXYAbsVal = LVertex->Z;
       LMaxXYAbsVal=E3dM_ABS(LYF);
      }
     }
    }
    LVertex++;
   }
  }

  LMaxProjectedXYAbsValPoint->X = LXOfMaxXYAbsVal;
  LMaxProjectedXYAbsValPoint->Y = LYOfMaxXYAbsVal;
  LMaxProjectedXYAbsValPoint->Z = LZOfMaxXYAbsVal;
  LMaxProjectedXYAbsValPoint->W = LMaxXYAbsVal;

  return(TRUE);
 }

 return(FALSE);
}


//================================================
// Add a new Vertex to a Mesh
//================================================
E3dVertex* E3d_MeshAddVertex(E3dMesh* LMesh)
{
 E3dVertex*	LVertices;

 if((LVertices=E3d_VerticesReallocate(LMesh->Vertices, LMesh->NumOfVertices+1, FALSE)))
 {
  LMesh->Vertices=LVertices;
  LMesh->NumOfVertices+=1;
  return(LVertices+LMesh->NumOfVertices-1);
 }
 else return(NULL);
}


//================================================================================================
// Get Polygons that share a Vertex
//================================================================================================
unsigned int E3d_MeshVertexGetPolygons(E3dMesh* LMesh, int LVertexIndex, E3dPolygon*** LPolygonsPtr, E3dVertexNode*** LVertexNodesPtr)
{
 E3dPolyGroup*		LPolyGroup;
 E3dPolygon*		LPolygon;
 E3dPolygon**		LPolygons=NULL;
 E3dVertexNode*		LVertexNode;
 E3dVertexNode**	LVertexNodes=NULL;
 unsigned int		LGC, LGN=LMesh->NumOfPolyGroups,
			LPC, LPN,
			LVC, LVN;
 unsigned int		LNumOfPolygons=0;


 for(LGC=0;LGC<LGN;LGC++)
 {
  LPolyGroup=LMesh->PolyGroups[LGC];

  LPolygon=LPolyGroup->Polygons;
  LPN=LPolyGroup->NumOfPolygons;
  for(LPC=0;LPC<LPN;LPC++, LPolygon++)
  {
   LVertexNode=LPolygon->VertexNodes;
   LVN=LPolygon->NumOfVertexNodes;
   for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
   {
    if(LVertexNode->VertexID==LVertexIndex)
    {
     ELst_AddPointer((void***)(&LPolygons), &LNumOfPolygons, LPolygon);

// If LVertexNodesPtr is not NULL, also return the VertexNodes involved...
//
     if(LVertexNodesPtr)
     {
      LNumOfPolygons--;
      ELst_AddPointer((void***)(&LVertexNodes), &LNumOfPolygons, LVertexNode);
     }
     break;
    }
   }
  }
 }

 *LPolygonsPtr=LPolygons;
 *LVertexNodesPtr=LVertexNodes;
 return(LNumOfPolygons); 
}


//================================================================================================
// Get Polygons that share an Edge
//================================================================================================
unsigned int E3d_MeshEdgeGetPolygons(E3dMesh* LMesh, E3dEdge* LEdge, E3dPolygon*** LPolygonsPtr)
{
 E3dPolyGroup*	LPolyGroup;
 E3dPolygon*	LPolygon;
 E3dPolygon**	LPolygons=NULL;
 E3dVertexNode*	LVertexNode;
 E3dVertexNode*	LOtherVertexNode;
 unsigned int	LGC, LGN=LMesh->NumOfPolyGroups,
		LPC, LPN,
		LVC, LVN;
 unsigned int	LNumOfPolygons=0;
 int		LEdgeStart=LEdge->Start, LEdgeEnd=LEdge->End;


 for(LGC=0;LGC<LGN;LGC++)
 {
  LPolyGroup=LMesh->PolyGroups[LGC];

  LPolygon=LPolyGroup->Polygons;
  LPN=LPolyGroup->NumOfPolygons;
  for(LPC=0;LPC<LPN;LPC++, LPolygon++)
  {
   LVertexNode=LPolygon->VertexNodes;
   LVN=LPolygon->NumOfVertexNodes;
   for(LVC=0;LVC<LVN;LVC++, LVertexNode++)
   {
    if(LVertexNode->VertexID==LEdgeStart)
    {
     LOtherVertexNode=E3dM_NextVertexNode(LPolygon->VertexNodes, LVertexNode, LVC, LVN);
     if(LOtherVertexNode->VertexID==LEdgeEnd)
     {
      ELst_AddPointer((void***)(&LPolygons), &LNumOfPolygons, LPolygon);
      break;
     }
     else
     {
      LOtherVertexNode=E3dM_PrevVertexNode(LPolygon->VertexNodes, LVertexNode, LVC, LVN);
      if(LOtherVertexNode->VertexID==LEdgeEnd)
      {
       ELst_AddPointer((void***)(&LPolygons), &LNumOfPolygons, LPolygon);
       break;
      }
     }
    }
   }
  }
 }

 *LPolygonsPtr=LPolygons;
 return(LNumOfPolygons); 
}



#ifdef USEOpenGL
//================================================
// Refresh GL data in the Edge list of a Mesh
//================================================
void E3d_MeshRefreshGLEdges(E3dMesh* LMesh, E3dMatrix LMatrix)
{
 E3dEdge*		LEdge;
 E3dVertex*		LVertices;
 E3dPolyGroup**		LPolyGroups;
 E3dPolyGroup*		LPolyGroup;
 E3dGLCoordinate	mX, mY, mZ;
 unsigned int		LEdgeN, LC, LGCnt, LGNum, LIndex;


 LVertices=LMesh->Vertices;

 if(E3d_GLDataTransformed)
 {
  for(LC=0, LEdge=LMesh->Edges, LEdgeN=LMesh->NumOfEdges;LC<LEdgeN;LC++, LEdge++)
  {
   LIndex=LEdge->Start;
   mX=LVertices[LIndex].X;mY=LVertices[LIndex].Y;mZ=LVertices[LIndex].Z;
   E3dM_MatrixTransform3x4(LMatrix, LEdge->GLStart[E3dX], LEdge->GLStart[E3dY], LEdge->GLStart[E3dZ]);
   LIndex=LEdge->End;
   mX=LVertices[LIndex].X;mY=LVertices[LIndex].Y;mZ=LVertices[LIndex].Z;
   E3dM_MatrixTransform3x4(LMatrix, LEdge->GLEnd[E3dX], LEdge->GLEnd[E3dY], LEdge->GLEnd[E3dZ]);
  }
 }
 else
 {
  for(LC=0, LEdge=LMesh->Edges, LEdgeN=LMesh->NumOfEdges;LC<LEdgeN;LC++, LEdge++)
  {
   LIndex=LEdge->Start;
   LEdge->GLStart[E3dX]=LVertices[LIndex].X;
   LEdge->GLStart[E3dY]=LVertices[LIndex].Y;
   LEdge->GLStart[E3dZ]=LVertices[LIndex].Z;
   LIndex=LEdge->End;
   LEdge->GLEnd[E3dX]=LVertices[LIndex].X;
   LEdge->GLEnd[E3dY]=LVertices[LIndex].Y;
   LEdge->GLEnd[E3dZ]=LVertices[LIndex].Z;
  }


// Do the edges of the PolyGroups
//
  LGNum=LMesh->NumOfPolyGroups;
  LPolyGroups=LMesh->PolyGroups;
  for(LGCnt=0;LGCnt<LGNum;LGCnt++)
  {
   LPolyGroup=LPolyGroups[LGCnt];
   for(LC=0, LEdge=LPolyGroup->Edges, LEdgeN=LPolyGroup->NumOfEdges;LC<LEdgeN;LC++, LEdge++)
   {
    LIndex=LEdge->Start;
    LEdge->GLStart[E3dX]=LVertices[LIndex].X;
    LEdge->GLStart[E3dY]=LVertices[LIndex].Y;
    LEdge->GLStart[E3dZ]=LVertices[LIndex].Z;
    LIndex=LEdge->End;
    LEdge->GLEnd[E3dX]=LVertices[LIndex].X;
    LEdge->GLEnd[E3dY]=LVertices[LIndex].Y;
    LEdge->GLEnd[E3dZ]=LVertices[LIndex].Z;
   }
  }

 }
}



//========================================================================================
// Refresh GL vertices and GL Polygon and Vertex normals of the Polygons of a Mesh
//========================================================================================
void E3d_MeshPolyGroupRefreshGLPolyVertices(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, E3dMatrix LLocalToWorldMatrix, EBool LDoNormals)
{
 E3dVertex*		LVertices;
 E3dVertex*		LVertex;
 E3dVertexNode*		LVertexNode;
 E3dPolygon*		LPolygon;
 E3dTriangleStrip*	LTriangleStrip;
 double			LVNI;
 E3dCoordinate		LCX, LCY, LCZ, LX, LY, LZ;
 unsigned int		LVNum, LVCnt, LPCnt, LPNum;
 int			LVertexID;


 LVertices=LMesh->Vertices;

 if(E3d_GLDataTransformed)
 {
  E3dCoordinate	mX, mY, mZ;


// Do Polygons
//
  if((LPNum=LPolyGroup->NumOfPolygons)>0)
  {
   LPolygon=LPolyGroup->Polygons;

   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
   {
// Do VertexNode loop for only the exterior vertices first for the Polygon center
//
    LVNum=LPolygon->NumOfExteriorVertices;
    LVertexNode=LPolygon->VertexNodes;
    LCX=LCY=LCZ=0.0;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      
      mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
      E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LX, LY, LZ);
      LVertexNode->GLVertex[E3dX]=LX;
      LVertexNode->GLVertex[E3dY]=LY;
      LVertexNode->GLVertex[E3dZ]=LZ;

      LCX+=LX;LCY+=LY;LCZ+=LZ;
     }
    }
    LVNI=1.0/(E3dCoordinate)LVNum;
    LPolygon->GLCenter[E3dX]=LCX*LVNI;
    LPolygon->GLCenter[E3dY]=LCY*LVNI;
    LPolygon->GLCenter[E3dZ]=LCZ*LVNI;

// Do VertexNode loop for the remaining VertexNodes
//
    LVNum=LPolygon->NumOfVertexNodes;
    for(;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
      E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LVertexNode->GLVertex[E3dX], LVertexNode->GLVertex[E3dY], LVertexNode->GLVertex[E3dZ]);
     }
    }
   }

   if(LDoNormals)
   {
    LPNum=LPolyGroup->NumOfPolygons;LPolygon=LPolyGroup->Polygons;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
    {
// Do polygon normal
//
     mX=LPolygon->Normal.X;mY=LPolygon->Normal.Y;mZ=LPolygon->Normal.Z;
     E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

     LPolygon->GLNormal[E3dX]=LX;LPolygon->GLNormal[E3dY]=LY;LPolygon->GLNormal[E3dZ]=LZ;

// Do Vertex normals
//
     LVNum=LPolygon->NumOfVertexNodes;
     LVertexNode=LPolygon->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if(LVertexNode->VertexID>-1)
      {
       mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
       E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

       LVertexNode->GLNormal[E3dX]=LX;LVertexNode->GLNormal[E3dY]=LY;LVertexNode->GLNormal[E3dZ]=LZ;
      }
     }
    }
   }
  }

// Do TriangleStrips
//
  if((LPNum=LPolyGroup->NumOfTriangleStrips)>0)
  {
   LPNum=LPolyGroup->NumOfTriangleStrips;
   LTriangleStrip=LPolyGroup->TriangleStrips;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
   {
// VertexNode loop
//
    LVNum=LTriangleStrip->NumOfVertices;
    LVertexNode=LTriangleStrip->VertexNodes;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      
      mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
      E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LX, LY, LZ);
      LVertexNode->GLVertex[E3dX]=LX;
      LVertexNode->GLVertex[E3dY]=LY;
      LVertexNode->GLVertex[E3dZ]=LZ;
     }
    }
   }

// Do Vertex normals of TriangleStrips
//
   LTriangleStrip=LPolyGroup->TriangleStrips;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
   {
    LVNum=LTriangleStrip->NumOfVertices;
    LVertexNode=LTriangleStrip->VertexNodes;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if(LVertexNode->VertexID>-1)
     {
      mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
      E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

      LVertexNode->GLNormal[E3dX]=LX;LVertexNode->GLNormal[E3dY]=LY;LVertexNode->GLNormal[E3dZ]=LZ;
     }
    }
   }
  }
 }
 else
 {
  if(E3dNV_AGPMemAllocateProc)
  {
   if(E3dp_Prefs.UseGLVertexArrays) E3dNV_AGPMemAllocateProc();
  }

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


   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
   {
// Do VertexNode loop for only the exterior Vertices first to compute the Polygon center
//
    LVNum=LPolygon->NumOfExteriorVertices;
    LVertexNode=LPolygon->VertexNodes;
    LCX=LCY=LCZ=0.0;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      
      LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
      LVertexNode->GLVertex[E3dX]=LX;
      LVertexNode->GLVertex[E3dY]=LY;
      LVertexNode->GLVertex[E3dZ]=LZ;

      LCX+=LX;LCY+=LY;LCZ+=LZ;
     }
    }
    LVNI=1.0/(E3dCoordinate)LVNum;
    LPolygon->GLCenter[E3dX]=LCX*LVNI;
    LPolygon->GLCenter[E3dY]=LCY*LVNI;
    LPolygon->GLCenter[E3dZ]=LCZ*LVNI;

// Do VertexNode loop for the remaining VertexNodes
//
    LVNum=LPolygon->NumOfVertexNodes;
    for(;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      LVertexNode->GLVertex[E3dX]=LVertex->X;
      LVertexNode->GLVertex[E3dY]=LVertex->Y;
      LVertexNode->GLVertex[E3dZ]=LVertex->Z;
     }
    }
   }

   if(LDoNormals)
   {
    LPNum=LPolyGroup->NumOfPolygons;LPolygon=LPolyGroup->Polygons;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
    {
     LPolygon->GLNormal[E3dX]=LPolygon->Normal.X;
     LPolygon->GLNormal[E3dY]=LPolygon->Normal.Y;
     LPolygon->GLNormal[E3dZ]=LPolygon->Normal.Z;

     LVNum=LPolygon->NumOfVertexNodes;
     LVertexNode=LPolygon->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if(LVertexNode->VertexID>-1)
      {
       LVertexNode->GLNormal[E3dX]=LVertexNode->Normal.X;
       LVertexNode->GLNormal[E3dY]=LVertexNode->Normal.Y;
       LVertexNode->GLNormal[E3dZ]=LVertexNode->Normal.Z;
      }
     }
    }
   }


// For fast rendering on NVidia cards
//
   {
    E3dMaterial*	LMaterial=LPolyGroup->DrawingMaterial;
    EBool		LDoAGP=TRUE;


    if(LMaterial->Shader)
    {
     E3dShaderClass*	LShaderClass=LMaterial->Shader->Class;

     if(LShaderClass->GLDrawProc) LDoAGP=FALSE;
    }


    if(LDoAGP && E3dp_Prefs.UseGLVertexArrays && E3dNV_AGPInitialized)
    {
     if(LMaterial->NumOf2DTextures) E3d_PolyGroupCreateShadedSTVertexArrayProc(LPolyGroup, LMesh->Vertices);
     else E3d_PolyGroupCreateShadedVertexArrayProc(LPolyGroup, LMesh->Vertices);
    }
   }

  }


// Do TriangleStrips
//
  if((LPNum=LPolyGroup->NumOfTriangleStrips)>0)
  {
   LTriangleStrip=LPolyGroup->TriangleStrips;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
   {
// VertexNode loop
//
    LVNum=LTriangleStrip->NumOfVertices;
    LVertexNode=LTriangleStrip->VertexNodes;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if((LVertexID=LVertexNode->VertexID)>=0)
     {
      LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
      
      LVertexNode->GLVertex[E3dX]=LVertex->X;
      LVertexNode->GLVertex[E3dY]=LVertex->Y;
      LVertexNode->GLVertex[E3dZ]=LVertex->Z;
     }
    }
   }

// Do vertex normals of TriangleStrips
//
   LTriangleStrip=LPolyGroup->TriangleStrips;
   for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
   {
    LVNum=LTriangleStrip->NumOfVertices;
    LVertexNode=LTriangleStrip->VertexNodes;
    for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     if(LVertexNode->VertexID>-1)
     {
      LVertexNode->GLNormal[E3dX]=LVertexNode->Normal.X;
      LVertexNode->GLNormal[E3dY]=LVertexNode->Normal.Y;
      LVertexNode->GLNormal[E3dZ]=LVertexNode->Normal.Z;
     }
    }
   }

  }
 }
}





//========================================================================================
// Refresh GL vertices and GL Polygon and Vertex normals of the Polygons of a Mesh
//========================================================================================
void E3d_MeshRefreshGLPolyVertices(E3dMesh* LMesh, E3dMatrix LLocalToWorldMatrix, EBool LDoNormals)
{
 E3dVertex*			LVertices;
 E3dVertex*			LVertex;
 E3dVertexNode*			LVertexNode;
 E3dPolyGroup*			LPolyGroup;
 E3dPolyGroup**			LPolyGroups;
 E3dPolygon*			LPolygon;
 E3dTriangleStrip*		LTriangleStrip;
 double				LVNI;
 E3dCoordinate			LCX, LCY, LCZ, LX, LY, LZ;
 unsigned int			LGCnt, LGNum, LVNum, LVCnt, LPCnt, LPNum;
 int				LVertexID;


 LVertices=LMesh->Vertices;

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

 if(E3d_GLDataTransformed)
 {
  E3dCoordinate	mX, mY, mZ;

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

// Do Polygons
//
   if((LPNum=LPolyGroup->NumOfPolygons)>0)
   {
    LPolygon=LPolyGroup->Polygons;

    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
    {
// Do VertexNode loop for only the exterior vertices first for the Polygon center
//
     LVNum=LPolygon->NumOfExteriorVertices;
     LVertexNode=LPolygon->VertexNodes;
     LCX=LCY=LCZ=0.0;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       
       mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
       E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LX, LY, LZ);
       LVertexNode->GLVertex[E3dX]=LX;
       LVertexNode->GLVertex[E3dY]=LY;
       LVertexNode->GLVertex[E3dZ]=LZ;

       LCX+=LX;LCY+=LY;LCZ+=LZ;
      }
     }
     LVNI=1.0/(E3dCoordinate)LVNum;
     LPolygon->GLCenter[E3dX]=LCX*LVNI;
     LPolygon->GLCenter[E3dY]=LCY*LVNI;
     LPolygon->GLCenter[E3dZ]=LCZ*LVNI;

// Do VertexNode loop for the remaining VertexNodes
//
     LVNum=LPolygon->NumOfVertexNodes;
     for(;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
       E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LVertexNode->GLVertex[E3dX], LVertexNode->GLVertex[E3dY], LVertexNode->GLVertex[E3dZ]);
      }
     }
    }

    if(LDoNormals)
    {
     LPNum=LPolyGroup->NumOfPolygons;LPolygon=LPolyGroup->Polygons;
     for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
     {
// Do Polygon normal
//
      mX=LPolygon->Normal.X;mY=LPolygon->Normal.Y;mZ=LPolygon->Normal.Z;
      E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

      LPolygon->GLNormal[E3dX]=LX;LPolygon->GLNormal[E3dY]=LY;LPolygon->GLNormal[E3dZ]=LZ;

// Do Vertex normals
//
      LVNum=LPolygon->NumOfVertexNodes;
      LVertexNode=LPolygon->VertexNodes;
      for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
      {
       if(LVertexNode->VertexID>-1)
       {
	mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
	E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

	LVertexNode->GLNormal[E3dX]=LX;LVertexNode->GLNormal[E3dY]=LY;LVertexNode->GLNormal[E3dZ]=LZ;
       }
      }
     }
    }
   }

// Do TriangleStrips
//
   if((LPNum=LPolyGroup->NumOfTriangleStrips)>0)
   {
    LPNum=LPolyGroup->NumOfTriangleStrips;
    LTriangleStrip=LPolyGroup->TriangleStrips;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
    {
// VertexNode loop
//
     LVNum=LTriangleStrip->NumOfVertices;
     LVertexNode=LTriangleStrip->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       
       mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
       E3dM_MatrixTransform3x4(LLocalToWorldMatrix, LX, LY, LZ);
       LVertexNode->GLVertex[E3dX]=LX;
       LVertexNode->GLVertex[E3dY]=LY;
       LVertexNode->GLVertex[E3dZ]=LZ;
      }
     }
    }

// Do vertex normals of TriangleStrips
//
    LTriangleStrip=LPolyGroup->TriangleStrips;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
    {
     LVNum=LTriangleStrip->NumOfVertices;
     LVertexNode=LTriangleStrip->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if(LVertexNode->VertexID>-1)
      {
       mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
       E3dM_MatrixTransform3x3(LLocalToWorldMatrix, LX, LY, LZ);

       LVertexNode->GLNormal[E3dX]=LX;LVertexNode->GLNormal[E3dY]=LY;LVertexNode->GLNormal[E3dZ]=LZ;
      }
     }
    }
   }

  }

 }
 else
 {
  if(E3dNV_AGPMemAllocateProc)
  {
   if(E3dp_Prefs.UseGLVertexArrays) E3dNV_AGPMemAllocateProc();
  }

  for(LGCnt=0;LGCnt<LGNum;LGCnt++)
  {
   LPolyGroup=LPolyGroups[LGCnt];
   if((LPNum=LPolyGroup->NumOfPolygons)>0)
   {
    LPolygon=LPolyGroup->Polygons;


    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
    {
// Do VertexNode loop for only the exterior Vertices first to compute the Polygon center
//
     LVNum=LPolygon->NumOfExteriorVertices;
     LVertexNode=LPolygon->VertexNodes;
     LCX=LCY=LCZ=0.0;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       
       LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
       LVertexNode->GLVertex[E3dX]=LX;
       LVertexNode->GLVertex[E3dY]=LY;
       LVertexNode->GLVertex[E3dZ]=LZ;

       LCX+=LX;LCY+=LY;LCZ+=LZ;
      }
     }
     LVNI=1.0/(E3dCoordinate)LVNum;
     LPolygon->GLCenter[E3dX]=LCX*LVNI;
     LPolygon->GLCenter[E3dY]=LCY*LVNI;
     LPolygon->GLCenter[E3dZ]=LCZ*LVNI;

// Do VertexNode loop for the remaining VertexNodes
//
     LVNum=LPolygon->NumOfVertexNodes;
     for(;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       LVertexNode->GLVertex[E3dX]=LVertex->X;
       LVertexNode->GLVertex[E3dY]=LVertex->Y;
       LVertexNode->GLVertex[E3dZ]=LVertex->Z;
      }
     }
    }

    if(LDoNormals)
    {
     LPNum=LPolyGroup->NumOfPolygons;LPolygon=LPolyGroup->Polygons;
     for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
     {
      LPolygon->GLNormal[E3dX]=LPolygon->Normal.X;
      LPolygon->GLNormal[E3dY]=LPolygon->Normal.Y;
      LPolygon->GLNormal[E3dZ]=LPolygon->Normal.Z;

      LVNum=LPolygon->NumOfVertexNodes;
      LVertexNode=LPolygon->VertexNodes;
      for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
      {
       if(LVertexNode->VertexID>-1)
       {
	LVertexNode->GLNormal[E3dX]=LVertexNode->Normal.X;
	LVertexNode->GLNormal[E3dY]=LVertexNode->Normal.Y;
	LVertexNode->GLNormal[E3dZ]=LVertexNode->Normal.Z;
       }
      }
     }
    }


// For fast rendering on NVidia cards
//
    {
     E3dMaterial*	LMaterial=LPolyGroup->DrawingMaterial;
     EBool		LDoAGP=TRUE;


     if(LMaterial->Shader)
     {
      E3dShaderClass*	LShaderClass=LMaterial->Shader->Class;

//printf("MeshAGP %p  %p()\n", LShaderClass, LShaderClass->GLDrawProc);fflush(stdout);

      if(LShaderClass->GLDrawProc) LDoAGP=FALSE;
     }


     if(LDoAGP && E3dp_Prefs.UseGLVertexArrays && E3dNV_AGPInitialized)
     {
      if(LMaterial->NumOf2DTextures) E3d_PolyGroupCreateShadedSTVertexArrayProc(LPolyGroup, LMesh->Vertices);
      else E3d_PolyGroupCreateShadedVertexArrayProc(LPolyGroup, LMesh->Vertices);
     }
    }
   }


// Do TriangleStrips
//
   if((LPNum=LPolyGroup->NumOfTriangleStrips)>0)
   {
    LTriangleStrip=LPolyGroup->TriangleStrips;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
    {
// VertexNode loop
//
     LVNum=LTriangleStrip->NumOfVertices;
     LVertexNode=LTriangleStrip->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if((LVertexID=LVertexNode->VertexID)>=0)
      {
       LVertexID=LVertexNode->VertexID;LVertex=LVertices+LVertexID;
       
       LVertexNode->GLVertex[E3dX]=LVertex->X;
       LVertexNode->GLVertex[E3dY]=LVertex->Y;
       LVertexNode->GLVertex[E3dZ]=LVertex->Z;
      }
     }
    }

// Do vertex normals of TriangleStrips
//
    LTriangleStrip=LPolyGroup->TriangleStrips;
    for(LPCnt=0;LPCnt<LPNum;LPCnt++, LTriangleStrip++)
    {
     LVNum=LTriangleStrip->NumOfVertices;
     LVertexNode=LTriangleStrip->VertexNodes;
     for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
     {
      if(LVertexNode->VertexID>-1)
      {
       LVertexNode->GLNormal[E3dX]=LVertexNode->Normal.X;
       LVertexNode->GLNormal[E3dY]=LVertexNode->Normal.Y;
       LVertexNode->GLNormal[E3dZ]=LVertexNode->Normal.Z;
      }
     }
    }

   }

  }
 }
}


//================================================================
// Refresh GL polygon and vertex normals of the Polygons a Mesh
//================================================================
void E3d_MeshRefreshGLNormals(E3dMesh* LMesh, E3dMatrix LMatrix)
{
 E3dVertexNode*		LVertexNode;
 E3dPolyGroup*		LPolyGroup;
 E3dPolyGroup**		LPolyGroups;
 E3dPolygon*		LPolygon;
 E3dCoordinate		mX, mY, mZ, LX, LY, LZ, LL;
 unsigned int		LGCnt, LGNum, LVNum, LVCnt, LPCnt, LPNum;


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

 for(LGCnt=0;LGCnt<LGNum;LGCnt++)
 {
  LPolyGroup=LPolyGroups[LGCnt];
  LPNum=LPolyGroup->NumOfPolygons;LPolygon=LPolyGroup->Polygons;
  for(LPCnt=0;LPCnt<LPNum;LPCnt++, LPolygon++)
  {
   mX=LPolygon->Normal.X;mY=LPolygon->Normal.Y;mZ=LPolygon->Normal.Z;
   E3dM_MatrixTransform3x3(LMatrix, LX, LY, LZ);
// Re-normalize normal
//
   LL=sqrt(LX*LX+LY*LY+LZ*LZ);
   if(LL!=1.0) { if(LL>0.0) { LL=1.0/LL;LX*=LL;LY*=LL;LZ*=LL; } }
   LPolygon->GLNormal[E3dX]=LX;LPolygon->GLNormal[E3dY]=LY;LPolygon->GLNormal[E3dZ]=LZ;

   LVNum=LPolygon->NumOfVertexNodes;
   LVertexNode=LPolygon->VertexNodes;
   for(LVCnt=0;LVCnt<LVNum;LVCnt++, LVertexNode++)
   {
    if(LVertexNode->VertexID>-1)
    {
     mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
     E3dM_MatrixTransform3x3(LMatrix, LX, LY, LZ);
// Re-normalize normal
//
     LL=sqrt(LX*LX+LY*LY+LZ*LZ);
     if(LL!=1.0) { if(LL>0.0) { LL=1.0/LL;LX*=LL;LY*=LL;LZ*=LL; } }

     LVertexNode->GLNormal[E3dX]=LX;LVertexNode->GLNormal[E3dY]=LY;LVertexNode->GLNormal[E3dZ]=LZ;
    }
   }
  }
 }
}
#endif // USEOpenGL



//================================================================================
// Allocate memory for and initialize a SkinMesh
//
// Description
//  Allocates a new mesh and initializes it.
//
// Return value
//  A pointer to the new SkinMesh or NULL if unsuccessful.
//
// See also
//  Geometry/E3d_GeometryFree
//================================================================================
E3dSkinMesh* E3d_SkinMeshAllocate()
{
 E3dSkinMesh*	LSkinMesh=(E3dSkinMesh*)EMalloc(sizeof(E3dSkinMesh));

 if(LSkinMesh)
 {
  E3d_MeshDefault((E3dMesh*)LSkinMesh);

  LSkinMesh->GeoType=E3dGEO_SKINMESH;

  LSkinMesh->SkinVertices=NULL;
  LSkinMesh->Skeleton=NULL;
  LSkinMesh->UpdateCnt=0;
 }
 return(LSkinMesh);
}


//================================================================================
// Free a SkinMesh
//
// Arguments
//  E3dSkinMesh* LSkinMesh             Pointer to the E3dSkinMesh to be freed
//
// Description
//  Frees the specified SkinMesh.
//
// See also
//  E3d_SkinMeshAllocate
//================================================================================
void E3d_SkinMeshFree(E3dSkinMesh* LSkinMesh)
{
 if(LSkinMesh->SkinVertices) EFree(LSkinMesh->SkinVertices);
 if(LSkinMesh->Skeleton) E3d_ModelHrcFree(LSkinMesh->Skeleton, FALSE);

 E3d_MeshFree((E3dMesh*)LSkinMesh);
}


//================================================================================
// Update the shape of a SkinMesh
//
// Arguments
//  E3dSkinMesh* LSkinMesh             Pointer to the E3dSkinMesh
//
// Description
//  Performs Vertex weight-based skinning on the given SkinMesh.
//
// See also
//  E3d_SkinMeshAllocate
//================================================================================
void E3d_SkinMeshUpdateShape(E3dSkinMesh* LSkinMesh, EBool LDoNormals)
{
 if(LSkinMesh->SkinVertices)
 {
  E3dSkinVertex*	LSkinVertex=LSkinMesh->SkinVertices;
  E3dVertex*		LVertex=LSkinMesh->Vertices;
  E3dCoordinate		mX, mY, mZ, LX, LY, LZ, LTX, LTY, LTZ, LF;
  E3dWeight		LWeight;
  unsigned int		LC, LN=LSkinMesh->NumOfVertices,
			LJC, LJN;


  for(LC=0;LC<LN;LC++, LVertex++, LSkinVertex++)
  {
   if(LSkinVertex->NumOfBones)
   {
    LJN=LSkinVertex->NumOfBones;

    mX=LSkinVertex->X;mY=LSkinVertex->Y;mZ=LSkinVertex->Z;
    LWeight=LSkinVertex->Weights[0];
    E3dM_MatrixTransform3x4(((E3dJointModel*)(LSkinVertex->Bones[0]))->SkinningMatrix, LX, LY, LZ);
    LX*=LWeight;LY*=LWeight;LZ*=LWeight;
    LF=LWeight;

    for(LJC=1;LJC<LJN;LJC++)
    {
     LWeight=LSkinVertex->Weights[LJC];
     LF+=LWeight;
     E3dM_MatrixTransform3x4(((E3dJointModel*)(LSkinVertex->Bones[LJC]))->SkinningMatrix, LTX, LTY, LTZ);
     LX+=LTX*LWeight;LY+=LTY*LWeight;LZ+=LTZ*LWeight;
    }

    LF=1.0/LF;

    LVertex->X=LX*LF;LVertex->Y=LY*LF;LVertex->Z=LZ*LF;
   }
  }

  {
   E3dMatrix	LMatrix;

   if(LDoNormals) E3d_MeshRefreshNormals((E3dMesh*)LSkinMesh, TRUE);
   E3d_MeshRefreshGLPolyVertices((E3dMesh*)LSkinMesh, LMatrix, LDoNormals);
  }
 }
}


//================================================================================
// Make the current shape of a SkinMesh its default shape
//
// Arguments
//  E3dSkinMesh* LSkinMesh             Pointer to the E3dSkinMesh
//
// Description
//  Copies the Vertex positions into the SkinVertices and makes the current pose
//  of the Skeleton hierarchy its default pose.
//
// See also
//  E3d_SkinMeshAllocate, E3d_SkinMeshUpdateShape, E3d_ModelHrcSkeletonSetDefaultTransform
//================================================================================
void E3d_SkinMeshBindPose(E3dSkinMesh* LSkinMesh)
{
 E3dModel*	LModel=NULL;
 E3dSkinVertex*	LSkinVertex=LSkinMesh->SkinVertices;
 E3dVertex*	LVertex=LSkinMesh->Vertices;
 unsigned int	LC, LN=LSkinMesh->NumOfVertices;


 for(LC=0;LC<LN;LC++, LVertex++, LSkinVertex++)
 {
  LSkinVertex->X=LVertex->X;
  LSkinVertex->Y=LVertex->Y;
  LSkinVertex->Z=LVertex->Z;

  if(LModel==NULL)
  {
   if(LSkinVertex->Bones) LModel=LSkinVertex->Bones[0];
  }
 }

 if(LModel)
 {
  while(LModel->Parent) LModel=LModel->Parent;
  E3d_ModelHrcSkeletonSetDefaultTransform(LModel);
 }
}


//================================================================================================
// Remove selected Polygons of a PolyonGroup or the PolyGroup
//
// Arguments
//  E3dPolyGroup*  LPolyGroup     Pointer to the E3dPolyGroup structure
//
// Description
//  This function checks if there are any Polygons selected on the given PolyGroup. If there
//  are, it removes the selected Polygons from the PolyGroup by first copying the unselected
//  Polygons into a new array and replacing the old array with this array.
//  If there are no selected Polygons, it removes the PolyGroup from the Mesh.
//  A Polygon is considered selected if its 'Flags' field has the 'E3dPolyFlagSELECTED' flag set.
//
// Return value
//  The number of Polygons removed.
//
// See also
//  E3d_PolyGroupDeleteSelectedPolygons
//================================================================================================
unsigned int E3d_MeshPolyGroupDeleteSelection(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dPolygon*	LPolygons;
 E3dPolygon*	LPolygon;
 E3dPolygon*	LDPolygon;
 unsigned int	LC, LN, LNUnselectedPolys, LNSelectedPolys;


 LN=LPolyGroup->NumOfPolygons;
 LNUnselectedPolys=LNSelectedPolys=0;

// Count unselected Polygons
//
 LPolygon=LPolyGroup->Polygons;
 for(LC=0;LC<LN;LC++, LPolygon++)
 {
  if((LPolygon->Flags&E3dPolyFlagSELECTED)==0) LNUnselectedPolys++;
  else LNSelectedPolys++;
 }

 if(LNSelectedPolys>0)
 {
  if(LNUnselectedPolys>0)
  {
// Remove the selected Polygons from the original PolyGroup by copying the
// unselected ones into a new array and replacing the old array with this array.
//
   if((LPolygons=E3d_PolygonsAllocate(LNUnselectedPolys))!=NULL)
   {
    LPolygon=LPolyGroup->Polygons;
    LDPolygon=LPolygons;
    LN=LPolyGroup->NumOfPolygons;
    for(LC=0;LC<LN;LC++, LPolygon++)
    {
     if((LPolygon->Flags&E3dPolyFlagSELECTED)==0)
     {
       memcpy(LDPolygon, LPolygon, sizeof(E3dPolygon));
       LDPolygon++;
     }
    }

// FIXME: free the Polygons too (VertexNodes) or store them for Undo!
//
    EFree(LPolyGroup->Polygons);
    LPolyGroup->Polygons=LPolygons;
    LPolyGroup->NumOfPolygons=LNUnselectedPolys;
   }
  }
  else
  {
// FIXME: Store for Undo!
//
   E3d_PolyGroupFreePolygons(LPolyGroup);
  }
 }
 else
 {
// FIXME: Store for Undo!
//
  E3d_MeshRemoveAndFreePolyGroup(LMesh, LPolyGroup);
 }
 return(LNSelectedPolys);
}


//========================================================
// Recompute Polygon normals of a PolyGroup
//========================================================
void E3d_MeshPolyGroupRefreshPolygonNormals(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup)
{
 E3dPolygon*	LPolygon;
 E3dVertexNode*	LVertexNode;
 E3dCoordinate	LX0, LY0, LZ0, LX1, LY1, LZ1, LX2, LY2, LZ2, LNormalX, LNormalY, LNormalZ;
 E3dCoordinate	r;
 E3dVertex*	LVertices=LMesh->Vertices;
 E3dVertex*	LVertex;
 unsigned int	LVCnt, LPCnt, LNumOfPolys, LVertNum;


 LNumOfPolys=LPolyGroup->NumOfPolygons;

 LPolygon=LPolyGroup->Polygons;
 for(LPCnt=0;LPCnt<LNumOfPolys;LPCnt++, LPolygon++)
 {
  if((LVertNum=LPolygon->NumOfExteriorVertices)>2)
  {
   LVertexNode=LPolygon->VertexNodes;
   if(LVertNum==3)
   {
    LVertex=LVertices+LVertexNode[0].VertexID;
    LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;
    LVertex=LVertices+LVertexNode[1].VertexID;
    LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;
    LVertex=LVertices+LVertexNode[2].VertexID;
    LX2=LVertex->X;LY2=LVertex->Y;LZ2=LVertex->Z;

    LNormalX=-((LY2-LY1)*(LZ2-LZ0)-(LZ2-LZ1)*(LY2-LY0));
    LNormalY=(LX2-LX1)*(LZ2-LZ0)-(LZ2-LZ1)*(LX2-LX0);
    LNormalZ=-((LX2-LX1)*(LY2-LY0)-(LY2-LY1)*(LX2-LX0));
   }
   else
//----------------------------------------------------------------------------------------------------------------
// If the Polygon has more than 3 vertices, it can be concave, so to make sure we don't pick 3 vertices for
// the normal calculation which determine a reverse polygon, we have to run trough all the triangles of 3
// connected vertices and sum the normals of them, because even if a polygon is "strongly" concave, there are
// always more such triangles with the right orientation than ones with the wrong.
//----------------------------------------------------------------------------------------------------------------
   {
    LNormalX=0.0, LNormalY=0.0, LNormalZ=0.0;

    LVertNum--;
    for(LVCnt=0;LVCnt<LVertNum;LVCnt++)
    {
     LVertex=LVertices+LVertexNode[LVCnt].VertexID;
     LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;

     LVertex=LVertices+LVertexNode[LVCnt+1].VertexID;
     LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;

     LNormalX-=(LZ0+LZ1)*(LY1-LY0);
     LNormalY+=(LZ0+LZ1)*(LX1-LX0);
     LNormalZ-=(LY0+LY1)*(LX1-LX0);
    }

// The last Vertex pair is the last one and the 0th one
//
    LVertex=LVertices+LVertexNode[LVCnt].VertexID;
    LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;
    LVertex=LVertices+LVertexNode[0].VertexID;
    LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;

    LNormalX-=(LZ0+LZ1)*(LY1-LY0);
    LNormalY+=(LZ0+LZ1)*(LX1-LX0);
    LNormalZ-=(LY0+LY1)*(LX1-LX0);
   }

   r=sqrt(LNormalX*LNormalX+LNormalY*LNormalY+LNormalZ*LNormalZ);
   if(r>0.0) { r=1.0/r;LNormalX*=r;LNormalY*=r;LNormalZ*=r; }
   LPolygon->Normal.X=LNormalX;LPolygon->Normal.Y=LNormalY;LPolygon->Normal.Z=LNormalZ;
   LPolygon->Normal.Length=1.0;
  }
  else
  {
   LPolygon->Normal.X=0.0;LPolygon->Normal.Y=0.0;LPolygon->Normal.Z=0.0;
  }
 }
 LPolyGroup->Flags|=E3dPOLY_NORMALS_UPTODATE;
}

// A temporary structure for fast normal averaging
// to calculate discontinuity for a Mesh
//
typedef struct
{
 E3dCoordinate	X, Y, Z;
 int		NormalNum;
} E3dVertexNormalSum;




//========================================================
// Collect information on what Polygon VertexNodes
// use each Vertex of the given Mesh.
//========================================================
E3dVertexUsageList* E3d_MeshGetVertexUsageList(E3dMesh* LMesh)
{
 unsigned int	LN=LMesh->NumOfVertices;


 if(LMesh->VertexUsageList) { E3d_MeshFreeVertexUsageList(LMesh);LMesh->VertexUsageList=NULL; }

 if(LN)
 {
  E3dVertexUsageList*	LVertexUsageLists=(E3dVertexUsageList*)EMalloc(sizeof(E3dVertexUsageList)*LN);

  if(LVertexUsageLists)
  {
   E3dVertexUsageList*	LVertexUsageList;
   E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
   E3dPolyGroup*	LPolyGroup;
   E3dPolygon*		LPolygon;
   E3dVertexNode*	LVertexNode;
   unsigned int		LGC, LGN=LMesh->NumOfPolyGroups,
			LPC, LVC, LVN=LMesh->NumOfVertices,
			LOldListSize;


   LMesh->VertexUsageList=LVertexUsageLists;

// Clear VertexUsageLists
//
   LVertexUsageList=LVertexUsageLists;
   for(LVC=0;LVC<LVN;LVC++, LVertexUsageList++)
   {
    LVertexUsageList->VertexNodes=NULL;
    LVertexUsageList->Polygons=NULL;
    LVertexUsageList->NumOfVertexNodes=0;
    LVertexUsageList->NumOfVertexNodesAllocated=0;
   }

//EMemVerbose=FALSE;
//printf("Mesh [%s]  PolyGroups %d\n", LMesh->Name, LGN);fflush(stdout);

/*
{
 static int	LCnt=0;

 if(LCnt>0) assert(0);
 LCnt++;
}
*/


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

    LPolygon=LPolyGroup->Polygons;
    if(LPolygon)
    {
     LPC=LPolyGroup->NumOfPolygons;
     do
     {

      LVertexNode=LPolygon->VertexNodes;
      LVC=LPolygon->NumOfVertices;

      do
      {
       if(LVertexNode->VertexID>-1)
       {
	LVertexUsageList=LVertexUsageLists+LVertexNode->VertexID;
	LOldListSize=LVertexUsageList->NumOfVertexNodesAllocated;

	if(ELst_AddPointerAChk((void***)(&(LVertexUsageList->VertexNodes)), &(LVertexUsageList->NumOfVertexNodes), &(LVertexUsageList->NumOfVertexNodesAllocated), 4, LVertexNode))
	{
	 if(LVertexUsageList->Polygons==NULL) LVertexUsageList->Polygons=(E3dPolygon**)EMalloc(sizeof(E3dPolygon*) * 4);
	 else
	 {
// Use the same alloc-increment for LVertexUsageList->Polygons
//
	  if(LVertexUsageList->NumOfVertexNodesAllocated>LOldListSize) LVertexUsageList->Polygons=(E3dPolygon**)ERealloc(LVertexUsageList->Polygons, sizeof(E3dPolygon*)*LVertexUsageList->NumOfVertexNodesAllocated);
//	  LVertexUsageList->Polygons=(E3dPolygon**)ERealloc(LVertexUsageList->Polygons, sizeof(E3dPolygon*)*LVertexUsageList->NumOfVertexNodes);
	 }
	 LVertexUsageList->Polygons[LVertexUsageList->NumOfVertexNodes-1]=LPolygon;
	}
       }

       LVertexNode++;
      } while(--LVC);
      LPolygon++;
     } while(--LPC);
    }
   }
//EMemVerbose=TRUE;
  }
  return(LVertexUsageLists);
 }
 else return(NULL);
}


//========================================================
// Free array of VertexNode lists
//========================================================
void E3d_MeshFreeVertexUsageList(E3dMesh* LMesh)
{
 if(LMesh->VertexUsageList)
 {
  E3dVertexUsageList*	LVertexUsageList=LMesh->VertexUsageList;
  unsigned int		LC, LN=LMesh->NumOfVertices;

  for(LC=0;LC<LN;LC++, LVertexUsageList++)
  {
   if(LVertexUsageList->VertexNodes) EFree(LVertexUsageList->VertexNodes);
   if(LVertexUsageList->Polygons) EFree(LVertexUsageList->Polygons);
  }
  EFree(LMesh->VertexUsageList);LMesh->VertexUsageList=NULL;
 }
}


//========================================================================================
// When using ray-tracing for integrating incoming light at a Vertex of a Mesh, rays
// originating at corner Vertices would graze the neighboring Polygons and a hit
// would not be recorded.
// To avoid the this problem, we offset ray origins along the averaged normal at
// the Vertices.
//========================================================================================
E3d3DPosition* E3d_MeshGetRayDisplacements(E3dMesh* LMesh, E3dCoordinate LDistance)
{
 unsigned int		LNumOfMeshVertices=LMesh->NumOfVertices;

 if(LNumOfMeshVertices)
 {
  E3d3DPosition*	LNormals=(E3d3DPosition*)EMalloc(sizeof(E3d3DPosition)*LNumOfMeshVertices);


// Average normals of all Polygons that use a given Vertex
//
  if(LNormals)
  {
   E3dPolyGroup*	LPolyGroup;
   E3dPolygon*		LPolygon;
   E3dVertexNode*	LVertexNode;
   E3dVertex*		LVertex;
   E3d3DPosition*	LNormal;
   E3dCoordinate	LLength;
   unsigned int		LGC, LGN=LMesh->NumOfPolyGroups,
			LPC, LPN, LVC, LVN;


   LNormal=LNormals;
   for(LVC=0;LVC<LNumOfMeshVertices;LVC++, LNormal++)
   {
    LNormal->X=0.0;
    LNormal->Y=0.0;
    LNormal->Z=0.0;
   }

// Sum Polygon normals into Vertex normals
//
   for(LGC=0;LGC<LGN;LGC++)
   {
    LPolyGroup=LMesh->PolyGroups[LGC];
    LPN=LPolyGroup->NumOfPolygons;
    for(LPC=0, LPolygon=LPolyGroup->Polygons;LPC<LPN;LPC++, LPolygon++)
    {
     LVertexNode=LPolygon->VertexNodes;
     for(LVC=0, LVN=LPolygon->NumOfVertexNodes;LVC<LVN;LVC++, LVertexNode++)
     {
      if(LVertexNode->VertexID>-1)
      {
       LNormal=LNormals+LVertexNode->VertexID;
       LNormal->X+=LPolygon->Normal.X;
       LNormal->Y+=LPolygon->Normal.Y;
       LNormal->Z+=LPolygon->Normal.Z;
      }
     }
    }
   }

   LVertex=LMesh->Vertices;
   LNormal=LNormals;
   for(LVC=0;LVC<LNumOfMeshVertices;LVC++, LVertex++, LNormal++)		// Normalize Mesh Vertex-normals
   {
    LLength=sqrt(LNormal->X*LNormal->X + LNormal->Y*LNormal->Y + LNormal->Z*LNormal->Z);
    if(LLength>0.0)
    {
     LLength=1.0/LLength*LDistance;
     LNormal->X = LVertex->X + LNormal->X * LLength;
     LNormal->Y = LVertex->Y + LNormal->Y * LLength;
     LNormal->Z = LVertex->Z + LNormal->Z * LLength;
    }
    else
    {
     LNormal->X = LVertex->X;
     LNormal->Y = LVertex->Y;
     LNormal->Z = LVertex->Z;
    }
   }
  }

  return(LNormals);
 }
 return(NULL);
}


//================================================
// Compute normals of a PolyGroup
//================================================
void E3d_MeshPolyGroupRefreshNormals(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, int LPolyVertexNormalType, EBool LRefreshPolygonNormals)
{
 E3dVertex*	LVertices=LMesh->Vertices;
 E3dPolygon*	LPolygon;
 E3dPolygon*	LActPolygon1;
 E3dVertexNode*	LVertexNode;
 E3dCoordinate	LX0, LY0, LZ0, LX1, LY1, LZ1, LX2, LY2, LZ2, LNormalX, LNormalY, LNormalZ;
 E3dCoordinate	LActNormalX, LActNormalY, LActNormalZ, LLength;
 E3dCoordinate	LDiscontinuityAngleCos;
 E3dCoordinate	LNormalSumX, LNormalSumY, LNormalSumZ;
 E3dCoordinate	r;
 unsigned int	LNumOfMeshVertices=LMesh->NumOfVertices;
 unsigned int	LVCnt, LVNum, LVCnt1, LVNum1, LPCnt, LNumOfPolys, LVertNum;
 int		LIndex;


 LNumOfPolys=LPolyGroup->NumOfPolygons;

 if((LRefreshPolygonNormals)||((LPolyGroup->Flags&E3dPOLY_NORMALS_UPTODATE)==0))
 {
  for(LPCnt=0, LPolygon=LPolyGroup->Polygons;LPCnt<LNumOfPolys;LPCnt++, LPolygon++)
  {
   if((LVertNum=LPolygon->NumOfExteriorVertices)>2)
   {
    E3dVertex*	LVertex;

    LVertexNode=LPolygon->VertexNodes;
    if(LVertNum==3)
    {
     LVertex=LVertices+LVertexNode[0].VertexID;
     LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;
     LVertex=LVertices+LVertexNode[1].VertexID;
     LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;
     LVertex=LVertices+LVertexNode[2].VertexID;
     LX2=LVertex->X;LY2=LVertex->Y;LZ2=LVertex->Z;

     LNormalX=-((LY2-LY1)*(LZ2-LZ0)-(LZ2-LZ1)*(LY2-LY0));
     LNormalY=(LX2-LX1)*(LZ2-LZ0)-(LZ2-LZ1)*(LX2-LX0);
     LNormalZ=-((LX2-LX1)*(LY2-LY0)-(LY2-LY1)*(LX2-LX0));
    }
    else
//----------------------------------------------------------------------------------------------------------------
// If the Polygon has more than 3 vertices, it can be concave, so to make sure we don't pick 3 vertices for
// the normal calculation that determine a reverse Polygon, we have to run trough all the triangles of 3
// connected Vertices and sum their normals, because even if a Polygon is "strongly" concave, there are
// always more such triangles with the right orientation than ones with the wrong one.
//----------------------------------------------------------------------------------------------------------------
    {
     LNormalX=0.0, LNormalY=0.0, LNormalZ=0.0;

     LVertNum--;
     for(LVCnt=0;LVCnt<LVertNum;LVCnt++)
     {
      LVertex=LVertices+LVertexNode[LVCnt].VertexID;
      LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;

      LVertex=LVertices+LVertexNode[LVCnt+1].VertexID;
      LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;

      LNormalX-=(LZ0+LZ1)*(LY1-LY0);
      LNormalY+=(LZ0+LZ1)*(LX1-LX0);
      LNormalZ-=(LY0+LY1)*(LX1-LX0);
     }

// The last Vertex pair is the last one and the 0th one
//
     LVertex=LVertices+LVertexNode[LVCnt].VertexID;
     LX0=LVertex->X;LY0=LVertex->Y;LZ0=LVertex->Z;
     LVertex=LVertices+LVertexNode[0].VertexID;
     LX1=LVertex->X;LY1=LVertex->Y;LZ1=LVertex->Z;

     LNormalX-=(LZ0+LZ1)*(LY1-LY0);
     LNormalY+=(LZ0+LZ1)*(LX1-LX0);
     LNormalZ-=(LY0+LY1)*(LX1-LX0);
    }

    r=sqrt(LNormalX*LNormalX+LNormalY*LNormalY+LNormalZ*LNormalZ);
    if(r>0.0) { r=1.0/r;LNormalX*=r;LNormalY*=r;LNormalZ*=r; }
    LPolygon->Normal.X=LNormalX;LPolygon->Normal.Y=LNormalY;LPolygon->Normal.Z=LNormalZ;
    LPolygon->Normal.Length=1.0;
   }
   else
   {
    LPolygon->Normal.X=0.0;LPolygon->Normal.Y=0.0;LPolygon->Normal.Z=0.0;
   }
  }
  LPolyGroup->Flags|=E3dPOLY_NORMALS_UPTODATE;
 }

// Refresh only the Polygon normals (don't change the Normals at the Vertices)
//
 if(LPolyVertexNormalType==E3dNormalNONE) return;


// Use discontiniuity angle
//
 if((LPolyVertexNormalType==E3dNormalFROM_DISCONTINUITY_ANGLE)&&(LPolyGroup->DiscontinuityAngle<180.0))
 {
  E3dVertexUsageList*	LVertexUsageLists=LMesh->VertexUsageList;
  EBool			LMeshHadVertexUsageList;

  if(LVertexUsageLists) LMeshHadVertexUsageList=TRUE;
  else { LVertexUsageLists=E3d_MeshGetVertexUsageList(LMesh);LMeshHadVertexUsageList=FALSE; }

  if(LVertexUsageLists)
  {
   E3dVertexUsageList*	LVertexUsageList;


   LDiscontinuityAngleCos=cos(LPolyGroup->DiscontinuityAngle*E3dDEGREES_TO_RADIANS);
   for(LPCnt=0, LPolygon=LPolyGroup->Polygons;LPCnt<LNumOfPolys;LPCnt++, LPolygon++)
   {
    LNormalX=LPolygon->Normal.X;
    LNormalY=LPolygon->Normal.Y;
    LNormalZ=LPolygon->Normal.Z;
    LVertexNode=LPolygon->VertexNodes;

    for(LVCnt=0, LVNum=LPolygon->NumOfVertexNodes;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     LNormalSumX=LNormalX;
     LNormalSumY=LNormalY;
     LNormalSumZ=LNormalZ;

     LIndex=LVertexNode->VertexID;
     if(LIndex>-1)
     {
      LVertexUsageList=LVertexUsageLists+LIndex;
      LVNum1=LVertexUsageList->NumOfVertexNodes;
      for(LVCnt1=0;LVCnt1<LVNum1;LVCnt1++)
      {
       LActPolygon1=LVertexUsageList->Polygons[LVCnt1];
       if(LActPolygon1!=LPolygon)
       {
	LActNormalX=LActPolygon1->Normal.X;
	LActNormalY=LActPolygon1->Normal.Y;
	LActNormalZ=LActPolygon1->Normal.Z;
	if((LNormalX*LActNormalX+LNormalY*LActNormalY+LNormalZ*LActNormalZ)>=LDiscontinuityAngleCos)
	{
	 LNormalSumX+=LActNormalX;
	 LNormalSumY+=LActNormalY;
	 LNormalSumZ+=LActNormalZ;
	}
       }
      }
     }

// Divide and re-normalize normal in one step
//
     r=sqrt(LNormalSumX*LNormalSumX+LNormalSumY*LNormalSumY+LNormalSumZ*LNormalSumZ);

     if(r>0.0) r=1.0/r;
     LVertexNode->Normal.X=LNormalSumX * r;
     LVertexNode->Normal.Y=LNormalSumY * r;
     LVertexNode->Normal.Z=LNormalSumZ * r;
    }
   }
   LPolyGroup->Flags|=E3dVERTEX_NORMALS_UPTODATE;

   if(!LMeshHadVertexUsageList) E3d_MeshFreeVertexUsageList(LMesh);
  }
  return;
 }


// Average normals of all Polygons using a given Vertex and make that the Vertex normal
//
 if((LPolyVertexNormalType==E3dNormalAVERAGED)||((LPolyVertexNormalType==E3dNormalFROM_DISCONTINUITY_ANGLE)&&(LPolyGroup->DiscontinuityAngle>=180.0)))
 {
  E3dVertexNormalSum*		LVertNormalSums=(E3dVertexNormalSum*)EMalloc(sizeof(E3dVertexNormalSum)*LNumOfMeshVertices);


  if(LVertNormalSums)
  {
   E3dVertexNormalSum*		LVertNormalSum;

   LVertNormalSum=LVertNormalSums;
   for(LVCnt=0;LVCnt<LNumOfMeshVertices;LVCnt++, LVertNormalSum++)
   {
    LVertNormalSum->X=0.0;
    LVertNormalSum->Y=0.0;
    LVertNormalSum->Z=0.0;
    LVertNormalSum->NormalNum=0;
   }

   for(LPCnt=0, LPolygon=LPolyGroup->Polygons;LPCnt<LNumOfPolys;LPCnt++, LPolygon++)
   {
    LVertexNode=LPolygon->VertexNodes;
    for(LVCnt=0, LVNum=LPolygon->NumOfExteriorVertices;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     LIndex=LVertexNode->VertexID;
     if(LIndex>-1)
     {
      LVertNormalSum=LVertNormalSums+LIndex;
      LVertNormalSum->X+=LPolygon->Normal.X;
      LVertNormalSum->Y+=LPolygon->Normal.Y;
      LVertNormalSum->Z+=LPolygon->Normal.Z;
      LVertNormalSum->NormalNum++;
     }
    }
   }

   LVertNormalSum=LVertNormalSums;
   for(LVCnt=0;LVCnt<LNumOfMeshVertices;LVCnt++, LVertNormalSum++)		// Normalize Mesh Vertex-normals
   {
    LLength=sqrt(LVertNormalSum->X*LVertNormalSum->X + LVertNormalSum->Y*LVertNormalSum->Y + LVertNormalSum->Z*LVertNormalSum->Z);
    if(LLength>0.0)
    {
     LLength=1.0/LLength;
     LVertNormalSum->X*=LLength;
     LVertNormalSum->Y*=LLength;
     LVertNormalSum->Z*=LLength;
    }
   }

   LPolygon=LPolyGroup->Polygons;
   for(LPCnt=0;LPCnt<LNumOfPolys;LPCnt++, LPolygon++)				// Set averaged Vertex-normals on Polygons of Mesh
   {
    LVertexNode=LPolygon->VertexNodes;
    for(LVCnt=0, LVNum=LPolygon->NumOfVertexNodes;LVCnt<LVNum;LVCnt++, LVertexNode++)
    {
     LIndex=LVertexNode->VertexID;
     if(LIndex>-1)
     {
      LVertNormalSum=LVertNormalSums+LIndex;
      LVertexNode->Normal.X=LVertNormalSum->X;
      LVertexNode->Normal.Y=LVertNormalSum->Y;
      LVertexNode->Normal.Z=LVertNormalSum->Z;
      LVertexNode->Normal.Length=1.0;
     }
    }
   }
   EFree(LVertNormalSums);
  }
  LPolyGroup->Flags|=E3dVERTEX_NORMALS_UPTODATE;
  return;
 }
}


//========================================================
// Remove cached OpenGL display lists from a Mesh
//========================================================
void E3d_MeshRemoveGLDisplayLists(E3dMesh* LMesh)
{
 if(LMesh->GLDisplayListShaded) { glDeleteLists(LMesh->GLDisplayListShaded, 1);LMesh->GLDisplayListShaded=0; }
 if(LMesh->GLDisplayListShaded2DTextured) { glDeleteLists(LMesh->GLDisplayListShaded2DTextured, 1);LMesh->GLDisplayListShaded2DTextured=0; }
}


//================================================================================
// Update a PolyGroup of a Mesh for drawing
//
// Arguments
//  E3dMesh*      LMesh      The Mesh to update
//  unsigned int  LFlags     OR-ed together flags, telling what to update
//  E3dPolyGroup* LPolyGroup The PolyGroup to update
//
// - For OpenGL: re-create OpenGL-optimized data sets
// - For custom displayer: notify display routine that the object(s) changed
//
// Description
//  This function updates the given PolyGroup of the given Mesh for drawing.
//  This is a platform-independent procedure that ensures that the given
//  Mesh will be drawn correctly after changes to its components.
//  Calling this function for a Geometry after change, with only the necessary
//  flags set, helps EQUINOX-3D greatly improve performance by only updating
//  things that really changed.
//  For example, if the position of a few Vertices changed in a Mesh, use the
//  E3dGF_SHAPE flag bit.
//================================================================================
void E3d_MeshPolyGroupUpdateForDisplay(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, unsigned int LFlags)
{
// FIXME: this will be for custom display routines
//
// E3d_DisplayCallGeometryChangedCallbacks(LGeometry);

 {
  E3dMatrix		LMatrix;

  E3d_MatrixLoadIdentity(LMatrix);

  if(LFlags&E3dGF_REMOVE_STRIPS)
  {
   E3d_PolyGroupFreeTriangleStrips(LPolyGroup);
  }
  if(LFlags&E3dGF_TOPOLOGY)
  {
   E3d_MeshCreateEdgesFromPolyData(LMesh, LPolyGroup);
   E3d_MeshCreateRenderablePolygons(LMesh);
   if(LFlags&E3dGF_CALL_DESTROYPROCS) E3d_GeometryInfoCallDestroyProcs((E3dGeometry*)LMesh);
  }

// Make sure that no PolyGroups have a NULL DrawMaterial
//
  E3d_UpdateDrawingMaterial((E3dGeometry*)LMesh, LPolyGroup->Material, &(LPolyGroup->DrawingMaterial));

  if(E3d_MeshGetBoundingBox(LMesh, &(LMesh->BBoxMin), &(LMesh->BBoxMax), E3dBB_ALL))
  {
   LMesh->BBoxUptodate=TRUE;
  }

  if(LFlags&E3dGF_REMAP_TEXTURES) E3d_MeshPolyGroupMap2DTexturesBB(LMesh, LPolyGroup, &(LMesh->BBoxMin), &(LMesh->BBoxMax));


#ifdef USEOpenGL
  E3d_MeshRemoveGLDisplayLists(LMesh);
  if(LFlags&(E3dGF_TOPOLOGY|E3dGF_VERTEX_POSITION|E3dGF_VERTEX_NORMAL|E3dGF_POLYGON_NORMAL|E3dGF_TEXTURE_ST)) E3d_MeshPolyGroupRefreshGLPolyVertices(LMesh, LPolyGroup, LMatrix, TRUE);
  if(LFlags&(E3dGF_TOPOLOGY|E3dGF_VERTEX_POSITION)) E3d_MeshRefreshGLEdges(LMesh, LMatrix);
#endif // USEOpenGL
 }

 {
  E3dGeometryCallbackStruct	LCBS;

  LCBS.Reasons=LFlags;
  E3d_CallOutputs((E3dGeometry*)LMesh, LMesh->Outputs, LMesh->NumOfOutputs, &LCBS);
 }

 {
  E3dSceneGeometryCallbackStruct	LCBS;
  E3dScene*				LScene=E3d_Scene;

  LCBS.Reason=E3dCR_GEOMETRY_CHANGED;
  LCBS.Geometry=(E3dGeometry*)LMesh;
  E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
 }
}


//========================================================
// Update/create 2DTextureMappers on a PolyGroup
//========================================================
static void _E3d_PolyGroupMap2DTextureMP(E3dMesh* LMesh, unsigned int LNumOfVertices, E3dPolyGroup* LPolyGroup, E3d2DTexture* L2DTexture, E3d2DTextureMapper* L2DTextureMapper,
	                                 E3d3DPosition* LCenterOffset)
{
 E3dVertex*		LVertices=LMesh->Vertices;
 E3dMatrix		LModelMatrix;
 E3dVertex*		LVertex;
 E3dCoordinate		LX, LY, LZ, LDX, LDY, LDZ, LTX, LTY,
			LXMin=0.0, LXMax=0.0, LYMin=0.0, LYMax=0.0, LZMin=0.0, LZMax=0.0;
 E3dCoordinate		LRotX=0.0, LRotY=0.0, LRotZ=0.0;
 E3dCoordinate		LSCropMin, LSCropMax, LTCropMin, LTCropMax, LPicWidth, LPicHeight;
 E3dCoordinate*		LTexels=NULL;
 E3dCoordinate*		LTTexels;
 E3dCoordinate		LSCountF, LTCountF;

#ifdef USEOpenGL
 E3dCoordinate*		LGLTexels;
 E3dCoordinate*		LTGLTexels;
#endif

 unsigned int		LVCnt;


 LSCropMin=L2DTexture->SCropMin;LSCropMax=L2DTexture->SCropMax;
 LTCropMin=L2DTexture->TCropMin;LTCropMax=L2DTexture->TCropMax;
 LPicWidth=(E3dCoordinate)(LSCropMax-LSCropMin+1);LPicHeight=(E3dCoordinate)(LTCropMax-LTCropMin+1);

// Determine what the type of the projector Geometry will be
//
 E3d_3DPositionInit(&(L2DTextureMapper->CenterOffset), LCenterOffset->X, LCenterOffset->Y, LCenterOffset->Z);
 switch(L2DTexture->MappingMethod)
 {
  case E3dTXT_XY:
  case E3dTXT_XZ:
  case E3dTXT_YZ:
  case E3dTXT_CYLINDRICAL:
   switch(L2DTexture->MappingMethod)
   {
    case E3dTXT_XY:
    case E3dTXT_XZ:
    case E3dTXT_YZ:
     LRotX=L2DTexture->Rotation.X;LRotY=L2DTexture->Rotation.Y;LRotZ=L2DTexture->Rotation.Z;
     E3d_MatrixLoadIdentity(LModelMatrix);
     if(L2DTexture->MappingMethod==E3dTXT_XZ) E3d_MatrixRotate(LModelMatrix, 'x', -90.0);
     if(L2DTexture->MappingMethod==E3dTXT_YZ) E3d_MatrixRotate(LModelMatrix, 'y', 90.0);
     E3d_MatrixRotate(LModelMatrix, 'z', LRotZ);
     E3d_MatrixRotate(LModelMatrix, 'y', LRotY);
     E3d_MatrixRotate(LModelMatrix, 'x', LRotX);

//--------------------------------------------------------------------------------------------------------------------------------
// Transform vertices of the Mesh by the 2DTexture's Matrix to calculate the 8 bounding vertices of the projector Geometry
//--------------------------------------------------------------------------------------------------------------------------------

// Must initialize BBox with first Vertex
//
     LVertex=LVertices;
     LX=LVertex->X-LCenterOffset->X;LY=LVertex->Y-LCenterOffset->Y;LZ=LVertex->Z-LCenterOffset->Z;
     LDX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20];
     LDY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21];
     LDZ=LX*LModelMatrix[M02]+LY*LModelMatrix[M12]+LZ*LModelMatrix[M22];
     LXMin=LXMax=LDX;
     LYMin=LYMax=LDY;
     LZMin=LZMax=LDZ;
     LVertex++;

     for(LVCnt=1;LVCnt<LNumOfVertices;LVCnt++, LVertex++)
     {
      LX=LVertex->X-LCenterOffset->X;LY=LVertex->Y-LCenterOffset->Y;LZ=LVertex->Z-LCenterOffset->Z;
      LDX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20];
      LDY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21];
      LDZ=LX*LModelMatrix[M02]+LY*LModelMatrix[M12]+LZ*LModelMatrix[M22];

      if(LDX<LXMin) LXMin=LDX;if(LDX>LXMax) LXMax=LDX;
      if(LDY<LYMin) LYMin=LDY;if(LDY>LYMax) LYMax=LDY;
      if(LDZ<LZMin) LZMin=LDZ;if(LDZ>LZMax) LZMax=LDZ;
     }
    break;

    case E3dTXT_CYLINDRICAL:
     LRotX=L2DTexture->Rotation.X;LRotY=L2DTexture->Rotation.Y;LRotZ=L2DTexture->Rotation.Z;
     E3d_MatrixLoadIdentity(LModelMatrix);
     E3d_MatrixRotate(LModelMatrix, 'x', LRotX);
     E3d_MatrixRotate(LModelMatrix, 'z', LRotZ);
    break;
   }

 }



 switch(L2DTexture->MappingMethod)
 {
  case E3dTXT_XY:
  case E3dTXT_XZ:
  case E3dTXT_YZ:
   {
    E3dMesh*		LProjMesh;
    unsigned int	LMNumOfVertices;


// Compute TextureMapper Matrix
//
    E3d_MatrixLoadIdentity(LModelMatrix);
    E3d_MatrixTranslate(LModelMatrix, LCenterOffset->X, LCenterOffset->Y, LCenterOffset->Z);
    L2DTexture->Translation.X=L2DTexture->SOffset;
    L2DTexture->Translation.Y=L2DTexture->TOffset;
    E3d_MatrixRotate(LModelMatrix, 'x', -LRotX);
    E3d_MatrixRotate(LModelMatrix, 'y', -LRotY);
    E3d_MatrixRotate(LModelMatrix, 'z', -LRotZ);
    if(L2DTexture->MappingMethod==E3dTXT_YZ) E3d_MatrixRotate(LModelMatrix, 'y', -90.0);
    if(L2DTexture->MappingMethod==E3dTXT_XZ) E3d_MatrixRotate(LModelMatrix, 'x', 90.0);
    LDX=(L2DTexture->Translation.X)*(LXMax-LXMin)+LXMin;
    LDY=(-L2DTexture->Translation.Y+(1.0-L2DTexture->TScaling))*(LYMax-LYMin)+LYMin;

//    LDDX=LDX*LModelMatrix[M00]+LDY*LModelMatrix[M10];
//    LDDY=LDX*LModelMatrix[M01]+LDY*LModelMatrix[M11];
//    LDDZ=LDX*LModelMatrix[M02]+LDY*LModelMatrix[M12];

    E3d_MatrixTranslate(LModelMatrix, LDX, LDY, 0.0);

    E3d_MatrixScale(LModelMatrix, L2DTexture->SScaling, L2DTexture->TScaling, 1.0);
    E3d_MatrixCopy(LModelMatrix, L2DTextureMapper->ProjectorMatrix);


    if((LProjMesh=(E3dMesh*)(L2DTextureMapper->Geometry))==NULL) LProjMesh=E3d_MeshAllocate();
    if(LProjMesh)
    {
     E3dEdge*	LEdge;
     int	LSCount=L2DTexture->SCount, LTCount=L2DTexture->TCount,
		LNEdges;

     LMNumOfVertices=(LSCount+1)*(LTCount+1)*2;

     L2DTextureMapper->Geometry=(E3dGeometry*)LProjMesh;

     if(LProjMesh->NumOfVertices!=LMNumOfVertices)
     {
      if(LProjMesh->Vertices) EFree(LProjMesh->Vertices);
      LProjMesh->Vertices=E3d_VerticesAllocate(LProjMesh->NumOfVertices=LMNumOfVertices, TRUE);
     }
     if(LProjMesh->Vertices)
     {
      E3dCoordinate	LXStep, LYStep;
      int		LS, LT;

      LVertex=LProjMesh->Vertices;

      LXStep=(LXMax-LXMin)/(E3dCoordinate)LSCount;
      LYStep=(LYMax-LYMin)/(E3dCoordinate)LTCount;
      LY=0.0;
      for(LT=0;LT<=LTCount;LT++)
      {
       LX=0.0;
       for(LS=0;LS<=LSCount;LS++)
       {
        E3d_VertexInit(LVertex++, LX, LY, LZMax);
	LX+=LXStep;
       }
       LY+=LYStep;
      }

      LY=0.0;
      for(LT=0;LT<=LTCount;LT++)
      {
       LX=0.0;
       for(LS=0;LS<=LSCount;LS++)
       {
        E3d_VertexInit(LVertex++, LX, LY, LZMin);
	LX+=LXStep;
       }
       LY+=LYStep;
      }

      LNEdges=(LSCount*(LTCount+1)+LTCount*(LSCount+1))*2;
      if(LProjMesh->NumOfEdges!=LNEdges)
      {
       if(LProjMesh->Edges) EFree(LProjMesh->Edges);
       LProjMesh->Edges=E3d_EdgesAllocate(LProjMesh->NumOfEdges=LNEdges);
      }

      if(LProjMesh->Edges)
      {
       int	LRO;

       LEdge=LProjMesh->Edges;

// Front projector
//
// Horizontal edges
//
       LRO=0;
       for(LT=0;LT<=LTCount;LT++, LRO+=LSCount+1)
       {
        for(LS=0;LS<LSCount;LS++)
        {
	 LEdge->Start=LS+LRO;
	 LEdge->End=LS+LRO+1;
	 LEdge++;
        }
       }

// Vertical edges
//
       for(LS=0;LS<=LSCount;LS++)
       {
	LRO=0;
        for(LT=0;LT<LTCount;LT++, LRO+=LSCount+1)
        {
	 LEdge->Start=LS+LRO;
	 LEdge->End=LS+LRO+LSCount+1;
	 LEdge++;
        }
       }


// Back projector
//
// Horizontal edges
//
       LRO=(LSCount+1)*(LTCount+1);
       for(LT=0;LT<=LTCount;LT++, LRO+=LSCount+1)
       {
        for(LS=0;LS<LSCount;LS++)
        {
	 LEdge->Start=LS+LRO;
	 LEdge->End=LS+LRO+1;
	 LEdge++;
        }
       }

// Vertical edges
//
       for(LS=0;LS<=LSCount;LS++)
       {
	LRO=(LSCount+1)*(LTCount+1);
        for(LT=0;LT<LTCount;LT++, LRO+=LSCount+1)
        {
	 LEdge->Start=LS+LRO;
	 LEdge->End=LS+LRO+LSCount+1;
	 LEdge++;
        }
       }
      }

// FIXME we'll need the Model's LocalToWorldMatrix here if E3d_GLDataTransformed is TRUE

      E3d_MeshRefreshGLEdges(LProjMesh, E3d_IdentityMatrix);


//----------------------------------------------------------------
// Create the vertex->pixel transformation Matrix
//----------------------------------------------------------------
      E3d_MatrixLoadIdentity(LModelMatrix);

      E3d_MatrixTranslate(LModelMatrix, LSCropMin, -LTCropMin, 0.0);

      E3d_MatrixScale(LModelMatrix, LPicWidth/(L2DTexture->SScaling)/(LXMax-LXMin), LPicHeight/(L2DTexture->TScaling)/(LYMax-LYMin), 1.0);
      E3d_MatrixTranslate(LModelMatrix, -LDX, -LDY, 0.0);
      if(L2DTexture->MappingMethod==E3dTXT_XZ) E3d_MatrixRotate(LModelMatrix, 'x', -90.0);
      if(L2DTexture->MappingMethod==E3dTXT_YZ) E3d_MatrixRotate(LModelMatrix, 'y', 90.0);
      E3d_MatrixRotate(LModelMatrix, 'z', LRotZ);
      E3d_MatrixRotate(LModelMatrix, 'y', LRotY);
      E3d_MatrixRotate(LModelMatrix, 'x', LRotX);
      E3d_MatrixTranslate(LModelMatrix, -LCenterOffset->X, -LCenterOffset->Y, -LCenterOffset->Z);
      E3d_MatrixCopy(LModelMatrix, L2DTextureMapper->VertexToPixelMatrix);


//----------------------------------------------------------------
// Create texel coordinates for the Vertices
//----------------------------------------------------------------
      LTexels=(E3dCoordinate*)EMalloc(sizeof(E3dCoordinate)*2*LNumOfVertices);
      if(LTexels)
      {
       E3dPolygon*	LTPolygon;
       E3dVertexNode*	LTVertexNode;
       unsigned int	LTC, LTN, LTVC, LTVN;
       int		LTVID;

       LTTexels=LTexels;
       for(LVCnt=0, LVertex=LVertices;LVCnt<LNumOfVertices;LVCnt++, LVertex++, LTTexels+=2)
       {
	LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
	LTX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20]+LModelMatrix[M30];
	LTY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21]+LModelMatrix[M31];
	if(LTX==LPicWidth) LTX-=0.0001;
	if(LTY==0.0) LTY+=0.0001;
	LTTexels[E3dX]=LTX;LTTexels[E3dY]=LPicHeight-LTY;
       }

// Update STs on Polygon VertexNodes of the PolyGroup
//
       LSCountF=(E3dCoordinate)(L2DTexture->SCount);
       LTCountF=(E3dCoordinate)(L2DTexture->TCount);

       LTPolygon=LPolyGroup->Polygons;LTN=LPolyGroup->NumOfPolygons;
       LVertex=LVertices;
       for(LTC=0;LTC<LTN;LTC++, LTPolygon++)
       {
	LTVertexNode=LTPolygon->VertexNodes;LTVN=LTPolygon->NumOfVertexNodes;
	for(LTVC=0;LTVC<LTVN;LTVC++, LTVertexNode++)
	{
	 if((LTVID=LTVertexNode->VertexID)>-1)
	 {
	  LTVertexNode->S=LTexels[LTVID*2]*LSCountF;LTVertexNode->T=LTexels[LTVID*2+1]*LTCountF;
	 }
	}
       }
      }

//------------------------------------------------------------------------------------------------------------------------
// Create the vertex->pixel transformation matrix for GL
// (no Cropping, because only the used area is stored for the GL)
//------------------------------------------------------------------------------------------------------------------------
#ifdef USEOpenGL
      E3d_MatrixLoadIdentity(LModelMatrix);
      E3d_MatrixScale(LModelMatrix, (LPicWidth)/(L2DTexture->SScaling)/(LXMax-LXMin), (LPicHeight)/(L2DTexture->TScaling)/(LYMax-LYMin), 1.0);
      E3d_MatrixTranslate(LModelMatrix, -LDX, -LDY, 0.0);
      if(L2DTexture->MappingMethod==E3dTXT_XZ) E3d_MatrixRotate(LModelMatrix, 'x', -90.0);
      if(L2DTexture->MappingMethod==E3dTXT_YZ) E3d_MatrixRotate(LModelMatrix, 'y', 90.0);
      E3d_MatrixRotate(LModelMatrix, 'z', LRotZ);
      E3d_MatrixRotate(LModelMatrix, 'y', LRotY);
      E3d_MatrixRotate(LModelMatrix, 'x', LRotX);
      E3d_MatrixTranslate(LModelMatrix, -LCenterOffset->X, -LCenterOffset->Y, -LCenterOffset->Z);
//---------------------------------------------------------------------
// Create GL texel coordinates for the vertices (values: 0.0...1.0)
//---------------------------------------------------------------------

      LGLTexels=(E3dCoordinate*)EMalloc(sizeof(E3dCoordinate)*2*LNumOfVertices);
      if(LGLTexels)
      {
       E3dPolygon*	LTPolygon;
       E3dVertexNode*	LTVertexNode;
       unsigned int	LTC, LTN, LTVC, LTVN;
       int		LTVID;

       LTTexels=LTexels;LTGLTexels=LGLTexels;
       for(LVCnt=0, LVertex=LVertices;LVCnt<LNumOfVertices;LVCnt++, LVertex++, LTTexels+=2, LTGLTexels+=2)
       {
	LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
	LTX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20]+LModelMatrix[M30];
	LTY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21]+LModelMatrix[M31];
	LTGLTexels[E3dX]=LTX/LPicWidth;LTGLTexels[E3dY]=1.0-LTY/LPicHeight;
       }

// Update OpenGL STs on Polygon VertexNodes of the PolyGroup
//
       LTPolygon=LPolyGroup->Polygons;LTN=LPolyGroup->NumOfPolygons;
       LVertex=LVertices;
       for(LTC=0;LTC<LTN;LTC++, LTPolygon++)
       {
	LTVertexNode=LTPolygon->VertexNodes;LTVN=LTPolygon->NumOfVertexNodes;
	for(LTVC=0;LTVC<LTVN;LTVC++, LTVertexNode++)
	{
	 if((LTVID=LTVertexNode->VertexID)>-1)
	 {
	  LTVertexNode->GLST[0]=LGLTexels[LTVID*2];LTVertexNode->GLST[1]=LGLTexels[LTVID*2+1];
	 }
	}
       }
      }
#endif // USEOpenGL
     }
    }
   }
  break;

  case E3dTXT_CYLINDRICAL:
   {
    E3dMesh*		LProjMesh;
    unsigned int	LMNumOfVertices;

    if((LProjMesh=(E3dMesh*)(L2DTextureMapper->Geometry))==NULL) LProjMesh=E3d_MeshAllocate();
    if(LProjMesh)
    {
     E3dCoordinate	LXMiddle, LZMiddle, LRadius, LRadiusSquare, LDSq;
     E3dEdge*		LEdge;
     int		LSCount=L2DTexture->SCount, LTCount=L2DTexture->TCount,
			LNEdges,
			LSteps=36;	// Sub-steps between repeats to approximate side of cylinder better

     LMNumOfVertices=(LSCount*LSteps+1)*(LTCount+1);

     L2DTextureMapper->Geometry=(E3dGeometry*)LProjMesh;



//----------------------------------------------------------------------------------------------------------------
// Transform vertices of the Mesh by the 2DTexture's Matrix to calculate the bounding cylinder for the Geometry
//----------------------------------------------------------------------------------------------------------------

// Must initialize BCylinder with first 2 Vertices (must have at least 2)
//
     if(LNumOfVertices>1)
     {
      LVertex=LVertices;

//      LX=LVertex->X-LCenterOffset->X;LY=LVertex->Y-LCenterOffset->Y;LZ=LVertex->Z-LCenterOffset->Z;
      LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
      LDX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20];
      LDY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21];
      LDZ=LX*LModelMatrix[M02]+LY*LModelMatrix[M12]+LZ*LModelMatrix[M22];
      LYMin=LYMax=LDY;
      LVertex++;

      LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
      LXMiddle=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20];
      LDY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21];
      LZMiddle=LX*LModelMatrix[M02]+LY*LModelMatrix[M12]+LZ*LModelMatrix[M22];
      LXMiddle=(LDX+LXMiddle)*0.5;
      if(LDY<LYMin) LYMin=LDY;if(LDY>LYMax) LYMax=LDY;
      LZMiddle=(LDZ+LZMiddle)*0.5;
      LRadiusSquare=(LDX-LXMiddle)*(LDX-LXMiddle)+(LDZ-LZMiddle)*(LDZ-LZMiddle);
      LRadius=sqrt(LRadiusSquare);

printf("LRadius %f  %f,%f\n", LRadius, LXMiddle, LZMiddle);fflush(stdout);
      LVertex++;



      for(LVCnt=2;LVCnt<LNumOfVertices;LVCnt++, LVertex++)
      {
       LX=LVertex->X;LY=LVertex->Y;LZ=LVertex->Z;
       LDX=LX*LModelMatrix[M00]+LY*LModelMatrix[M10]+LZ*LModelMatrix[M20];
       LDY=LX*LModelMatrix[M01]+LY*LModelMatrix[M11]+LZ*LModelMatrix[M21];
       LDZ=LX*LModelMatrix[M02]+LY*LModelMatrix[M12]+LZ*LModelMatrix[M22];

       LDSq=(LDX-LXMiddle)*(LDX-LXMiddle)+(LDZ-LZMiddle)*(LDZ-LZMiddle);

// Is the new point outside the circle?
//
       if(LDSq>LRadiusSquare)
       {
       }


       if(LDY<LYMin) LYMin=LDY;if(LDY>LYMax) LYMax=LDY;
      }


// Compute TextureMapper Matrix
//
      E3d_MatrixLoadIdentity(LModelMatrix);
      E3d_MatrixTranslate(LModelMatrix, LXMiddle, 0.0, LZMiddle);
//      E3d_MatrixTranslate(LModelMatrix, LCenterOffset->X, LCenterOffset->Y, LCenterOffset->Z);
      L2DTexture->Translation.Y=L2DTexture->TOffset;
      E3d_MatrixRotate(LModelMatrix, 'z', -LRotZ);
      E3d_MatrixRotate(LModelMatrix, 'x', -LRotX);
      E3d_MatrixRotate(LModelMatrix, 'y', -LRotY+90.0);
      LDY=(-L2DTexture->Translation.Y+(1.0-L2DTexture->TScaling))*(LYMax-LYMin)+LYMin;

      E3d_MatrixTranslate(LModelMatrix, 0.0, LDY, 0.0);

      E3d_MatrixScale(LModelMatrix, 1.0, L2DTexture->TScaling, 1.0);
      E3d_MatrixCopy(LModelMatrix, L2DTextureMapper->ProjectorMatrix);



      if(LProjMesh->NumOfVertices!=LMNumOfVertices)
      {
       if(LProjMesh->Vertices) EFree(LProjMesh->Vertices);
       LProjMesh->Vertices=E3d_VerticesAllocate(LProjMesh->NumOfVertices=LMNumOfVertices, TRUE);
      }

      if(LProjMesh->Vertices)
      {
       E3dCoordinate	LX, LY, LZ, LYStep=(LYMax-LYMin)/(E3dCoordinate)LTCount;
       E3dAngle		LStartAngle, LEndAngle, LAngle, LAngleStep;
       int		LS, LT, LSides;

       LVertex=LProjMesh->Vertices;

       LStartAngle=L2DTexture->SOffset*360.0;
       LEndAngle=LStartAngle+L2DTexture->SScaling*360.0;
       LSteps=(int)((LEndAngle-LStartAngle)*0.1);if(LSteps<1) LSteps=1;

       LSides=LSCount*LSteps;
       LAngleStep=(LEndAngle-LStartAngle)/(double)LSides*E3dRPI;


//printf("CYL #v %d\n", LMNumOfVertices);fflush(stdout);

       LVertex=LProjMesh->Vertices;
       LAngle=LStartAngle*E3dRPI;
       for(LS=0;LS<=LSides;LS++, LAngle+=LAngleStep)
       {
	LX=cos(LAngle)*LRadius;LY=LYMax*2.0;LZ=sin(LAngle)*LRadius;

	LVertex=LProjMesh->Vertices+LS;
	for(LT=0;LT<=LTCount;LT++, LVertex+=LSides+1, LY-=LYStep)
	{
	 LVertex->X=LX;LVertex->Y=LY;LVertex->Z=LZ;
	}
       }


       LNEdges=LSides*(LTCount+1)+LSides+1;
       if(LProjMesh->NumOfEdges!=LNEdges)
       {
	if(LProjMesh->Edges) EFree(LProjMesh->Edges);
	LProjMesh->Edges=E3d_EdgesAllocate(LProjMesh->NumOfEdges=LNEdges);
       }

       if(LProjMesh->Edges)
       {
	int	LRO;

	LEdge=LProjMesh->Edges;

// Horizontal edges
//
	LRO=0;
	for(LT=0;LT<=LTCount;LT++, LRO+=LSides+1)
	{
	 for(LS=0;LS<LSides;LS++)
	 {
	  LEdge->Start=LS+LRO;
	  LEdge->End=LS+LRO+1;
	  LEdge++;
	 }
	}


// Vertical edges
//
	LRO=(LSides+1)*LTCount;
	for(LS=0;LS<=LSides;LS+=LSteps)
	{
	 LEdge->Start=LS;
	 LEdge->End=LS+LRO;
	 LEdge++;
	}

        E3d_MeshRefreshGLEdges(LProjMesh, E3d_IdentityMatrix);

       }

      }
     }
    }
   }
  break;

  case E3dTXT_UV:
   {
    E3dUV*		LUVs=LMesh->UVs;

    if(LUVs)
    {
     E3dPolygon*	LTPolygon;
     E3dVertexNode*	LTVertexNode;
     unsigned int	LTC, LTN, LTVC, LTVN;
     int		LIndex;



// Update STs on Polygon VertexNodes of the PolyGroup
//
     LSCountF=(E3dCoordinate)(L2DTexture->SCount);
     LTCountF=(E3dCoordinate)(L2DTexture->TCount);

     LTPolygon=LPolyGroup->Polygons;LTN=LPolyGroup->NumOfPolygons;
     LVertex=LVertices;
     for(LTC=0;LTC<LTN;LTC++, LTPolygon++)
     {
      LTVertexNode=LTPolygon->VertexNodes;LTVN=LTPolygon->NumOfVertexNodes;
      for(LTVC=0;LTVC<LTVN;LTVC++, LTVertexNode++)
      {
       LIndex=LTVertexNode->VertexID;
       if(LIndex>-1)
       {
	LTVertexNode->S=LUVs[LIndex].U*LSCountF;LTVertexNode->T=LUVs[LIndex].V*LTCountF;
       }
      }
     }


#ifdef USEOpenGL

// Update OpenGL STs on Polygon VertexNodes of the PolyGroup
//
     LTPolygon=LPolyGroup->Polygons;LTN=LPolyGroup->NumOfPolygons;
     LVertex=LVertices;
     for(LTC=0;LTC<LTN;LTC++, LTPolygon++)
     {
      LTVertexNode=LTPolygon->VertexNodes;LTVN=LTPolygon->NumOfVertexNodes;
      for(LTVC=0;LTVC<LTVN;LTVC++, LTVertexNode++)
      {
       LIndex=LTVertexNode->VertexID;
       if(LIndex>-1)
       {
	LTVertexNode->GLST[0]=LUVs[LIndex].U*LSCountF;LTVertexNode->GLST[1]=LUVs[LIndex].V*LTCountF;
       }
      }
     }
#endif // USEOpenGL

    }
   }
  break;

 }
 if(LTexels) EFree(LTexels);

#ifdef USEOpenGL
 E3dGL_2DTextureSetMapping(L2DTexture);
#endif
}


//================================================================
// Update/create 2DTextureMapper for a 2DTexture on a PolyGroup
//================================================================
int E3d_MeshPolyGroupMap2DTexture(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, E3d2DTexture* L2DTexture)
{
 E3dMaterial*		LMaterial;
 unsigned int		LNumOfVertices=LMesh->NumOfVertices;


 LMaterial=LPolyGroup->DrawingMaterial;

 if((LMaterial->Textures2D!=NULL)||(LPolyGroup->Textures2D!=NULL))
 {
   E3d2DTextureMapper*	L2DTextureMapper;
   E3d3DPosition	LBBMin, LBBMax;
   unsigned int		LTCnt, LTotalTNum;

   E3d_MeshGetBoundingBox(LMesh, &LBBMin, &LBBMax, E3dBB_ALL);


// No TextureMappers yet? Create them
//
   if(LPolyGroup->TextureMappers==NULL)
   {
     E3d_MeshPolyGroupMap2DTexturesBB(LMesh, LPolyGroup, &LBBMin, &LBBMax);
     return(1);
   }


// Look for the given 2DTexture in the 2DTextureMapper array of the PolyGroup
//
   L2DTextureMapper=LPolyGroup->TextureMappers;
   LTotalTNum=LPolyGroup->NumOfTextureMappers;

   for(LTCnt=0;LTCnt<LTotalTNum;LTCnt++, L2DTextureMapper++)
   {
     if(L2DTextureMapper->T2DTexture==L2DTexture)

     if(L2DTexture->MappingMethod!=E3dTXT_FREEHAND)
     {
       E3d3DPosition	LCenterOffset;

//----------------------------------------------------------------------------------------
// Calculate offset between center of the bounding box and the local origin of the Mesh
//----------------------------------------------------------------------------------------
       LCenterOffset.X=(LBBMax.X+LBBMin.X)*0.5;
       LCenterOffset.Y=(LBBMax.Y+LBBMin.Y)*0.5;
       LCenterOffset.Z=(LBBMax.Z+LBBMin.Z)*0.5;

       _E3d_PolyGroupMap2DTextureMP(LMesh, LNumOfVertices, LPolyGroup, L2DTexture, L2DTextureMapper, &LCenterOffset);
       return(1);
     }
   }

// If we get here, we couldn't find the Mapper for this 2DTexture on the PolyGroup
//

   return(1);
 }
 else return(0);
}


//========================================================
// Update/create 2DTextureMappers on a PolyGroup
//========================================================
int E3d_MeshPolyGroupMap2DTexturesBB(E3dMesh* LMesh, E3dPolyGroup* LPolyGroup, E3d3DPosition* LBBMin, E3d3DPosition* LBBMax)
{
 E3d2DTexture*		L2DTexture;
 E3d2DTexture**		L2DTextures;
 E3d2DTextureMapper*	L2DTextureMapper;
 E3d3DPosition		LCenterOffset;
 E3dMaterial*		LMaterial;
 E3dVertex*		LVertices=LMesh->Vertices;
 unsigned int		LNumOfVertices=LMesh->NumOfVertices;


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


//--------------------------------------------------------------------------------
// Calculate offset between center of the bounding box and its local origin
//--------------------------------------------------------------------------------
 LCenterOffset.X=(LBBMax->X+LBBMin->X)*0.5;
 LCenterOffset.Y=(LBBMax->Y+LBBMin->Y)*0.5;
 LCenterOffset.Z=(LBBMax->Z+LBBMin->Z)*0.5;

 LMaterial=LPolyGroup->DrawingMaterial;

 if((LMaterial->Textures2D!=NULL)||(LPolyGroup->Textures2D!=NULL))
 {
   unsigned int		LTCnt, LMatTNum, LPGrpTNum;

   LMatTNum=LMaterial->NumOf2DTextures;
   LPGrpTNum=LPolyGroup->NumOf2DTextures;

// Create TextureMapper array
//
   if(LPolyGroup->TextureMappers) EFree(LPolyGroup->TextureMappers);

   LPolyGroup->TextureMappers=L2DTextureMapper=(E3d2DTextureMapper*)EMalloc(sizeof(E3d2DTextureMapper)*(LMatTNum+LPGrpTNum));

   if(L2DTextureMapper)
   {
    LPolyGroup->NumOfTextureMappers=LMatTNum+LPGrpTNum;

    L2DTextures=LMaterial->Textures2D;
    for(LTCnt=0;LTCnt<LMatTNum;LTCnt++, L2DTextureMapper++)
    {
     E3d_2DTextureMapperDefault(L2DTextureMapper, L2DTextures[LTCnt]);
    }

    L2DTextures=LPolyGroup->Textures2D;
    for(LTCnt=0;LTCnt<LPGrpTNum;LTCnt++, L2DTextureMapper++)
    {
     E3d_2DTextureMapperDefault(L2DTextureMapper, L2DTextures[LTCnt]);
    }
   }


   L2DTextureMapper=LPolyGroup->TextureMappers;

// Material's 2DTextures
//
   L2DTextures=LMaterial->Textures2D;
   for(LTCnt=0;LTCnt<LMatTNum;LTCnt++, L2DTextureMapper++)
   {
    L2DTexture=L2DTextures[LTCnt];

    if(L2DTexture->MappingMethod!=E3dTXT_FREEHAND)
    {
     _E3d_PolyGroupMap2DTextureMP(LMesh, LNumOfVertices, LPolyGroup, L2DTexture, L2DTextureMapper, &LCenterOffset);
    }
   }

// PolyGroup's 2DTextures
//
   L2DTextures=LPolyGroup->Textures2D;
   for(LTCnt=0;LTCnt<LPGrpTNum;LTCnt++, L2DTextureMapper++)
   {
    L2DTexture=L2DTextures[LTCnt];

    if(L2DTexture->MappingMethod!=E3dTXT_FREEHAND)
    {
     _E3d_PolyGroupMap2DTextureMP(LMesh, LNumOfVertices, LPolyGroup, L2DTexture, L2DTextureMapper, &LCenterOffset);
    }
   }
 }
 return(LMaterial->NumOf2DTextures);
}
