/*======================================================================*/
/* 3DLib								*/
/*									*/
/* General Geometry-related functions					*/
/*									*/
/* AUTHOR:	Gabor Nagy						*/
/* DATE:	1996-Dec-29 23:04:25					*/
/*									*/
/* 3DLib(TM) Copyright (C) 1995 by Gabor Nagy. All rights reserved.	*/
/*======================================================================*/
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <sys/types.h>

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

#ifndef _E3D_h
#include <E3D/E3D.h>
#endif

#ifndef _E3DMatrix_h
#include <E3D/Matrix.h>
#endif

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

#ifndef _E3DSelect_h
#include <E3D/Select.h>
#endif


E3dGeometryClass**	E3d_CustomGeometryClasses=NULL;
int			E3d_NumOfCustomGeometryClasses=0;


//================================================================
// Add an Output interface to a Geometry
//================================================================
EBool E3d_GeometryAddOutput(E3dGeometry* LGeometry, E3dInterface* LOutput)
{
 return(ELst_AddPointerChk((void***)(&(LGeometry->Outputs)), &(LGeometry->NumOfOutputs), LOutput));
}


//================================================================
// Remove Output from a Geometry
//================================================================
EBool E3d_GeometryRemoveOutput(E3dGeometry* LGeometry, E3dInterface* LOutput)
{
 if(LGeometry) return(ELst_RemovePointer((void***)(&(LGeometry->Outputs)), &(LGeometry->NumOfOutputs), LOutput));
 else return(FALSE);
}


//========================================================================================
// Free a Geometry's rendering-related data, such as OpenGL vertex arrays etc.
// we usually do this when a Geometry is deleted from the Scene, but stored for undo.
//========================================================================================
void E3d_GeometryFreeRenderData(E3dGeometry* LGeometry)
{
 switch(LGeometry->GeoType)
 {
  case E3dGEO_SPLINE:
   E3d_SplineFreeRenderData((E3dSpline*)LGeometry);
  break;

  case E3dGEO_FACE:
   {
    E3dFace*		LFace=(E3dFace*)LGeometry;
    unsigned int	LHC, LHN=LFace->NumOfHoles;

    if(LFace->Vertices) { EFree(LFace->Vertices);LFace->Vertices=NULL;LFace->NumOfVertices=0; }
    if(LFace->Triangles) { EFree(LFace->Triangles);LFace->Triangles=NULL;LFace->NumOfTriangles=0; }

    E3d_SplineFreeRenderData(LFace->Exterior);
    for(LHC=0;LHC<LHN;LHC++) E3d_SplineFreeRenderData(LFace->Holes[LHC]);
   }
  break;

  case E3dGEO_MESH:
  case E3dGEO_SKINMESH:
   E3d_MeshFreeRenderData((E3dMesh*)LGeometry);
  break;
 }
}


//========================================================================================
// Free a Geometry
//
// Arguments
//  E3dGeometry* LGeometry       Pointer to the E3dGeometry to be freed
//
// Description
//  This function first decrements the reference count (RefCnt field) of the given
//  Geometry structure (if it's not already zero).
//  If RefCnt is still greater than zero, E3d_GeometryFree() will simply return.<BR>
//  If RefCnt is zero, E3d_GeometryFree() will free all the memory associated
//  with this Geometry and the Geometry structure itself.<BR>
//  Note that there are separate functions for allocating different kinds of
//  Geometries, but any Geometry can and should be freed by calling <B>E3d_GeometryFree()</B>.
//
// See also
//  Face/E3d_FaceAllocate, Mesh/E3d_MeshAllocate, Mesh/E3d_SkinMeshAllocate, Spline/E3d_SplineAllocate			*/
//========================================================================================
void E3d_GeometryFree(E3dGeometry* LGeometry)
{
//printf("E3d_GeometryFree %08x %s  %d\n", (unsigned int)LGeometry, LGeometry->Name, LGeometry->RefCnt);fflush(stdout);

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

 if(LGeometry->Name) { EFree(LGeometry->Name);LGeometry->Name=NULL; }

// if(LGeometry->Callbacks) { EFree(LGeometry->Callbacks);LGeometry->Callbacks=NULL; }

 if(LGeometry->Inputs) { EFree(LGeometry->Inputs);LGeometry->Inputs=NULL; }
 if(LGeometry->Outputs) { EFree(LGeometry->Outputs);LGeometry->Outputs=NULL; }


 E3d_GeometryInfoRemoveAll(LGeometry);


 switch(LGeometry->GeoType)
 {
  case E3dGEO_POINTS:			E3d_PointsFree((E3dPoints*)LGeometry);break;
  case E3dGEO_MESH:			E3d_MeshFree((E3dMesh*)LGeometry);break;
  case E3dGEO_SKINMESH:			E3d_SkinMeshFree((E3dSkinMesh*)LGeometry);break;
//  case E3dGEO_SUBDIVISION_SURFACE:	E3d_SubdivisionSurfaceFree((E3dMesh*)LGeometry);break;
  case E3dGEO_SPLINE:			E3d_SplineFree((E3dSpline*)LGeometry);break;
//  case E3dGEO_FACE:			E3d_FaceFree((E3dSpline*)LGeometry);break;
//  case E3dGEO_PATCH:			E3d_PatcheFree((E3dSpline*)LGeometry);break;
 }
}



//================================================================================================
// Perform transformation on a Geometry
//
// Arguments
//  E3dGeometry* LGeometry       The Geometry to be transformed
//  E3dMatrix    LMatrix         The Matrix, defining the transformation
//
// Description
//  This function performs the transformation, described by LMatrix, on the given Geometry, by
//  transforming the actual Vertices (of a Mesh), CVs (of a Spline, Face etc.) etc..<BR>
//  This operation is sometimes referred to as "Freezing the transformation" of an object, or
//  more colloquially: "baking the transformation into a Geometry / object".
//================================================================================================
void E3d_GeometryTransform(E3dGeometry* LGeometry, E3dMatrix LMatrix, E3dMatrix LNormalMatrix)
{
 E3dCoordinate	mX, mY, mZ, LR, LNX, LNY, LNZ;
 unsigned int	LGC, LVN, LVC, LN, LC;


 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    E3dMesh*		LMesh=(E3dMesh*)LGeometry;
    E3dPolyGroup*	LPolyGroup;
    E3dPolyGroup**	LPolyGroups;
    E3dPolygon*		LPolygon;
    E3dVertexNode*	LVertexNode;
    E3dVertex*		LVertex;


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

    LVertex=LMesh->Vertices;

// Transform the vertices of the Mesh with LMatrix
//
    for(LVC=0, LVN=LMesh->NumOfVertices;LVC<LVN;LVC++, LVertex++)
    {
     mX=LVertex->X;mY=LVertex->Y;mZ=LVertex->Z;
     E3dM_MatrixTransform3x4(LMatrix, LVertex->X, LVertex->Y, LVertex->Z);
    }


    E3d_MeshRefreshPolygonNormals(LMesh);

// Transform the normals of the Mesh with LNormalMatrix
//

// FIXME: To speed up, we could check the scaling if it's non-unit,
// before renormalizing the normals...
//
    for(LGC=0;LGC<LMesh->NumOfPolyGroups;LGC++)
    {
      LPolyGroup=LPolyGroups[LGC];

      LPolygon=LPolyGroup->Polygons;
      for(LC=0, LN=LPolyGroup->NumOfPolygons;LC<LN;LC++, LPolygon++)
      {
/*
	mX=LPolygon->Normal.X;mY=LPolygon->Normal.Y;mZ=LPolygon->Normal.Z;
//       E3dM_MatrixTransform3x3(LNormalMatrix, LPolygon->Normal.X, LPolygon->Normal.Y, LPolygon->Normal.Z);
	E3dM_MatrixTransform3x3(LNormalMatrix, LNX, LNY, LNZ);
	LR=sqrt(LNX*LNX+LNY*LNY+LNZ*LNZ);
	if(LR>0.0) LR=1.0/LR;
	LPolygon->Normal.X=LNX*LR;
	LPolygon->Normal.Y=LNY*LR;
	LPolygon->Normal.Z=LNZ*LR;
*/

	LVertexNode = LPolygon->VertexNodes;
	for(LVC=0, LVN=LPolygon->NumOfVertexNodes;LVC<LVN;LVC++, LVertexNode++)
	{
	  if(LVertexNode->VertexID >= -1)
	  {
	    mX=LVertexNode->Normal.X;mY=LVertexNode->Normal.Y;mZ=LVertexNode->Normal.Z;
//	   E3dM_MatrixTransform3x3(LNormalMatrix, LVertexNode->Normal.X, LVertexNode->Normal.Y, LVertexNode->Normal.Z);
	    E3dM_MatrixTransform3x3(LNormalMatrix, LVertexNode->Normal.X, LVertexNode->Normal.Y, LVertexNode->Normal.Z);

	    E3dM_MatrixTransform3x3(LNormalMatrix, LNX, LNY, LNZ);
	    LR=sqrt(LNX*LNX+LNY*LNY+LNZ*LNZ);
	    if(LR>0.0) LR=1.0/LR;
	    LVertexNode->Normal.X=LNX*LR;
	    LVertexNode->Normal.Y=LNY*LR;
	    LVertexNode->Normal.Z=LNZ*LR;
	  }
	}
      }

    }
   }
  break;

  case E3dGEO_SPLINE:
   E3d_SplineTransform((E3dSpline*)LGeometry, LMatrix);
  break;

  case E3dGEO_FACE:
   {
    E3dFace*		LFace=(E3dFace*)LGeometry;
    unsigned int	LHC, LHN=LFace->NumOfHoles;

    E3d_SplineTransform(LFace->Exterior, LMatrix);
    for(LHC=0;LHC<LHN;LHC++) E3d_SplineTransform(LFace->Holes[LHC], LMatrix);
   }
  break;
 }
}


//========================================
// Get the bounding box of a Geometry
//========================================
EBool E3d_GeometryGetBoundingBox(E3dGeometry* LGeometry, E3d3DPosition* LBBMin, E3d3DPosition* LBBMax, unsigned int LWhatToInclude)
{
 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
  return(E3d_MeshGetBoundingBox((E3dMesh*)LGeometry, LBBMin, LBBMax, LWhatToInclude));

  case E3dGEO_SPLINE:
  return(E3d_SplineGetBoundingBox((E3dSpline*)LGeometry, LBBMin, LBBMax, LWhatToInclude));

  case E3dGEO_FACE:
  return(E3d_SplineGetBoundingBox(((E3dFace*)LGeometry)->Exterior, LBBMin, LBBMax, LWhatToInclude));
 }
 return(FALSE);
}


//========================================================================
// Get the bounding box of a Geometry in a given coordinate system
//========================================================================
EBool E3d_GeometryGetTransformedBoundingBox(E3dGeometry* LGeometry, E3dMatrix LMatrix, E3d3DPosition* LBBMin, E3d3DPosition* LBBMax, unsigned int LWhatToInclude)
{
 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
  return(E3d_MeshGetTransformedBoundingBox((E3dMesh*)LGeometry, LMatrix, LBBMin, LBBMax, LWhatToInclude));

  case E3dGEO_SPLINE:
  return(E3d_SplineGetTransformedBoundingBox((E3dSpline*)LGeometry, LMatrix, LBBMin, LBBMax, LWhatToInclude));

  case E3dGEO_FACE:
  return(E3d_SplineGetTransformedBoundingBox(((E3dFace*)LGeometry)->Exterior, LMatrix, LBBMin, LBBMax, LWhatToInclude));
 }
 return(FALSE);
}


//================================================
// Get the bounding box of a Geometry
//================================================
EBool E3d_GeometryGetPerspectiveProjectedBoundingBox(E3dGeometry* LGeometry, E3dMatrix LMatrix, E3d2DPosition* LBBMin, E3d2DPosition* LBBMax, E3dHPosition* LMaxProjectedXYAbsValPoint, unsigned int LWhatToInclude)
{
 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
  return(E3d_MeshGetPerspectiveProjectedBoundingBox((E3dMesh*)LGeometry, LMatrix, LBBMin, LBBMax, LMaxProjectedXYAbsValPoint, LWhatToInclude));

  case E3dGEO_SPLINE:
  return(E3d_SplineGetPerspectiveProjectedBoundingBox((E3dSpline*)LGeometry, LMatrix, LBBMin, LBBMax, LMaxProjectedXYAbsValPoint, LWhatToInclude));

  case E3dGEO_FACE:
  return(E3d_SplineGetPerspectiveProjectedBoundingBox(((E3dFace*)LGeometry)->Exterior, LMatrix, LBBMin, LBBMax, LMaxProjectedXYAbsValPoint, LWhatToInclude));
 }
 return(FALSE);
}


//================================================================================
// Clone a Geometry
//
// Arguments
//  E3dGeometry* LGeometry       Pointer to the E3dGeometry to be duplicated
//  int          LFlags          Option flags
//
// Description
//  This function creates an exact duplicate of the given Geometry.
//  The option flags are OR-ed together bits that have the following meaning:
// <PRE>  E3dCLONE_CONSTRUCTION_HISTORY -  Clone the resources in GeoInfo
//  E3dCLONE_LODS                  - Clone Levels of Detail
//  E3dCLONE_MATERIALS             - Clone Materials (otherwise they will be shared)</PRE>
//
// Return value
//  A pointer to the copy, or NULL in case of an error
//================================================================================
E3dGeometry* E3d_GeometryClone(E3dGeometry* LGeometry, int LFlags)
{
 E3dGeometry*	LNewGeometry;

 switch(LGeometry->GeoType)
 {
  case E3dGEO_MESH:	LNewGeometry=(E3dGeometry*)E3d_MeshClone((E3dMesh*)LGeometry, LFlags);break;
  case E3dGEO_SPLINE:	LNewGeometry=(E3dGeometry*)E3d_SplineClone((E3dSpline*)LGeometry);break;
  case E3dGEO_FACE:	LNewGeometry=(E3dGeometry*)E3d_FaceClone((E3dFace*)LGeometry, NULL, LFlags);break;
  default:		LNewGeometry=NULL;break;
 }

 if(LNewGeometry)
 {
  LNewGeometry->LODReference=LGeometry->LODReference;

  if(LFlags&E3dCLONE_INFO)
  {
   if(LNewGeometry)
   {
    if(LGeometry->Info)
    {
     E3dGeometryInfo*	LInfo;
     E3dGeometryInfo*	LNewInfo;
     E3dGeometryClass*	LClass;
     unsigned int	LC, LN=LGeometry->NumOfInfoRecords;

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

// Create new Info record by calling the EditProc with a NULL LInfo:
//
      LClass->EditProc(NULL, LNewGeometry, NULL, 0);
      LNewInfo=E3d_GeometryInfoByClass(LNewGeometry, LClass);

// Clone resources if any
//
      if(LClass->Resources) ERes_Copy(LClass->Resources, LInfo, LNewInfo);
     }
    }
   }
  }

  if(LFlags&E3dCLONE_LODS)
  {
   if(LGeometry->NumOfLODs)
   {
    LNewGeometry->LODs=(E3dGeometry**)EMalloc(sizeof(E3dGeometry*)*LGeometry->NumOfLODs);
    if(LNewGeometry->LODs)
    {
     int	LC;

     for(LC=0;LC<LGeometry->NumOfLODs;LC++)
     {
      LNewGeometry->LODs[LC]=E3d_GeometryClone(LGeometry->LODs[LC], LFlags&(E3dCLONE_ALL-E3dCLONE_LODS));
     }
     LNewGeometry->NumOfLODs=LGeometry->NumOfLODs;
    }
   }
  }
 }

 return(LNewGeometry);
}


//================================================
// Find a GeometryClass by its name
//================================================
E3dGeometryClass* E3d_GeometryClassFindByName(char* LName)
{
 E3dGeometryClass**	LClasses=E3d_CustomGeometryClasses;

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

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


//================================================
// Register a new Geometry class
//================================================
E3dGeometryClass* E3d_GeometryClassRegister(E3dGeometryClass* LClassTemplate)
{
 E3dGeometryClass**	LClasses=E3d_CustomGeometryClasses;
 E3dGeometryClass*	LClass;
 unsigned int		LC, LN;

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

    return(LClass);
   }
  }
 }

 if(LClasses==NULL) E3d_CustomGeometryClasses=LClasses=(E3dGeometryClass**)EMalloc(sizeof(E3dGeometryClass*));
 else
 {
  if((LClasses=(E3dGeometryClass**)ERealloc(E3d_CustomGeometryClasses, sizeof(E3dGeometryClass*)*(E3d_NumOfCustomGeometryClasses+1)))!=NULL) E3d_CustomGeometryClasses=LClasses;
 }

 if(LClasses)
 {
  if((LClass=(E3dGeometryClass*)EMalloc(sizeof(E3dGeometryClass)))!=NULL)
  {
   LClasses[E3d_NumOfCustomGeometryClasses]=LClass;

   LClass->Name=EStrDup(LClassTemplate->Name);
   LClass->StructSize=LClassTemplate->StructSize;
   LClass->EditProc=LClassTemplate->EditProc;
   LClass->DestroyProc=LClassTemplate->DestroyProc;
   LClass->Resources=LClassTemplate->Resources;
   E3d_NumOfCustomGeometryClasses++;
  }
  return(LClass);
 }
 else return(NULL);
}


//================================================
// Deactivate a GeometryClass
//================================================
void E3d_GeometryClassDeactivate(E3dGeometryClass* LClass)
{
 E3dGeometryClass**	LClasses=E3d_CustomGeometryClasses;

 if(LClasses)
 {
  unsigned int	LC, LN;

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


//================================================
// Remove a Geometry class
//================================================
void E3d_GeometryClassRemove(E3dGeometryClass* LClass)
{
 E3dGeometryClass**	LClasses=E3d_CustomGeometryClasses;

 if(LClasses)
 {
  unsigned int	LC, LN;

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


//================================================
// Remove Info record from a Geometry
//================================================
int E3d_GeometryInfoRemove(E3dGeometry* LGeometry, E3dGeometryInfo* LInfo)
{
// Free Class-specific data
//
 if(LGeometry->Info)
 {
  unsigned int	LC, LN=LGeometry->NumOfInfoRecords;

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

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


//================================================
// Remove all Info records from a Geometry
//================================================
int E3d_GeometryInfoRemoveAll(E3dGeometry* LGeometry)
{
// Free Class-specific data
//
 if(LGeometry->Info)
 {
  E3dGeometryInfo*	LInfo;
  E3dGeometryClass*	LClass;
  unsigned int		LC, LN=LGeometry->NumOfInfoRecords;

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

   LClass=LInfo->Class;

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


//========================================================
// Add an Info record of a given Class to a Model
//========================================================
E3dGeometryInfo* E3d_GeometryInfoAdd(E3dGeometry* LGeometry, E3dGeometryClass* LClass)
{
 E3dGeometryInfo*	LInfo=(E3dGeometryInfo*)EMalloc(LClass->StructSize);

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


//================================================
// Find Info record of a Geometry by its Class
//================================================
E3dGeometryInfo* E3d_GeometryInfoByClass(E3dGeometry* LGeometry, E3dGeometryClass* LClass)
{
 if(LGeometry->Info)
 {
  unsigned int		LC, LN=LGeometry->NumOfInfoRecords;

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


//================================================
// Call DestroyProc of GeometryInfo records
//================================================
int E3d_GeometryInfoCallDestroyProcs(E3dGeometry* LGeometry)
{
 if(LGeometry->Info)
 {
  E3dGeometryInfo*	LInfo;
  E3dGeometryClass*	LClass;
  unsigned int		LC, LN=LGeometry->NumOfInfoRecords;


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

   LClass=LInfo->Class;

   if(LClass)
   {
    if(LClass->DestroyProc) LClass->DestroyProc(NULL, LGeometry, LInfo);
   }
  }
  return(LN);
 }
 return(0);
}


//================================================================================
// Get the Material for a Geometry, parsing downward (SubGeometries)
//================================================================================
E3dMaterial* E3d_GeometryGetMaterial(E3dGeometry* LGeometry, EBool LSelectedSubGeo)
{
 switch(LGeometry->GeoType)
 {
   caseE3dMESH():
    {
      unsigned int	LPGC, LPGN;
      E3dMesh*		LMesh=(E3dMesh*)LGeometry;
      E3dPolyGroup*	LPolyGroup;
      E3dPolyGroup**	LPolyGroups;

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

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

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


//========================================================================
// Create/update TextureMapper for the given 2DTexture on a Geometry
//========================================================================
int E3d_GeometryMap2DTexture(E3dGeometry* LGeometry, E3d2DTexture* L2DTexture)
{
 unsigned int	LTotalMapped=0;

 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    unsigned int	LPGC, LPGN;
    E3dMesh*		LMesh=(E3dMesh*)LGeometry;
    E3dPolyGroup*	LPolyGroup;
    E3dPolyGroup**	LPolyGroups;

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

      LTotalMapped+=E3d_MeshPolyGroupMap2DTexture(LMesh, LPolyGroup, L2DTexture);
    }
   }
  break;
 }

 return(LTotalMapped);
}


//========================================================================
// Create/update TextureMappers for a Geometry
//========================================================================
int E3d_GeometryMap2DTextures(E3dGeometry* LGeometry)
{
 unsigned int	LTotalMapped=0;

 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    E3dMesh*		LMesh=(E3dMesh*)LGeometry;
    E3dPolyGroup*	LPolyGroup;
    E3dPolyGroup**	LPolyGroups;
    E3d3DPosition	LBBMin;
    E3d3DPosition	LBBMax;
    unsigned int	LPGC, LPGN;


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

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

    for(LPGC=0;LPGC<LPGN;LPGC++)
    {
     LPolyGroup=LPolyGroups[LPGC];

     LTotalMapped+=E3d_MeshPolyGroupMap2DTexturesBB(LMesh, LPolyGroup, &LBBMin, &LBBMax);
    }
   }
  break;
 }

 return(LTotalMapped);
}


//================================================================================
// Update a Geometry for drawing
//
// Arguments
//  E3dGeometry*   LGeometry       The Geometry to update
//  unsigned int   LFlags          OR-ed together flags, telling what 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 Geometry for rendering (drawing).
//  This is a platform-independent procedure that ensures that the given
//  Geometry 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_GeometryUpdateForDisplay(E3dGeometry* LGeometry, unsigned int LFlags)
{
// FIXME: this will be for custom display routines
//
// E3d_DisplayCallGeometryChangedCallbacks(LGeometry);


 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    E3dMesh*		LMesh=(E3dMesh*)LGeometry;
    E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
    E3dPolyGroup*	LPolyGroup;
    E3dMatrix		LMatrix;
    unsigned int	LGC, LGN=LMesh->NumOfPolyGroups;


    E3d_MatrixLoadIdentity(LMatrix);

    if(LFlags&E3dGF_REMOVE_STRIPS)
    {
     for(LGC=0;LGC<LGN;LGC++)
     {
      LPolyGroup=LPolyGroups[LGC];

      E3d_PolyGroupFreeTriangleStrips(LPolyGroup);
     }
    }
    if(LFlags&E3dGF_TOPOLOGY)
    {
     E3d_MeshFreeVertexUsageList(LMesh);
     E3d_MeshCreateEdgesFromPolyData(LMesh, NULL);
     if(LFlags&E3dGF_CALL_DESTROYPROCS) E3d_GeometryInfoCallDestroyProcs(LGeometry);
    }

    if(LFlags&E3dGF_VERTEX_POSITION) E3d_MeshCreateRenderablePolygons(LMesh);

// Make sure that no PolyGroups have a NULL DrawMaterial
//
    for(LGC=0;LGC<LGN;LGC++)
    {
     LPolyGroup=LPolyGroups[LGC];

     E3d_UpdateDrawingMaterial(LGeometry, LPolyGroup->Material, &(LPolyGroup->DrawingMaterial));
     if(LFlags&E3dGF_MATERIALS) E3d_MaterialUpdateForDisplay(LPolyGroup->DrawingMaterial);
    }


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

    if(LFlags&E3dGF_NORMALS) E3d_MeshRefreshNormals(LMesh, TRUE);

    if(LFlags&E3dGF_REMAP_TEXTURES) E3d_GeometryMap2DTextures(LGeometry);

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

  case E3dGEO_SPLINE:
   E3d_SplineCreateLinearSegments((E3dSpline*)LGeometry);
   E3d_SplineUpdateSegmentLengths((E3dSpline*)LGeometry, ((E3dSpline*)LGeometry)->NumOfStepsForLength);
  break;

  case E3dGEO_FACE:
   {
    E3dFace*	LFace=(E3dFace*)LGeometry;
    E3dSpline**	LHoles=LFace->Holes;
    E3dSpline*	LHole;

    E3d_SplineCreateLinearSegments(LFace->Exterior);
    E3d_SplineUpdateSegmentLengths(LFace->Exterior, LFace->Exterior->NumOfStepsForLength);

    if(LHoles)
    {
     unsigned int	LHC, LHN;

     LHN=LFace->NumOfHoles;
     for(LHC=0;LHC<LHN;LHC++)
     {
      LHole=LHoles[LHC];

      E3d_SplineCreateLinearSegments(LHole);
      E3d_SplineUpdateSegmentLengths(LHole, LHole->NumOfStepsForLength);
     }
    }

// Free these and E3d_DrawFace() or E3d_GetRenderTriangles() etc. will call E3d_FaceTesselate() when necessary
//
// This will speed up point moving, Contour tesselation changing etc. when there are no Windows with Solid
// display modes
//
    if(LFace->Vertices) { EFree(LFace->Vertices);LFace->Vertices=NULL;LFace->NumOfVertices=0; }
    if(LFace->Triangles) { EFree(LFace->Triangles);LFace->Triangles=NULL;LFace->NumOfTriangles=0; }

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


 if(LFlags&(E3dGF_TOPOLOGY|E3dGF_SHAPE|E3dGF_NORMALS))
 {
  E3dGeometryCallbackStruct	LCBS;

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

 if(LFlags&(E3dGF_TOPOLOGY|E3dGF_SHAPE|E3dGF_NORMALS))
 {
  E3dSceneGeometryCallbackStruct	LCBS;
  E3dScene*				LScene=E3d_Scene;


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


//================================================================================
// Remove a Geometry from all the Models that reference it
//================================================================================
int E3d_GeometryRemoveFromModels(E3dGeometry* LGeometry)
{
 if(LGeometry->NumOfModels)
 {
  E3dModel*	LModel;
  unsigned int	LC, LN;
  int		LMN=LGeometry->NumOfModels;

  while(LGeometry->NumOfModels>0)
  {
// Do it from the end, so we don't have to memmove things...
//
   LModel=LGeometry->Models[LGeometry->NumOfModels-1];

   if((LN=LModel->NumOfGeometries)==0) return(-1);
   for(LC=0;LC<LN;LC++)
   {
    if(LModel->Geometries[LC]==LGeometry)
    {
     if(LC<(LN-1)) memmove(LModel->Geometries+LC, LModel->Geometries+LC+1, sizeof(E3dGeometry*)*(LN-LC-1));

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

   LGeometry->NumOfModels-=1;
  }
  return(LMN);
 }
 return(0);
}


//================================================
// Select/deselect the given Geometry
//================================================
void E3d_GeometryToggleSelection(E3dGeometry* LGeometry, EBool LUnselectTheRest)
{
 E3dModel*	LModel;
 int		LCnt;
 EBool		LSet;


 if(LGeometry->Selection==E3dGSEL_GEOMETRY) LSet=FALSE;
 else LSet=TRUE;

 if(LUnselectTheRest)
 {
  E3dModel**		LRootModels;
  unsigned int		LRN;


// Unselect all other Models and their Geometries
//
  LRN=E3d_Scene->NumOfRootModels;LRootModels=E3d_Scene->RootModels;
  for(LCnt=0;LCnt<LRN;LCnt++)
  {
   for(LModel=LRootModels[LCnt];LModel;LModel=LModel->Next)
   {
    LModel->Selection=E3dSEL_NONE;
    E3d_UnselectModelGeometries(LModel, LGeometry, E3dSF_ALL);
   }
  }
 }

 if(LSet)
 {
  LGeometry->Selection=E3dGSEL_GEOMETRY;
  E3d_GeometryModelsSetSelection(LGeometry, E3dSEL_GEOMETRY);
 }
}


//========================================================================
// Set the Selection field of the Models of a Geometry to a given value
//========================================================================
void E3d_GeometryModelsSetSelection(E3dGeometry* LGeometry, int LSelection)
{
 unsigned int	LC, LN;

 LN=LGeometry->NumOfModels;
 for(LC=0;LC<LN;LC++)
 {
  LGeometry->Models[LC]->Selection=LSelection;
 }
}


//========================================================
// Find the Model of a Geometry that has it selected
//========================================================
E3dModel* E3d_GeometryGetSelectingModel(E3dGeometry* LGeometry)
{
 E3dModel*	LModel=NULL;
 int		LC;
 EBool		LModelSelected;

 for(LC=0;LC<LGeometry->NumOfModels;LC++)
 {
  LModel=LGeometry->Models[LC];

  E3dM_IsModelSelected(LGeometry->Models[LC], LModelSelected);

  if(LModelSelected) return(LGeometry->Models[LC]);
  if((LGeometry->Models[LC]->Selection==E3dSEL_GEOMETRY)&&(LGeometry->Selection!=E3dGSEL_NONE)) return(LGeometry->Models[LC]);
 }
 return(NULL);
}


//========================================
// Tesselate a Geometry
//========================================
EBool E3d_GeometryTesselate(E3dGeometry* LGeometry, EBool LDoConvex)
{
 E3dPolyGroup*	LPolyGroup;
 E3dPolyGroup**	LPolyGroups;
 E3dPolygon*	LPolygon;
 E3dPolygon*	LPolygonsR;
 E3dPolygon*	LPolygonR;
 E3dITriangle*	LITriangle;
 E3dVertexNode*	LVertexNodes;
 int		LC, LC1, LN, LPolyNum, LNumOfTriangles;
 unsigned int	LGC;


 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    E3dMesh* LMesh=(E3dMesh*)LGeometry;

    if((LGC=LMesh->NumOfPolyGroups)==0) return(FALSE);

    LPolyGroups=LMesh->PolyGroups;

    for(LGC=0;LGC<LMesh->NumOfPolyGroups;LGC++)
    {
     LNumOfTriangles=0;
     LPolyGroup=LPolyGroups[LGC];
     if((LPolyNum=LPolyGroup->NumOfPolygons)>0)
     {
      for(LC=0, LPolygon=LPolyGroup->Polygons;LC<LPolyNum;LC++, LPolygon++)
      {
       if(LPolygon->NumOfVertices>3) E3d_TriangulatePolygon(LMesh->Vertices, LPolygon, LDoConvex);
      }

// Get total number of resulting Polygons
//
      for(LC=0, LPolygon=LPolyGroup->Polygons;LC<LPolyNum;LC++, LPolygon++)
      {
       if(LPolygon->ITriangles==NULL) LNumOfTriangles++;
       else LNumOfTriangles+=LPolygon->NumOfTriangles;
      }

      if((LPolygonsR=E3d_PolygonsAllocate(LNumOfTriangles))!=NULL)
      {
       for(LC=0, LPolygon=LPolyGroup->Polygons, LPolygonR=LPolygonsR;LC<LPolyNum;LC++, LPolygon++)
       {
	if(LPolygon->ITriangles==NULL)
	{
	 memcpy(LPolygonR, LPolygon, sizeof(E3dPolygon));LPolygonR++;
	}
	else
	{
	 for(LC1=0, LITriangle=LPolygon->ITriangles, LN=LPolygon->NumOfTriangles; LC1<LN; LC1++, LITriangle++)
	 {
	  memcpy(LPolygonR, LPolygon, sizeof(E3dPolygon));
	  if((LVertexNodes=E3d_PolygonVertexNodesAllocate(LPolygonR, 3))!=NULL)
	  {
	   LPolygonR->VertexNodes=LVertexNodes;

	   memcpy(LVertexNodes, LITriangle->VertexNodes[0], sizeof(E3dVertexNode));
	   memcpy(LVertexNodes+1, LITriangle->VertexNodes[1], sizeof(E3dVertexNode));
	   memcpy(LVertexNodes+2, LITriangle->VertexNodes[2], sizeof(E3dVertexNode));

	   LPolygonR->NumOfVertices=LPolygonR->NumOfVertexNodes=LPolygonR->NumOfExteriorVertices=3;
	   LPolygonR->ITriangles=NULL;LPolygonR->NumOfTriangles=0;
	  }
	  LPolygonR++;
	 }
	 EFree(LPolygon->ITriangles);
	}
       }
       EFree(LPolyGroup->Polygons);LPolyGroup->Polygons=LPolygonsR;LPolyGroup->NumOfPolygons=LNumOfTriangles;
      }
     }
    }
   }
  break;
 }

 return(TRUE);
}


//========================================================
// Add lock to a Geometry
//========================================================
void E3d_GeometryAddLock(E3dGeometry* LGeometry)
{
 LGeometry->LockCount+=1;
 if(LGeometry->LockCount==1) E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_LOCKUNLOCK);
}


//========================================================
// Remove lock from a Geometry
//========================================================
void E3d_GeometryDelLock(E3dGeometry* LGeometry)
{
 LGeometry->LockCount-=1;
 if(LGeometry->LockCount==0) E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_LOCKUNLOCK);
}


#ifdef USEOpenGL
//========================================================
// Remove cached OpenGL display lists from a Geometry
//========================================================
void E3d_GeometryRemoveGLDisplayLists(E3dGeometry* LGeometry)
{
 switch(LGeometry->GeoType)
 {
  caseE3dMESH():
   {
    E3dMesh*	LMesh=(E3dMesh*)LGeometry;

    if(LMesh->GLDisplayListShaded) { glDeleteLists(LMesh->GLDisplayListShaded, 1);LMesh->GLDisplayListShaded=0; }
    if(LMesh->GLDisplayListShaded2DTextured) { glDeleteLists(LMesh->GLDisplayListShaded2DTextured, 1);LMesh->GLDisplayListShaded2DTextured=0; }
   }
  break;
 }
}
#endif // USEOpenGL


//================================================
// Free the contents of a ShapeSnapshot
//================================================
void E3d_ShapeSnapshotFree(E3dShapeSnapshot* LShapeSnapshot)
{
 if(LShapeSnapshot->Geometry)
 {
  switch(LShapeSnapshot->Geometry->GeoType)
  {
   caseE3dMESH():
    EFree(((E3dMeshVerticesSnapshot*)LShapeSnapshot)->SnapshotVertices);
   break;

   case E3dGEO_SPLINE:
    EFree(((E3dSplineCVsSnapshot*)LShapeSnapshot)->SnapshotCVs);
   break;

   case E3dGEO_FACE:
    {
     E3dSplineCVsSnapshot*	LSplineCVsSnapshot=((E3dFaceCVsSnapshot*)LShapeSnapshot)->SplineCVsSnapshots;
     unsigned int		LC, LN=((E3dFaceCVsSnapshot*)LShapeSnapshot)->NumOfSplineCVsSnapshots;

     for(LC=0;LC<LN;LC++, LSplineCVsSnapshot++) EFree(LSplineCVsSnapshot->SnapshotCVs);

     EFree(((E3dFaceCVsSnapshot*)LShapeSnapshot)->SplineCVsSnapshots);
    }
   break;
  }

  E3d_GeometryFree(LShapeSnapshot->Geometry);
 }
}
