/*======================================================================*/
/* 3DLib								*/
/*									*/
/* Scene-related functions, global variables etc.			*/
/*									*/
/* AUTHOR:	Gabor Nagy						*/
/* DATE:	1996-Jan-03 23:31:08					*/
/*									*/
/* 3DLib(TM) Copyright (C) 1995 by Gabor Nagy. All rights reserved.	*/
/*======================================================================*/
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <assert.h>

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

#include <Image/Image.h>

#include <E3D/E3D.h>

#ifndef _E3DFace_h
#include <E3D/Face.h>
#endif

#ifndef _E3DAnimation_h
#include <E3D/Animation.h>
#endif

#ifndef _E3DEMacros_h
#include <E3D/EMacros.h>
#endif

#ifndef _E3DMaterial_h
#include <E3D/Material.h>
#endif

#ifndef _E3DMath_h
#include <E3D/Math.h>
#endif

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

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

#ifndef _E3DModel_h
#include <E3D/Model.h>
#endif

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

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

#ifndef _E3DShape_h
#include <E3D/Shape.h>
#endif

#ifndef _E3DSpline_h
#include <E3D/Spline.h>
#endif




//----------------------------------------
// From Matrix.c
//----------------------------------------
extern int		E3d_MatrixPtr, E3d_MatrixScalePtr;

extern E3dCoordinate	E3d_ActScaleX, E3d_ActScaleY, E3d_ActScaleZ;
extern E3d3DPosition	E3d_ScaleStack[E3dMAX_HIERARCHY_DEPTH];
extern E3dMatrix	E3d_MatrixStack[E3dMAX_HIERARCHY_DEPTH];


//----------------------------------------
// From E3D/Texture.c
//----------------------------------------
extern EpImage*	E3d_NotFound2DTextureGLImage;
extern GLuint	E3d_NotFound2DTextureGLIndex;


//----------------------------------------
// From 3DPanel.c
//----------------------------------------
#ifdef _E3dGUI
extern E3dMesh*	E3d_BulbMesh;
#endif // _E3dGUI


//----------------------------------------
// Scene
//----------------------------------------
E3dScene*	E3d_Scene=NULL;

unsigned int	E3d_MatOffsetModels=0;

EBool		E3d_Verbose=TRUE;

E3d2DTexture*	E_Current2DGLTexture;



// Possible responses from E3d_Scene2DTextureGetGLImage()
//
enum
{
 E3dGLIMG_SUCCESS=0,
 E3dGLIMG_ERROR,
 E3dGLIMG_SET
};


#ifdef USEOpenGL
int	E3dGL_2DTextureSizes[] = { 16, 32, 64, 128, 256, 512, 1024, 2048, 0 };		// List must end with a 0
EBool	E3d_GLDataTransformed=FALSE;
#endif // USEOpenGL


//================================================================================
// Get RootModel and Node index for a Model in a Scene
//================================================================================
EBool E3d_SceneGetModelIndices(E3dScene* LScene, E3dModel* LModel, int* LRootModelIndexRet, int* LNodeIndexRet)
{
 E3dModel*	LRootModel;

 LRootModel=LModel;
 while(LRootModel->Parent) LRootModel=LRootModel->Parent;

 if((*LRootModelIndexRet=ELst_GetIndexOfPointer((void**)(LScene->RootModels), LScene->NumOfRootModels, LRootModel))==-1) return(FALSE);

 {
  E3dModel*	LTModel=LModel;
  int		LC;

  for(LC=0;LTModel;LC++, LTModel=LTModel->Next)
  {
   if(LTModel==LTModel) { *LNodeIndexRet=LC;return(TRUE); }
  }
 }

 return(FALSE);
}


//================================================================================
// Initialize a 3D Scene
//
// Argument
//  E3dScene* LScene           Pointer to the Scene structure
//
// Description
//  This function initializes the given E3dScene structure.
//
// See also
//  E3d_SceneAllocate, E3d_SceneFree
//================================================================================
void E3d_SceneDefault(E3dScene* LScene)
{
 E3dRenderInfo*	LRenderInfo=&(LScene->RenderInfo);

 LScene->Name=NULL;
 E3d_RenderInfoDefault(LRenderInfo);
 LScene->RootModels=NULL;
 LScene->NumOfRootModels=LScene->NumOfRootModelsAllocated=0;
 LScene->Materials=NULL;
 LScene->NumOfMaterials=LScene->NumOfMaterialsAllocated=0;

 LScene->Textures2D=NULL;
 LScene->NumOf2DTextures=LScene->NumOf2DTexturesAllocated=0;

 LScene->Lights=NULL;
 LScene->NumOfLights=0;LScene->NumOfLightsAllocated=0;

 LScene->Frame=0.0;LScene->Time=0.0;
 LScene->Animations=NULL;LScene->NumOfAnimations=0;LScene->NumOfAnimationsAllocated=0;


// Callbacks
//
 LScene->NumOfCallbacks=0;
 LScene->Callbacks=NULL;

 LScene->NumOfRenderPolyGroups=0;
 LScene->RenderPolyGroups=NULL;

 LScene->NumOfSelectCallbacks=0;
 LScene->SelectCallbacks=NULL;

 LScene->NumOfAddRemoveCallbacks=0;
 LScene->AddRemoveCallbacks=NULL;


// Uniform grid
//
 LScene->Voxels=NULL;
 LScene->GridXCount=LRenderInfo->GridXCount;
 LScene->GridYCount=LRenderInfo->GridYCount;
 LScene->GridZCount=LRenderInfo->GridZCount;
}


//================================================================================
// Allocate and initialize a 3D Scene
//
// Argument
//  char* LName            The name of the new Scene
//
// Description
//  This function allocates memory for and initializes an E3dScene structure.
//
// Return value
//  Pointer to the allocated E3dScene structure or NULL in case of an error.
//
// See also
//  E3d_SceneFree
//================================================================================
E3dScene* E3d_SceneAllocate(char* LName)
{
 E3dScene*	LScene;

 if((LScene=(E3dScene*)EMalloc(sizeof(E3dScene)))!=NULL)
 {
  E3d_SceneDefault(LScene);
  LScene->Name=EStrDup(LName);
 }

 return(LScene);
}


//========================================================================================
// Free a Scene
//
// Arguments
//  E3dScene*  LScene      Pointer to the E3dScene structure to free
//
// description
//  If RefCnt is zero, E3d_SceneFree() frees all the Geometries and other
//  dynamically allocated structures of the Model and the Model structure itself.
//
// See also
//  E3d_SceneAllocate
//========================================================================================
void E3d_SceneFree(E3dScene* LScene)
{
 unsigned int	LC, LN;


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

 E3d_SceneFreeRenderTriangles(LScene);

 LN=LScene->NumOfLights;
 if(LN)
 {
  for(LC=0;LC<LN;LC++) E3d_LightFree(LScene->Lights[LC]);
  EFree(LScene->Lights);
 }

 EFree(LScene);
}


//========================================
// RemoveProc of Light Models
//========================================
int E3d_SceneLightModelRemoveProc(E3dModel* LModel, E3dModelInfo* LInfo)
{
 E3dLightInfo*	LLightInfo=(E3dLightInfo*)LInfo;

 E3d_SceneRemoveLight(E3d_Scene, LLightInfo->Light);
 return(0);
}


//========================================
// DestroyProc of Light Models
//========================================
int E3d_SceneLightModelDestroyProc(E3dModel* LModel, E3dModelInfo* LInfo)
{
 E3dLightInfo*	LLightInfo=(E3dLightInfo*)LInfo;

// We have to set this to NULL before calling E3d_LightFree(),
// so we won't get into an infinite loop
//
 LLightInfo->Light->Model=NULL;
 E3d_LightFree(LLightInfo->Light);
 return(0);
}


//========================================
// RemoveProc of Camera Models
//========================================
void E3d_SceneCameraModelRemoveProc(E3dModel* LModel, E3dModelInfo* LInfo)
{
// E3dCameraInfo*	LCameraInfo=(E3dCameraInfo*)LInfo;

// E3d_SceneRemoveCamera(E3d_Scene, LCameraInfo->Camera);
}


//========================================
// DestroyProc of Camera Models
//========================================
void E3d_SceneCameraModelDestroyProc(E3dModel* LModel, E3dModelInfo* LInfo)
{
 E3dCameraInfo*	LCameraInfo=(E3dCameraInfo*)LInfo;

// We have to set this to NULL before calling E3d_CameraFree(),
// so we won't get into an infinite loop
//
 LCameraInfo->Camera->Model=NULL;
 E3d_CameraFree(LCameraInfo->Camera);
}


//========================================
// Initialize 3D=
//========================================
void E3d_3DInit()
{
 E3dMaterial*	LMaterial;

// Initialize the default Material
//
 LMaterial=&E3d_DefaultMaterial;
 E3d_MaterialDefault(LMaterial);
 LMaterial->Name=EStrDup("Default");
 LMaterial->Specular.R=0.2;LMaterial->Specular.G=0.5;LMaterial->Specular.B=0.7;
#ifdef USEOpenGL
 E3d_MaterialConvertToGL(LMaterial);
#endif // USEOpenGL

// Initialize the default Material used for textured objects
//
 LMaterial=&E3d_PlasterMaterial;
 E3d_MaterialDefault(LMaterial);
 LMaterial->Ambient.R=0.2;LMaterial->Ambient.G=0.2;LMaterial->Ambient.B=0.2;
 LMaterial->Diffuse.R=0.9;LMaterial->Diffuse.G=0.9;LMaterial->Diffuse.B=0.9;
 LMaterial->Specular.R=0.7;LMaterial->Specular.G=0.7;LMaterial->Specular.B=0.7;
#ifdef USEOpenGL
 E3d_MaterialConvertToGL(LMaterial);
#endif // USEOpenGL

#ifdef _E3dGUI
// Initialize the "Light source" ModelClass
//
// Note: The EditProc will be defined in the Lights plugin
//
 {
  E3dModelClass		LModelClass=
    {
     "Light source",			// Name
     sizeof(E3dLightInfo),		// StructSize
     NULL,				// EditProc
     E3d_SceneLightModelRemoveProc,	// RemoveProc
     E3d_SceneLightModelDestroyProc,	// DestroyProc
     NULL				// Resources
    };

  if((E3d_LightModelInfo.Class=E3d_ModelClassRegister(&LModelClass))!=NULL)
  {
   E3d_LightModelInfo.Light=NULL;
  }
 }

// Initialize the "Camera" ModelClass
//
// Note: The EditProc will be defined in the Camera plugin
//
 {
  E3dModelClass		LModelClass=
    {
     "Camera",				// Name
     sizeof(E3dCameraInfo),		// StructSize
     NULL,				// EditProc
     NULL,				// RemoveProc
     NULL,				// DestroyProc
     NULL				// Resources
    };

  if((E3d_CameraModelInfo.Class=E3d_ModelClassRegister(&LModelClass))!=NULL)
  {
   E3d_CameraModelInfo.Camera=NULL;
  }
 }
#endif	// _E3dGUI

 E3d_MatrixPtr=E3dMAX_HIERARCHY_DEPTH-1;
 E3d_MatrixScalePtr=E3dMAX_HIERARCHY_DEPTH-1;
 E3d_ActScaleX=1.0;
 E3d_ActScaleY=1.0;
 E3d_ActScaleZ=1.0;
}


//========================================
// Free 3D library
//========================================
void E3d_Free()
{
 E3dMaterial**	LMaterials; 
 E3d2DTexture**	L2DTextures; 
 unsigned int	LC, LNumOfMaterials, LNumOf2DTextures;


 LMaterials=E3d_Scene->Materials;
 LNumOfMaterials=E3d_Scene->NumOfMaterials;

// Free Materials in E3d_Scene
//
 if(LMaterials)
 {
  E3dMaterial*	LMaterial; 

  for(LC=0;LC<LNumOfMaterials;LC++)
  {
   LMaterial=LMaterials[LC];
   E3d_MaterialFree(LMaterial);
  }
  EFree(LMaterials);E3d_Scene->Materials=NULL;E3d_Scene->NumOfMaterials=0;E3d_Scene->NumOfMaterialsAllocated=0;
 }

 L2DTextures=E3d_Scene->Textures2D;
 LNumOf2DTextures=E3d_Scene->NumOf2DTextures;
// Free 2DTextures in E3d_Scene
//
 if(L2DTextures)
 {
  E3d2DTexture*	L2DTexture; 

  for(LC=0;LC<LNumOf2DTextures;LC++)
  {
   L2DTexture=L2DTextures[LC];
   E3d_2DTextureFree(L2DTexture);
  }
  EFree(L2DTextures);E3d_Scene->Textures2D=NULL;E3d_Scene->NumOf2DTextures=0;E3d_Scene->NumOf2DTexturesAllocated=0;
 }

// if(E3d_CustomModelTypes) { EFree(E3d_CustomModelTypes);E3d_CustomModelTypes=NULL;E3d_NumOfCustomModelTypes=0; }
}


//========================================
// Clear Scene
//========================================
void E3d_ClearScene(E3dScene* LScene)
{
 unsigned int	LC, LN;

 if((LN=LScene->NumOfRootModels)>0)
 {
  E3dModel*	LRootModel;

  for(LC=0;LC<LScene->NumOfRootModels;)
  {
   LRootModel=LScene->RootModels[LC];

   switch(LRootModel->Type)
   {
    case E3dMDL_CAMERA:
     LC++;
    break;

    default:
     E3d_SceneRemoveModelHrc(LScene, LRootModel);
     E3d_ModelHrcFree(LRootModel, TRUE);
    break;
   }
  }
 }

// Remove and free Materials
//
 {
  E3dMaterial*	LMaterial;

  for(LC=0;LC<LScene->NumOfMaterials;)
  {
   LMaterial=LScene->Materials[LC];

printf("MatFree %s\n", LMaterial->Name);fflush(stdout);
   E3d_SceneRemoveMaterial(LScene, LMaterial);
   E3d_MaterialFree(LMaterial);
  }
 }
}



#ifdef USEOpenGL


//========================================================================================
// Remove all OpenGL display-lists in the Scene to force their full re-generation
//========================================================================================
void E3d_SceneRemoveGLDisplayLists()
{
 E3dModel**		LRootModels;
 E3dModel*		LModel;
 E3dGeometry**		LGeometries;
 unsigned int		LNumOfRootModels,
			LModelCnt, LGmN, LGmC;


 LNumOfRootModels=E3d_Scene->NumOfRootModels;LRootModels=E3d_Scene->RootModels;
 for(LModelCnt=0;LModelCnt<LNumOfRootModels;LModelCnt++)
 {
  for(LModel=LRootModels[LModelCnt];LModel;)
  {
   LGmN=LModel->NumOfGeometries;
   LGeometries=LModel->Geometries;

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

   LModel=LModel->Next;
  }
 }
}


//========================================
// Convert lights to GL format
//========================================
void E3d_SceneLightsRefreshGL(E3dScene* LScene)
{
 E3dLight*	LLight;
 E3dGLLight*	LGLLight;
 unsigned int	LC, LN=LScene->NumOfLights;


 for(LC=0;LC<LN;LC++)
 {
  LLight=LScene->Lights[LC];
  LGLLight=LScene->GLLights+LC;

  if(LC==0)
  {
   LGLLight->Ambient[0]=1.0;
   LGLLight->Ambient[1]=1.0;
   LGLLight->Ambient[2]=1.0;
   LGLLight->Ambient[3]=1.0;
  }
  else
  {
   LGLLight->Ambient[0]=0.0;
   LGLLight->Ambient[1]=0.0;
   LGLLight->Ambient[2]=0.0;
   LGLLight->Ambient[3]=0.0;
  }

  LGLLight->Diffuse[0]=LLight->Color.R;
  LGLLight->Diffuse[1]=LLight->Color.G;
  LGLLight->Diffuse[2]=LLight->Color.B;
  LGLLight->Diffuse[3]=0.0;
  LGLLight->Specular[0]=LLight->Color.R;
  LGLLight->Specular[1]=LLight->Color.G;
  LGLLight->Specular[2]=LLight->Color.B;
  LGLLight->Specular[3]=0.0;

  LGLLight->Position[0]=LLight->Position.X;
  LGLLight->Position[1]=LLight->Position.Y;
  LGLLight->Position[2]=LLight->Position.Z;
  LGLLight->QuadraticAttenuation=LLight->QuadraticAttenuation;
  switch(LLight->Type)
  {
   case E3dLIGHT_POINT:
   case E3dLIGHT_SPOT:
    LGLLight->Position[3]=1.0;
   break;

   case E3dLIGHT_INFINITE:
   case E3dLIGHT_SUN:
    LGLLight->Position[3]=0.0;
   break;
  }
 }
}


//========================================
// Define GL lights
//========================================
void E3d_SceneLightsDefineGL(E3dScene* LScene)
{
 E3dGLLight*	LGLLight;
 unsigned int	LC, LNumOfLights, LLightID;


 LNumOfLights=LScene->NumOfLights;if(LNumOfLights>GL_MAX_LIGHTS) LNumOfLights=GL_MAX_LIGHTS;

 if(LNumOfLights)
 {
  LGLLight=LScene->GLLights;
  LLightID=GL_LIGHT0;

  glLightfv(LLightID, GL_POSITION, LGLLight->Position);

// Only add ambient to the 0th light
//
  glLightfv(LLightID, GL_AMBIENT, LGLLight->Ambient);
  glLightfv(LLightID, GL_DIFFUSE, LGLLight->Diffuse);
  glLightfv(LLightID, GL_SPECULAR, LGLLight->Specular);
  glLightf(LLightID, GL_QUADRATIC_ATTENUATION, LGLLight->QuadraticAttenuation);
  LGLLight++;LLightID++;

  for(LC=1;LC<LNumOfLights;LC++, LGLLight++, LLightID++)
  {
   glLightfv(LLightID, GL_POSITION, LGLLight->Position);
   glLightfv(LLightID, GL_DIFFUSE, LGLLight->Diffuse);
   glLightfv(LLightID, GL_SPECULAR, LGLLight->Specular);
   glLightf(LLightID, GL_QUADRATIC_ATTENUATION, LGLLight->QuadraticAttenuation);
  }
 }
}
#endif // USEOpenGL


//========================================
// Refresh lights for a Scene
//========================================
void E3d_SceneRefreshLights(E3dScene* LScene, E3dMatrix LCameraTransMatrix)
{
 E3dModel*	LModel=NULL;
 E3dLight*	LLight;
 E3dCoordinate	mX, mY, mZ;
 unsigned int	LC, LN=LScene->NumOfLights;


 for(LC=0;LC<LN;LC++)
 {
  LLight=LScene->Lights[LC];

// Update camera-transformed position
//
  mX=LLight->Position.X;mY=LLight->Position.Y;mZ=LLight->Position.Z;
  E3dM_MatrixTransform3x4(LCameraTransMatrix, LLight->CamTransPosition.X, LLight->CamTransPosition.Y, LLight->CamTransPosition.Z);

// Update pseudo-Model of the Light
//
  if((LModel=LLight->Model)!=NULL)
  {
   E3d_3DPositionCopy(&(LLight->Position), &(LModel->Translation));
   E3d_ModelHrcRefreshMatrices(LModel);
  }
 }
#ifdef USEOpenGL
 E3d_SceneLightsRefreshGL(LScene);
#endif // USEOpenGL
}


//================================================
// Add a light source to the given Scene
//================================================
void E3d_SceneAppendLight(E3dScene* LScene, E3dLight* LLight)
{
 E3dModel*	LModel=NULL;
 E3dLightInfo*	LLightInfo;
 unsigned int	LN;


#ifdef _E3dGUI
 if((LModel=LLight->Model)==NULL)
 {
   if((LModel=E3d_ModelAllocate("Light"))!=NULL)
   {
     LModel->Type=E3dMDL_LIGHT;
     LLight->Model=LModel;

     LLightInfo=(E3dLightInfo*)E3d_ModelInfoAdd(LModel, E3d_LightModelInfo.Class);
     if(LLightInfo)
     {
      memcpy(LLightInfo, &E3d_LightModelInfo, sizeof(E3dLightInfo));
      LLightInfo->Light=LLight;LLight->RefCnt+=1;
     }
   }
 }

 if(LModel)
 {
   switch(LLight->Type)
   {
    case E3dLIGHT_POINT:
     if(E3d_BulbMesh)
     {
       if(LModel->Geometries==NULL) E3d_ModelAppendGeometry(LModel, (E3dGeometry*)E3d_BulbMesh);
       else
       {
	 if(LModel->Geometries[0]!=(E3dGeometry*)E3d_BulbMesh)
	 {
	   E3d_GeometryFree(LModel->Geometries[0]);
	   LModel->Geometries[0]=(E3dGeometry*)E3d_BulbMesh;E3d_BulbMesh->RefCnt+=1;
	 }
       }
     }
    break;

    default:
    break;
   }
 }

#endif // _E3dGUI

 
 LN=LScene->NumOfLights;
 if(ELst_AddPointerAChk((void***)(&(LScene->Lights)), &(LScene->NumOfLights), &(LScene->NumOfLightsAllocated), 4, LLight))
 {
  LLight->RefCnt+=1;

#ifdef USEOpenGL
  E3d_SceneLightsRefreshGL(LScene);
#endif // USEOpenGL
 }
}


//================================================
// Create new Light and add it to the Scene
//================================================
E3dLight* E3d_SceneCreateAndAddLight(E3dScene* LScene, int LType)
{
 E3dLight*	LLight=E3d_LightAllocate();


 if(LLight)
 {
  E3dModel*	LModel;

  E3d_SceneAppendLight(LScene, LLight);
  if((LModel=LLight->Model)!=NULL)
  {
   E3d_3DPositionCopy(&(LLight->Position), &(LModel->Translation));
   E3d_ModelHrcRefreshMatrices(LModel);
   E3d_SceneAddModelHrc(E3d_Scene, LModel);
  }

  return(LLight);
 }
 return(NULL);
}



//================================================
// Remove a Light source from a Scene
//================================================
void E3d_SceneRemoveLight(E3dScene* LScene, E3dLight* LLight)
{
 if(ELst_RemovePointerA((void***)(&(LScene->Lights)), &(LScene->NumOfLights), &(LScene->NumOfLightsAllocated), LLight))
 {
  LLight->RefCnt-=1;
 }
#ifdef USEOpenGL
 E3d_SceneLightsRefreshGL(LScene);
#endif // USEOpenGL
}


//===============================================================================
// Add a Model-hierarchy to a Scene
//
// Arguments
//  E3dScene* LScene           Pointer to the Scene structure
//  E3dModel* LRootModel       Pointer to the Root node of the new hierarchy
//
// Description
//  Checks if the given hierarchy is in the given Scene. If not, adds it to the
//  array: Scene->RootModels.
//
// See also
//  E3d_SceneRemoveModelHrc
//================================================================================
EBool E3d_SceneAddModelHrc(E3dScene* LScene, E3dModel* LRootModel)
{
 if(LRootModel)
 {
  if(ELst_AddPointerAChk((void***)(&(LScene->RootModels)), &(LScene->NumOfRootModels), &(LScene->NumOfRootModelsAllocated), 4, (void*)LRootModel))
  {
   E3dSceneModelsCallbackStruct	LCBS;
   E3dModel*			LModel;
   E3dModel*			LModels[1];


// Increase RefCnt of all Models in the Hierarchy
//
   LModel=LRootModel;
   while(LModel) { LModel->RefCnt+=1;LModel=LModel->Next; }

// Add Materials of the Hierarchy to the Scene
//
//   E3d_SceneAddMaterialsFromModelHrc(LScene, LRootModel);

   LModels[0]=LRootModel;
   LCBS.Reason=E3dCR_ROOTMODEL_ADDED;
   LCBS.Models=LModels;
   LCBS.NumOfModels=1;
   E3d_CallCallbacks(LScene, LScene->AddRemoveCallbacks, LScene->NumOfAddRemoveCallbacks, &LCBS);
   return(TRUE);
  }
 }
 return(FALSE);
}


//================================================================================
// Remove Model-hierarchy from a Scene
//
// Arguments
//  E3dScene* LScene           Pointer to the Scene structure
//  E3dModel* LRootModel       Pointer to the Root of the hierarchy
//
// Description
//  Checks if the given Hierarchy is in the Scene (it's in RootModels array of
//  the Scene). If it is, it removes it from that array.
//
// See also
//  E3d_SceneAddModelHrc
//================================================================================
void E3d_SceneRemoveModelHrc(E3dScene* LScene, E3dModel* LRootModel)
{
 if(LScene->NumOfRootModels==0) return;

 if(ELst_RemovePointerA((void***)(&(LScene->RootModels)), &(LScene->NumOfRootModels), &(LScene->NumOfRootModelsAllocated), LRootModel))
 {
  E3dModel*			LModel;

// Decrease RefCnt of all Models in the Hierarchy
//
  LModel=LRootModel;
  while(LModel) { LModel->RefCnt-=1;LModel=LModel->Next; }

  E3d_ModelHrcCallRemoveProcs(LRootModel);


// FIXME
// If some Geometries in this Hierarchy are referenced from other Models still in the Scene, we
// shouldn't remove their RenderData...
//
  for(LModel=LRootModel;LModel;LModel=LModel->Next) E3d_ModelFreeRenderData(LModel);

  {
   E3dSceneModelsCallbackStruct	LCBS;
   E3dModel*			LModels[1];

   LModels[0]=LRootModel;
   LCBS.Reason=E3dCR_ROOTMODEL_REMOVED;
   LCBS.Models=LModels;
   LCBS.NumOfModels=1;
   E3d_CallCallbacks(LScene, LScene->AddRemoveCallbacks, LScene->NumOfAddRemoveCallbacks, &LCBS);
  }
 }
}


//================================================
// Add a Material to a Scene
//================================================
EBool E3d_SceneAddMaterial(E3dScene* LScene, E3dMaterial* LMaterial)
{
 if((LMaterial!=NULL)&&(LMaterial!=(&E3d_DefaultMaterial)))
 {
  if(ELst_AddPointerAChk((void***)(&(LScene->Materials)), &(LScene->NumOfMaterials), &(LScene->NumOfMaterialsAllocated), 4, (void*)LMaterial))
  {
   LMaterial->RefCnt+=1;

//printf("AddMtl [%s] %08x %d\n", LMaterial->Name, (unsigned int)(LMaterial), LMaterial->RefCnt);fflush(stdout);
//assert(LMaterial->RefCnt<4);

   if(LMaterial->Textures2D) E3d_SceneAdd2DTextures(LScene, LMaterial->Textures2D, LMaterial->NumOf2DTextures);


   {
    E3dSceneMaterialsCallbackStruct	LCBS;
    E3dMaterial*			LMaterials[1];

    LMaterials[0]=LMaterial;

    LCBS.Reason=E3dCR_MATERIAL_ADDED;
    LCBS.Materials=LMaterials;
    LCBS.NumOfMaterials=1;
    E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
   }

   return(TRUE);
  }
 }
 return(FALSE);
}


//================================================
// Add an array of Materials to a Scene
//================================================
int E3d_SceneAddMaterials(E3dScene* LScene, E3dMaterial** LMaterials, unsigned int LCount)
{
 E3dMaterial*	LMaterial;
 E3dMaterial**	LNewMaterials=NULL;
 int		LMatsAdded=0;
 unsigned int	LMatCnt, LNumOfNewMaterials=0;


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

// Count Materials that are not yet in the Scene
//
 for(LMatCnt=0;LMatCnt<LCount;LMatCnt++)
 {
  if(ELst_GetIndexOfPointer((void**)(LScene->Materials), LScene->NumOfMaterials, LMaterials[LMatCnt])==-1)
  {
   ELst_AddPointer((void***)(&LNewMaterials), &LNumOfNewMaterials, LMaterials[LMatCnt]);
  }
 }
 if(LNumOfNewMaterials==0) return(0);

 if(LScene->Materials==NULL) { LScene->Materials=(E3dMaterial**)EMalloc(sizeof(E3dMaterial*)*LNumOfNewMaterials);LScene->NumOfMaterialsAllocated=LNumOfNewMaterials; }
 else
 {
  if((LScene->NumOfMaterials+LNumOfNewMaterials)>LScene->NumOfMaterialsAllocated)
  {
   E3dMaterial**	LMatP;

   if((LMatP=(E3dMaterial**)ERealloc(LScene->Materials, sizeof(E3dMaterial*)*(LScene->NumOfMaterials+LNumOfNewMaterials)))!=NULL)
   {
    LScene->Materials=LMatP;LScene->NumOfMaterialsAllocated+=LNumOfNewMaterials;
   }
  }
 }

 if(LScene->Materials)
 {
  unsigned int	LMatDCnt;

  for(LMatCnt=0, LMatDCnt=LScene->NumOfMaterials;LMatCnt<LNumOfNewMaterials;LMatCnt++, LMatDCnt++)
  {
   LMaterial=LNewMaterials[LMatCnt];

   LScene->Materials[LMatDCnt]=LMaterial;LMaterial->RefCnt+=1;
   LMatsAdded++;

   if(LMaterial->Textures2D) E3d_SceneAdd2DTextures(LScene, LMaterial->Textures2D, LMaterial->NumOf2DTextures);
  }


  {
   E3dSceneMaterialsCallbackStruct	LCBS;

   LCBS.Reason=E3dCR_MATERIAL_ADDED;
   LCBS.Materials=LNewMaterials;
   LCBS.NumOfMaterials=LNumOfNewMaterials;
   E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
  }

  LScene->NumOfMaterials+=LNumOfNewMaterials;

  if(LNewMaterials) EFree(LNewMaterials);

  return(LMatsAdded);
 }

 if(LNewMaterials) EFree(LNewMaterials);
 return(LMatsAdded);
}


//================================================
// Remove a Material from a Scene
//================================================
EBool E3d_SceneRemoveMaterial(E3dScene* LScene, E3dMaterial* LMaterial)
{
 if(LMaterial==NULL) return(FALSE);
 if(LMaterial==(&E3d_DefaultMaterial)) return(FALSE);
 if(LScene->NumOfMaterials==0) return(FALSE);

 if(ELst_RemovePointerA((void***)(&(LScene->Materials)), &(LScene->NumOfMaterials), &(LScene->NumOfMaterialsAllocated), LMaterial))
 {
  LMaterial->RefCnt-=1;

printf("RemMtl [%s] %08x %d\n", LMaterial->Name, (unsigned int)(LMaterial), LMaterial->RefCnt);fflush(stdout);
  if(LMaterial->Textures2D) E3d_SceneRemove2DTextures(LScene, LMaterial->Textures2D, LMaterial->NumOf2DTextures);



  {
   E3dSceneMaterialsCallbackStruct	LCBS;
   E3dMaterial*				LMaterials[1];

   LMaterials[0]=LMaterial;

   LCBS.Reason=E3dCR_MATERIAL_REMOVED;
   LCBS.Materials=LMaterials;
   LCBS.NumOfMaterials=1;
   E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
  }


  return(TRUE);
 }
 return(FALSE);
}


//================================================
// Remove an array of Materials from a Scene
//================================================
int E3d_SceneRemoveMaterials(E3dScene* LScene, E3dMaterial** LMaterials, unsigned int LCount)
{
 E3dMaterial*	LMaterial;
 E3dMaterial**	LRemovedMaterials=NULL;
 unsigned int	LC, LNumOfRemovedMaterials=0;


 for(LC=0;LC<LCount;LC++)
 {
  LMaterial=LMaterials[LC];

  if(ELst_RemovePointerA((void***)(&(LScene->Materials)), &(LScene->NumOfMaterials), &(LScene->NumOfMaterialsAllocated), LMaterial))
  {
// Collect Materials that are actually removed
//
   ELst_AddPointer((void***)(&LRemovedMaterials), &LNumOfRemovedMaterials, LMaterial);

//printf("RemMtls [%s] %p %d\n", LMaterial->Name, LMaterial, LMaterial->RefCnt);fflush(stdout);

   if(LMaterial->Textures2D) E3d_SceneRemove2DTextures(LScene, LMaterial->Textures2D, LMaterial->NumOf2DTextures);
  }
 }



 {
  E3dSceneMaterialsCallbackStruct	LCBS;


  LCBS.Reason=E3dCR_MATERIAL_REMOVED;
  LCBS.Materials=LRemovedMaterials;
  LCBS.NumOfMaterials=LNumOfRemovedMaterials;
  E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
 }

 if(LRemovedMaterials)
 {
  for(LC=0;LC<LNumOfRemovedMaterials;LC++)
  {
   E3d_MaterialFree(LRemovedMaterials[LC]);
  }

  EFree(LRemovedMaterials);
 }

 return(LNumOfRemovedMaterials);
}


//================================================================================
// Clone a Material
//
// Arguments
//  E3dMaterial* LMaterial     The Material to clone
//  E3dScene*    LScene        Scene for unique	Material names
//
// Description
//  Creates a duplicate of the given Material.
//  If the LScene argument is not NULL, this function makes sure that the name
//  of the new Material will be unique among the Materials in that Scene.
//  For example, if the name of LMaterial is "Iron" and LScene already has a
//  Material called "Iron", the new Material will be called "Iron-1".
//
// Return value
//  Pointer to the new Material, or NULL in case of an error.
//
// See also
//  E3d_MaterialFree, E3d_MaterialDefault
//================================================================================
E3dMaterial* E3d_SceneMaterialClone(E3dScene* LScene, E3dMaterial* LMaterial)
{ 
 E3dMaterial*	LNewMaterial=NULL;

 if(LMaterial)
 {
#ifdef E_MEMDEBUG
  E_MemName="Material by E3d_SceneMaterialClone()";
#endif // E_MEMDEBUG

  if((LNewMaterial=E3d_MaterialAllocate())!=NULL)
  {
   unsigned int	LC, LN=LScene->NumOfMaterials;
   char*	LName;

   if(LN)
   {
    char**	LNames=(char**)EMalloc(sizeof(char*)*LN);

    if(LNames)
    {
     for(LC=0;LC<LN;LC++)
     {
      LNames[LC]=LScene->Materials[LC]->Name;
     }
     LName=EStr_StringGetUnique(LMaterial->Name, LNames, LN);
     EFree(LNames);
    }
    else LName=EStrDup(LMaterial->Name);
   }
   else LName=EStrDup(LMaterial->Name);

   LNewMaterial->Name=LName;

   LNewMaterial->Type=LMaterial->Type;
   LNewMaterial->Shader=LMaterial->Shader;
   if(LNewMaterial->Shader) LNewMaterial->Shader->RefCnt+=1;
   LNewMaterial->Ambient=LMaterial->Ambient;
   LNewMaterial->Diffuse=LMaterial->Diffuse;
   LNewMaterial->Specular=LMaterial->Specular;
   LNewMaterial->Emission=LMaterial->Emission;
   LNewMaterial->Transparent=LMaterial->Transparent;
   LNewMaterial->Specularity=LMaterial->Specularity;
   LNewMaterial->Reflectivity=LMaterial->Reflectivity;
   LNewMaterial->Transparency=LMaterial->Transparency;
   LNewMaterial->RefractiveIndex=LMaterial->RefractiveIndex;
   LNewMaterial->Glow=LMaterial->Glow;

   if((LN=LMaterial->NumOf2DTextures)>0)
   {
    E3d2DTexture*	L2DTexture;

    if((LNewMaterial->Textures2D=(E3d2DTexture**)EMalloc(sizeof(E3d2DTexture*)*LN))!=NULL)
    {
     LNewMaterial->NumOf2DTextures=LN;
     for(LC=0;LC<LN;LC++)
     {
      LNewMaterial->Textures2D[LC]=L2DTexture=E3d_2DTextureClone(LMaterial->Textures2D[LC], LScene->Textures2D, LScene->NumOf2DTextures);
      if(L2DTexture)
      {
       L2DTexture->RefCnt+=1;
       E3d_SceneAdd2DTexture(LScene, L2DTexture);
      }
     }
    }
   }

/*
   if((LN=LMaterial->NumOf3DTextures)>0)
   {
    if((LNewMaterial->Textures3D=(E3d3DTexture**)EMalloc(sizeof(E3d3DTexture*)*LN))!=NULL)
    {
     LNewMaterial->NumOf3DTextures=LN;
     for(LC=0;LC<LN;LC++)
     {
      LNewMaterial->Textures3D[LC]=E3d_3DTextureClone(LMaterial->Textures3D[LC]);
      LNewMaterial->Textures3D[LC]->RefCnt+=1;
     }
    }
   }
*/


   if(LScene) E3d_SceneAddMaterial(LScene, LNewMaterial);

   E3d_MaterialUpdateForDisplay(LNewMaterial);

   return(LNewMaterial);
  }
 }
 return(NULL);
}


//================================================
// Add a 2DTexture to a Scene
//================================================
EBool E3d_SceneAdd2DTexture(E3dScene* LScene, E3d2DTexture* L2DTexture)
{
 if(L2DTexture)
 {
  if(ELst_AddPointerAChk((void***)(&(LScene->Textures2D)), &(LScene->NumOf2DTextures), &(LScene->NumOf2DTexturesAllocated), 4, (void*)L2DTexture))
  {
   L2DTexture->RefCnt+=1;

//printf("TxtAdd %08x [%s] %d\n", (unsigned int)L2DTexture, L2DTexture->FileName, L2DTexture->RefCnt);fflush(stdout);

/*
   {
    E3dSceneCallbackStruct	LCBS;

    LCBS.Reason=E3dCR_2DTEXTURE_ADDED;
    LCBS.Value=L2DTexture;
    E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
   }
*/

   return(TRUE);
  }
 }
 return(FALSE);
}


//================================================
// Add an array of 2DTextures to a Scene
//================================================
int E3d_SceneAdd2DTextures(E3dScene* LScene, E3d2DTexture** L2DTextures, unsigned int LCount)
{
 E3d2DTexture*	L2DTexture;
 unsigned int	LC, L2DTxtNum, L2DTexturesAdded=0;

 if(L2DTextures==NULL) return(L2DTexturesAdded);

 for(LC=0, L2DTxtNum=0;LC<LCount;LC++)
 {
  if(ELst_GetIndexOfPointer((void**)(LScene->Textures2D), LScene->NumOf2DTextures, L2DTextures[LC])==-1) L2DTxtNum++;
 }
 if(L2DTxtNum==0) return(FALSE);

 if(LScene->Textures2D==NULL) { LScene->Textures2D=(E3d2DTexture**)EMalloc(sizeof(E3d2DTexture*)*L2DTxtNum);LScene->NumOf2DTexturesAllocated=L2DTxtNum; }
 else
 {
  if((LScene->NumOf2DTextures+L2DTxtNum)>LScene->NumOf2DTexturesAllocated)
  {
   E3d2DTexture**	LT2DTextures=(E3d2DTexture**)ERealloc(LScene->Textures2D, sizeof(E3d2DTexture*)*(LScene->NumOf2DTextures+L2DTxtNum));

   if(LT2DTextures)
   {
    LScene->Textures2D=LT2DTextures;LScene->NumOf2DTexturesAllocated+=L2DTxtNum;
   }
  }
 }

 if(LScene->Textures2D)
 {
  unsigned int	LTxtDCnt;

  for(LC=0, LTxtDCnt=LScene->NumOf2DTextures;LC<LCount;LC++)
  {
   L2DTexture=L2DTextures[LC];
   if(ELst_GetIndexOfPointer((void**)(LScene->Textures2D), LScene->NumOf2DTextures, L2DTexture)==-1)
   {
    LScene->Textures2D[LTxtDCnt]=L2DTexture;L2DTexture->RefCnt+=1;

//assert(L2DTexture->RefCnt<3);
//printf("TxtsAdd %08x [%s] %d\n", (unsigned int)L2DTexture, L2DTexture->FileName, L2DTexture->RefCnt);fflush(stdout);


    LTxtDCnt++;
    LScene->NumOf2DTextures+=1;
    L2DTexturesAdded++;
   }
  }
//printf("L2DTxtNum %d, LCount %d\n", L2DTxtNum, LCount);fflush(stdout);
 }
 return(L2DTexturesAdded);
}


//================================================
// Remove a 2DTexture from a Scene
//================================================
EBool E3d_SceneRemove2DTexture(E3dScene* LScene, E3d2DTexture* L2DTexture)
{
 if(L2DTexture==NULL) return(FALSE);
 if(L2DTexture==(&E3d_Default2DTexture)) return(FALSE);

 if(ELst_RemovePointerA((void***)(&(LScene->Textures2D)), &(LScene->NumOf2DTextures), &(LScene->NumOf2DTexturesAllocated), L2DTexture))
 {
  L2DTexture->RefCnt-=1;

/*
  {
   E3dSceneCallbackStruct	LCBS;

   LCBS.Reason=E3dCR_2DTEXTURE_REMOVED;
   LCBS.Value=L2DTexture;
   E3d_CallCallbacks(LScene, LScene->Callbacks, LScene->NumOfCallbacks, &LCBS);
  }
*/

  return(TRUE);
 }
 return(FALSE);
}


//================================================
// Remove an array of 2DTextures from a Scene
//================================================
int E3d_SceneRemove2DTextures(E3dScene* LScene, E3d2DTexture** L2DTextures, unsigned int LCount)
{
 E3d2DTexture*	L2DTexture;
 unsigned int	LC, L2DTexturesRemoved=0;


 for(LC=0;LC<LCount;LC++)
 {
  L2DTexture=L2DTextures[LC];
  
  if(ELst_RemovePointerA((void***)(&(LScene->Textures2D)), &(LScene->NumOf2DTextures), &(LScene->NumOf2DTexturesAllocated), L2DTexture))
  {
   L2DTexture->RefCnt-=1;
   L2DTexturesRemoved++;
  }
 }
 return(L2DTexturesRemoved);
}


//========================================================
// Create image of a 2D texture for OpenGL
//========================================================
static int E3d_Scene2DTextureGetGLImage(E3dScene* LScene, E3d2DTexture* L2DTexture, EpImage** LGLImageRet)
{
 unsigned int	LSubXSize, LSubYSize, LXStart, LYStart, LXEnd, LYEnd;

 int		LTexXSize, LTexYSize, LNewXSize, LNewYSize, LDiff, LD;

 unsigned char	LComponents;
 E2DTexture*	L2DTextureT;
 EImage*	LImage;
 EImage*	LSubImage;
 EImage*	LGLImage=NULL;
 EImage*	LGLImageT;

 if((LImage=L2DTexture->Image)==NULL) return(E3dGLIMG_ERROR);

 LSubImage=NULL;

// Get size of the sub-image used in the texture
//
 if(L2DTexture->Flags&E3dTXTF_CROPPING)
 {
  if((L2DTexture->SCropMax>0)&&(L2DTexture->TCropMax>0))
  {
printf("Cropped\n");fflush(stdout);
E3d_Print2DTexture(L2DTexture);
   LXStart=L2DTexture->SCropMin;LYStart=L2DTexture->TCropMin;
   LXEnd=L2DTexture->SCropMax+1;LYEnd=L2DTexture->TCropMax+1;
   LSubXSize=LXEnd-LXStart;LSubYSize=LYEnd-LYStart;

printf("CropSize: %d-%d, %d-%d %dx%d\n", LXStart, LXEnd, LYStart, LYEnd, LSubXSize, LSubYSize);fflush(stdout);


   LComponents=L2DTexture->NumOfComponents;
   switch(LComponents)
   {
    case 1:									// 1 component: I0,I1,I2...
/*
     if((LSubXSize%4)!=0) LLineModulo=4-(LSubXSize%4);
     else LLineModulo=0;
     LGLTextureImageSize=(LSubXSize+LLineModulo)*LSubYSize;
     if((LGLTextureImage=L2DTexture->GLTextureImage=(unsigned char*)EMalloc(LGLTextureImageSize))!=NULL)
     {
      for(LYCnt=LYStart,LGLPixelOffset=0,LLineOffset=LYStart*LFullWidth;LYCnt<LYEnd;LYCnt++,LLineOffset+=LFullWidth)
      {
       for(LXCnt=LXStart;LXCnt<LXEnd;LXCnt++)
       {
        LPixelOffset=LXCnt+LLineOffset;
        LFR=LR[LPixelOffset];LFG=LG[LPixelOffset];LFB=LB[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=(LFR*EcLumaR+LFG*EcLumaG+LFB*EcLumaB);
       }
       LGLPixelOffset+=LLineModulo;
      }
     }
*/
    break;

// 3 components: B0,G0,R0,B1,G1,R1,B2,G2,R2... longword alignment at the end of the line
//
    case 3:
    case 4:
/*
     if(((LSubXSize*3)%4)!=0) LLineModulo=4-(LSubXSize*3)%4;
     else LLineModulo=0;
     LGLTextureImageSize=(LSubXSize*3+LLineModulo)*LSubYSize;
     if((LGLTextureImage=L2DTexture->GLTextureImage=(unsigned char*)EMalloc(LGLTextureImageSize))!=NULL)
     {
      for(LYCnt=LYStart,LGLPixelOffset=0,LLineOffset=LYStart*LFullWidth;LYCnt<LYEnd;LYCnt++,LLineOffset+=LFullWidth)
      {
       for(LXCnt=LXStart;LXCnt<LXEnd;LXCnt++)
       {
        LPixelOffset=LXCnt+LLineOffset;
        LGLTextureImage[LGLPixelOffset++]=LB[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=LG[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=LR[LPixelOffset];
       }
       LGLPixelOffset+=LLineModulo;
      }
     }
*/
    break;
   }

  }
 }

// No cropping, use input Image
//
 if(LSubImage==NULL) LSubImage=LImage;

//printf("GLIM %08x -> %08x  %08x\n", LImage, LSubImage, LSubImage->PixelFormats);fflush(stdout);

 if(LSubImage->PixelFormats!=0)
 {
  E2DTexture**	L2DTextures=LScene->Textures2D;
  unsigned int	LC, LN=LScene->NumOf2DTextures;

  LTexXSize=LSubImage->XSize;LTexYSize=LSubImage->YSize;

// Make sure the texture dimensions are acceptable by OpenGL (powers of 2)
//
  LNewXSize=0;LNewYSize=0;
  for(LC=0;E3dGL_2DTextureSizes[LC]!=0;LC++) if(LTexXSize==E3dGL_2DTextureSizes[LC]) { LNewXSize=LTexXSize;break; }
  for(LC=0;E3dGL_2DTextureSizes[LC]!=0;LC++) if(LTexYSize==E3dGL_2DTextureSizes[LC]) { LNewYSize=LTexYSize;break; }

  if(LNewXSize==0)
  {
   if(LTexXSize<E3dGL_2DTextureSizes[0]) LNewXSize=E3dGL_2DTextureSizes[0];
   else
   {
    LDiff=LTexXSize-E3dGL_2DTextureSizes[0];
    for(LC=1;E3dGL_2DTextureSizes[LC]!=0;LC++)
    {
     LD=LTexXSize-E3dGL_2DTextureSizes[LC];
     if(E3dM_ABS(LD)<E3dM_ABS(LDiff)) LDiff=LD;
    }
    LNewXSize=LTexXSize-LDiff;
   }
  }

  if(LNewYSize==0)
  {
   if(LTexYSize<E3dGL_2DTextureSizes[0]) LNewYSize=E3dGL_2DTextureSizes[0];
   else
   {
    LDiff=LTexYSize-E3dGL_2DTextureSizes[0];
    for(LC=1;E3dGL_2DTextureSizes[LC]!=0;LC++)
    {
     LD=LTexYSize-E3dGL_2DTextureSizes[LC];
     if(E3dM_ABS(LD)<E3dM_ABS(LDiff)) LDiff=LD;
    }
    LNewYSize=LTexYSize-LDiff;
   }
  }

//printf("GLIM-1 %08x -> %08x\n", LImage, LSubImage);fflush(stdout);

// See if this image file has already been loaded for another 2D Texture
// and initialized for OpenGL with the same cropping and repeat parameters
//
  for(LC=0;LC<LN;LC++)
  {
   L2DTextureT=L2DTextures[LC];
   if((L2DTextureT!=L2DTexture)&&(L2DTextureT->Image==LSubImage))
   {
    if((LGLImageT=L2DTextureT->GLImage)!=NULL)
    {
     if((LGLImageT->XSize==LNewXSize)&&(LGLImageT->YSize==LNewYSize)&&
	(L2DTextureT->SCropMin==L2DTexture->SCropMin)&&(L2DTextureT->SCropMax==L2DTexture->SCropMax)&&(L2DTextureT->TCropMin==L2DTexture->TCropMin)&&(L2DTextureT->TCropMax==L2DTexture->TCropMax)&&
//	(L2DTextureT->SCount==L2DTexture->SCount)&&(L2DTextureT->TCount==L2DTexture->TCount)&&
	(L2DTextureT->GLIndex!=E3dGL2DTxtNONE))
     {
      L2DTexture->GLImage=LGLImageT;LGLImageT->RefCnt+=1;

      L2DTexture->GLIndex=L2DTextureT->GLIndex;
//printf("GLIM-2 %08x -> %08x\n", LImage, LSubImage);fflush(stdout);
      *LGLImageRet=LGLImageT;
      return(E3dGLIMG_SET);
     }
    }
   }
  }


// No such image for OpenGL yet, check if we have to scale it for OpenGL
//
  if((LNewXSize!=LTexXSize)||(LNewYSize!=LTexYSize))
  {
// Texture dimensions are not powers of 2, we have to scale the texture for OpenGL
//
   if(LGLImage==NULL)
   {
    if((LGLImage=Ep_ImageAllocate(0, 0, 0))!=NULL)
    {
printf("%dx%d -> %dx%d\n", LTexXSize, LTexYSize, LNewXSize, LNewYSize);fflush(stdout);
     if(!Ep_ImageScale(LSubImage, LGLImage, LNewXSize, LNewYSize, EpFILTER_QUADRATIC, 0.5, FALSE)) { Ep_ImageFree(LGLImage, TRUE);LGLImage=NULL; }
     if(LSubImage!=LImage) Ep_ImageFree(LSubImage, TRUE);
    }
   }
  }
  else LGLImage=LSubImage;

  if(LGLImage)
  {
   if(L2DTexture->GLImage!=LGLImage)
   {
    if(L2DTexture->GLImage) Ep_ImageFree(L2DTexture->GLImage, TRUE);
    L2DTexture->GLImage=LGLImage;LGLImage->RefCnt+=1;
   }
  }
 }

 *LGLImageRet=LGLImage;
 if(LGLImage) return(E3dGLIMG_SUCCESS);
 else return(E3dGLIMG_ERROR);
}


//========================================================================
// Assign the default image to a 2DTexture
//========================================================================
void E3d_Scene2DTextureSetDefaultImage(E3dScene* LScene, E2DTexture* L2DTexture, EBool LSetUpForDisplay)
{
 EImage*	LImage=E_NotFound2DTextureImage;

 if(L2DTexture->Image!=LImage)
 {
  if(L2DTexture->Image) Ep_ImageFree(L2DTexture->Image, TRUE);
  L2DTexture->Image=LImage;LImage->RefCnt+=1;
  L2DTexture->XSize=LImage->XSize;L2DTexture->YSize=LImage->YSize;
 }

 if(LSetUpForDisplay)
 {
// E_NotFound2DTextureGLImage is created "on-demand", not in advance as E_NotFound2DTextureImage
//
#ifdef USEOpenGL
  if(E3d_NotFound2DTextureGLImage==NULL)
  {
   EImage*	LGLImage;

   if(E3d_Scene2DTextureGetGLImage(LScene, L2DTexture, &LGLImage)==E3dGLIMG_SUCCESS)
   {
    E3d_NotFound2DTextureGLImage=LGLImage;
    if(LGLImage->RGBA8Image)
    {
     if(L2DTexture->GLIndex==E3dGL2DTxtNONE) glGenTextures(1 ,&(L2DTexture->GLIndex));

     E3d_NotFound2DTextureGLIndex=L2DTexture->GLIndex;
printf("Default 2DTexture: %dx%d 0x%08x Added to OpenGL as: %d\n", LGLImage->XSize, LGLImage->YSize, (unsigned int)LGLImage->RGBA8Image, L2DTexture->GLIndex);fflush(stdout);

     EGL_2DTextureSetMapping(L2DTexture);
     glTexImage2D(GL_TEXTURE_2D, 0, 4, LGLImage->XSize, LGLImage->YSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, LGLImage->RGBA8Image);
    }
   }
  }


  if((E3d_NotFound2DTextureGLImage!=NULL)&&(L2DTexture->GLImage!=E3d_NotFound2DTextureGLImage))
  {
   if(L2DTexture->GLImage) Ep_ImageFree(L2DTexture->GLImage, TRUE);
   L2DTexture->GLImage=E3d_NotFound2DTextureGLImage;L2DTexture->GLImage->RefCnt+=1;
   if(E3d_NotFound2DTextureGLIndex!=E3dGL2DTxtNONE)
   {
    L2DTexture->GLIndex=E3d_NotFound2DTextureGLIndex;
   }
  }
#endif // USEOpenGL
 }
}


//========================================================================
// Read image file of a 2DTexture and convert it to OpenGL format
//========================================================================
EBool E3d_Scene2DTextureReadImage(E3dScene* LScene, E3d2DTexture* L2DTexture, char* LPath, EBool LForceReRead, EBool LSetUpForDisplay)
{
 char			LActualFileName[MAXPATHLEN+1];
 EImage*		LCImage;
 EImage*		LImage;
 EpImageIOFormat*	LFileFormat;
 int			LReadResult;
 int			LPadTextTo=96;
 char			LOutStr[1024];
 EBool			LRecreateGLImage=FALSE;


#ifdef USEOpenGL
 if((L2DTexture->GLIndex!=E3dGL2DTxtNONE)&&(!LForceReRead)) return(TRUE);
#endif	// USEOpenGL

 LOutStr[0]=0;

// If we got an override path, let's use it
//
 if(L2DTexture->FileName)
 {
  if(LPath)
  {
   char	LFName[MAXPATHLEN+1];

   EStr_GetFileName(L2DTexture->FileName, LFName, MAXPATHLEN);

   sprintf(LActualFileName, "%s/%s", LPath, LFName);
  }
  else strcpy(LActualFileName, L2DTexture->FileName);
 }
 else LActualFileName[0]='\0';

 if((!LForceReRead)&&(L2DTexture->Image!=NULL)&&(L2DTexture->Image!=E_NotFound2DTextureImage)) LImage=L2DTexture->Image;
 else
 {
// See if this image file has already been loaded for another texture
//
  LImage=NULL;

  if(LActualFileName[0]!='\0')
  {
   E2DTexture**	L2DTextures=LScene->Textures2D;
   unsigned int	LC, LN=LScene->NumOf2DTextures;

//printf("  cmp %s\n", LActualFileName);fflush(stdout);
   for(LC=0;LC<LN;LC++)
   {
    if(L2DTextures[LC]!=L2DTexture)
    {
     if((LCImage=L2DTextures[LC]->Image)!=NULL)
     {
      if(LCImage->FileName)
      {
//printf(" with %s\n", LCImage->FileName);fflush(stdout);
       if(strcmp(LActualFileName, LCImage->FileName)==0)
       {
// If the the file name is the same, assign this image to L2DTexture
//
	LImage=LCImage;

#ifdef USEOpenGL
	if(L2DTexture->TransparencyFactor>0.0) L2DTexture->GLAlphaFunction=GL_GREATER;
	else L2DTexture->GLAlphaFunction=GL_ALWAYS;
	L2DTexture->GLAlphaReference=1.0-L2DTexture->TransparencyFactor;
#endif	// USEOpenGL

if(E_Verbose) sprintf(LOutStr, " Reusing image:   %s (%dx%d)", LImage->FileName, LImage->XSize, LImage->YSize);


	if(L2DTexture->Image==E_NotFound2DTextureImage) E_NotFound2DTextureImage->RefCnt-=1;
	L2DTexture->Image=LImage;LImage->RefCnt+=1;
	L2DTexture->XSize=LImage->XSize;L2DTexture->YSize=LImage->YSize;
	LRecreateGLImage=TRUE;

	LForceReRead=FALSE;
	break;
       }
      }

     }
    }
   }
  }
  else	// 2DTexture had no FileName, use default 2DTexture
  {
   E3d_Scene2DTextureSetDefaultImage(LScene, L2DTexture, LSetUpForDisplay);
   return(FALSE);
  }
 }


// No Image was read for this Texture, or ForceReread is TRUE
//
 if((LImage==NULL)||LForceReRead)
 {
  EBool	LImageAllocated=FALSE;

  if(LImage==NULL) { LImage=Ep_ImageAllocate(0, 0, 0);LImageAllocated=TRUE; }

  if(LImage)
  {
if(E_Verbose) sprintf(LOutStr, "Reading texture: %s ", LActualFileName);fflush(stdout);
   LReadResult=Ep_ImageReadFromFile(LActualFileName, EpFileFormatAUTOMATIC, &LFileFormat, LImage);

   if(LReadResult==EIO_SUCCESS)
   {
#ifdef USEOpenGL
    if(L2DTexture->TransparencyFactor>0.0) L2DTexture->GLAlphaFunction=GL_GREATER;
    else L2DTexture->GLAlphaFunction=GL_ALWAYS;
    L2DTexture->GLAlphaReference=1.0-L2DTexture->TransparencyFactor;
#endif	// USEOpenGL

if(E_Verbose)
{
 sprintf(LActualFileName, "%sSize: %dx%d ", LOutStr, LImage->XSize, LImage->YSize);fflush(stdout);
 strcpy(LOutStr, LActualFileName);
}
    if(L2DTexture->Image!=LImage)
    {
     if(L2DTexture->Image) Ep_ImageFree(L2DTexture->Image, TRUE);
     L2DTexture->Image=LImage;LImage->RefCnt+=1;

#ifdef USEOpenGL
     L2DTexture->GLIndex=E3dGL2DTxtNONE;	// Set this, so (A) will generate a new GLIndex for this new Image
#endif	// USEOpenGL
    }
    L2DTexture->XSize=LImage->XSize;L2DTexture->YSize=LImage->YSize;
    LRecreateGLImage=TRUE;
   }
   else
   {
    if(LImageAllocated) Ep_ImageFree(LImage, TRUE);

if(E_Verbose)
{
 EStr_StringPadToLength(LOutStr, LPadTextTo);
 printf("%s", LOutStr);fflush(stdout);
 printf("%sCouldn't read file%s\n", ERedCode, EGreenCode);fflush(stdout);
}
    E3d_Scene2DTextureSetDefaultImage(LScene, L2DTexture, LSetUpForDisplay);

    return(FALSE);
   }
  }
 }


 if(LSetUpForDisplay&&(LImage!=NULL))
 {
#ifdef USEOpenGL
  EImage*	LGLImage;

  if(LRecreateGLImage)
  {
   switch(E3d_Scene2DTextureGetGLImage(LScene, L2DTexture, &LGLImage))
   {
    case E3dGLIMG_SUCCESS:
     if(LGLImage->RGBA8Image)
     {
// (A)
//
      if(L2DTexture->GLIndex==E3dGL2DTxtNONE)
      {
       glGenTextures(1 ,&(L2DTexture->GLIndex));
       glBindTexture(GL_TEXTURE_2D, L2DTexture->GLIndex);
       E_Current2DGLTexture=L2DTexture;

if(E_Verbose)
{
EStr_StringPadToLength(LOutStr, LPadTextTo);
printf("%s", LOutStr);fflush(stdout);
printf(" %dx%d Added to OpenGL as: %d\n", LGLImage->XSize, LGLImage->YSize, L2DTexture->GLIndex);fflush(stdout);
}
      }
      else
      {
       if(E_Current2DGLTexture!=L2DTexture) { glBindTexture(GL_TEXTURE_2D, L2DTexture->GLIndex);E_Current2DGLTexture=L2DTexture; }
      }

      EGL_2DTextureSetMapping(L2DTexture);
      glTexImage2D(GL_TEXTURE_2D, 0, 4, LGLImage->XSize, LGLImage->YSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, LGLImage->RGBA8Image);

      return(TRUE);
     }
    break;

    case E3dGLIMG_ERROR:	return(FALSE);

    case E3dGLIMG_SET:
if(E_Verbose)
{
EStr_StringPadToLength(LOutStr, LPadTextTo);
printf("%s", LOutStr);fflush(stdout);
printf(" %dx%d Reusing OpenGL texture: %d\n", LGLImage->XSize, LGLImage->YSize, L2DTexture->GLIndex);fflush(stdout);
}

     return(TRUE);
   }
  }
  else
  {
   LGLImage=L2DTexture->GLImage;

   if(E_Verbose)
   {
    if(LGLImage)
    {
     EStr_StringPadToLength(LOutStr, LPadTextTo);
     printf("%s", LOutStr);fflush(stdout);
     printf(" %dx%d Reusing OpenGL texture: %d\n", LGLImage->XSize, LGLImage->YSize, L2DTexture->GLIndex);fflush(stdout);
    }
   }
  }

#endif // USEOpenGL
 }
 else
 {
  if(E_Verbose&&(LOutStr[0]!=0))
  {
   printf("%s\n", LOutStr);fflush(stdout);
  }
  return(TRUE);
 }

 if(E_Verbose&&(LOutStr[0]!=0))
 {
  printf("%s\n", LOutStr);fflush(stdout);
 }
 return(FALSE);
}


#ifdef USEOpenGL
//========================================================
// Create image of a 2D texture for OpenGL
//========================================================
static int E3d_Scene2DTextureGetGLImageDyn(E3dScene* LScene, E3d2DTexture* L2DTexture, EImage** LGLImageRet)
{
 unsigned int	LSubXSize, LSubYSize, LXStart, LYStart, LXEnd, LYEnd;

 int		LTexXSize, LTexYSize, LNewXSize, LNewYSize, LDiff, LD;

 unsigned char	LComponents;
 EImage*	LImage;
 EImage*	LSubImage;
 EImage*	LGLImage=NULL;

 if((LImage=L2DTexture->Image)==NULL) return(E3dGLIMG_ERROR);

 LSubImage=NULL;

// Get size of the sub-image used in the texture
//
 if(L2DTexture->Flags&E3dTXTF_CROPPING)
 {
  if((L2DTexture->SCropMax>0)&&(L2DTexture->TCropMax>0))
  {
printf("Cropped\n");fflush(stdout);
//E3d_Print2DTexture(L2DTexture);
   LXStart=L2DTexture->SCropMin;LYStart=L2DTexture->TCropMin;
   LXEnd=L2DTexture->SCropMax+1;LYEnd=L2DTexture->TCropMax+1;
   LSubXSize=LXEnd-LXStart;LSubYSize=LYEnd-LYStart;

printf("CropSize: %d-%d, %d-%d %dx%d\n", LXStart, LXEnd, LYStart, LYEnd, LSubXSize, LSubYSize);fflush(stdout);


   LComponents=L2DTexture->NumOfComponents;
   switch(LComponents)
   {
    case 1:									// 1 component: I0,I1,I2...
/*
     if((LSubXSize%4)!=0) LLineModulo=4-(LSubXSize%4);
     else LLineModulo=0;
     LGLTextureImageSize=(LSubXSize+LLineModulo)*LSubYSize;
     if((LGLTextureImage=L2DTexture->GLTextureImage=(unsigned char*)EMalloc(LGLTextureImageSize))!=NULL)
     {
      for(LYCnt=LYStart,LGLPixelOffset=0,LLineOffset=LYStart*LFullWidth;LYCnt<LYEnd;LYCnt++,LLineOffset+=LFullWidth)
      {
       for(LXCnt=LXStart;LXCnt<LXEnd;LXCnt++)
       {
        LPixelOffset=LXCnt+LLineOffset;
        LFR=LR[LPixelOffset];LFG=LG[LPixelOffset];LFB=LB[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=(LFR*EcLumaR+LFG*EcLumaG+LFB*EcLumaB);
       }
       LGLPixelOffset+=LLineModulo;
      }
     }
*/
    break;

// 3 components: B0,G0,R0,B1,G1,R1,B2,G2,R2... longword alignment at the end of the line
//
    case 3:
    case 4:
/*
     if(((LSubXSize*3)%4)!=0) LLineModulo=4-(LSubXSize*3)%4;
     else LLineModulo=0;
     LGLTextureImageSize=(LSubXSize*3+LLineModulo)*LSubYSize;
     if((LGLTextureImage=L2DTexture->GLTextureImage=(unsigned char*)EMalloc(LGLTextureImageSize))!=NULL)
     {
      for(LYCnt=LYStart,LGLPixelOffset=0,LLineOffset=LYStart*LFullWidth;LYCnt<LYEnd;LYCnt++,LLineOffset+=LFullWidth)
      {
       for(LXCnt=LXStart;LXCnt<LXEnd;LXCnt++)
       {
        LPixelOffset=LXCnt+LLineOffset;
        LGLTextureImage[LGLPixelOffset++]=LB[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=LG[LPixelOffset];
        LGLTextureImage[LGLPixelOffset++]=LR[LPixelOffset];
       }
       LGLPixelOffset+=LLineModulo;
      }
     }
*/
    break;
   }

  }
 }

// No cropping, use input Image
//
 if(LSubImage==NULL) LSubImage=LImage;

//printf("GLIM %08x -> %08x  %08x\n", LImage, LSubImage, LSubImage->PixelFormats);fflush(stdout);

 if(LSubImage->PixelFormats!=0)
 {
  unsigned int	LC;

  LTexXSize=LSubImage->XSize;LTexYSize=LSubImage->YSize;

// Make sure the texture dimensions are acceptable by OpenGL (powers of 2)
//
  LNewXSize=0;LNewYSize=0;
  for(LC=0;E3dGL_2DTextureSizes[LC]!=0;LC++) if(LTexXSize==E3dGL_2DTextureSizes[LC]) { LNewXSize=LTexXSize;break; }
  for(LC=0;E3dGL_2DTextureSizes[LC]!=0;LC++) if(LTexYSize==E3dGL_2DTextureSizes[LC]) { LNewYSize=LTexYSize;break; }

  if(LNewXSize==0)
  {
   if(LTexXSize<E3dGL_2DTextureSizes[0]) LNewXSize=E3dGL_2DTextureSizes[0];
   else
   {
    LDiff=LTexXSize-E3dGL_2DTextureSizes[0];
    for(LC=1;E3dGL_2DTextureSizes[LC]!=0;LC++)
    {
     LD=LTexXSize-E3dGL_2DTextureSizes[LC];
     if(E3dM_ABS(LD)<E3dM_ABS(LDiff)) LDiff=LD;
    }
    LNewXSize=LTexXSize-LDiff;
   }
  }

  if(LNewYSize==0)
  {
   if(LTexYSize<E3dGL_2DTextureSizes[0]) LNewYSize=E3dGL_2DTextureSizes[0];
   else
   {
    LDiff=LTexYSize-E3dGL_2DTextureSizes[0];
    for(LC=1;E3dGL_2DTextureSizes[LC]!=0;LC++)
    {
     LD=LTexYSize-E3dGL_2DTextureSizes[LC];
     if(E3dM_ABS(LD)<E3dM_ABS(LDiff)) LDiff=LD;
    }
    LNewYSize=LTexYSize-LDiff;
   }
  }


// Check if we have to s scale it for OpenGL
//
  if((LNewXSize!=LTexXSize)||(LNewYSize!=LTexYSize))
  {
// Texture dimensions are not powers of 2, we have to scale the texture for OpenGL
//
   if(LGLImage==NULL)
   {
    if((LGLImage=Ep_ImageAllocate(0, 0, 0))!=NULL)
    {
printf("%dx%d -> %dx%d\n", LTexXSize, LTexYSize, LNewXSize, LNewYSize);fflush(stdout);
     if(!Ep_ImageScale(LSubImage, LGLImage, LNewXSize, LNewYSize, EpFILTER_QUADRATIC, 0.5, FALSE)) { Ep_ImageFree(LGLImage, TRUE);LGLImage=NULL; }
     if(LSubImage!=LImage) Ep_ImageFree(LSubImage, TRUE);
    }
   }
  }
  else
  {
   LGLImage=LSubImage;LGLImage->RefCnt+=1;
  }

  if(LGLImage)
  {
   if((E3d_NotFound2DTextureGLImage!=NULL)&&(L2DTexture->GLImage==E3d_NotFound2DTextureGLImage))
   {
    L2DTexture->GLIndex=E3dGL2DTxtNONE;
    E3d_NotFound2DTextureGLImage->RefCnt-=1;
   }

   if(L2DTexture->GLImage!=LGLImage)
   {
    if(L2DTexture->GLImage) L2DTexture->GLImage->RefCnt-=1;
    L2DTexture->GLImage=LGLImage;LGLImage->RefCnt+=1;
   }
  }
 }

 *LGLImageRet=LGLImage;
 if(LGLImage) return(E3dGLIMG_SUCCESS);
 else return(E3dGLIMG_ERROR);
}
#endif	// USEOpenGL


//========================================================
// Update a 2DTexture for OpenGL
//========================================================
EBool E3d_Scene2DTextureUpdateImageForDisplay(E3dScene* LScene, E3d2DTexture* L2DTexture)
{
#ifdef USEOpenGL
 EImage*	LImage;

 if((LImage=L2DTexture->Image)==NULL) return(E3dGLIMG_ERROR);

 {
  EImage*	LGLImage;

  if(L2DTexture->GLImage==E3d_NotFound2DTextureGLImage)
  {
   L2DTexture->GLImage=NULL;if(E3d_NotFound2DTextureGLImage) E3d_NotFound2DTextureGLImage->RefCnt-=1;
  }

if(E_Verbose) { printf("E3d_Scene2DTextureUpdateImageForDisplay() ");fflush(stdout); }
  switch(E3d_Scene2DTextureGetGLImageDyn(LScene, L2DTexture, &LGLImage))
  {
   case E3dGLIMG_SUCCESS:
    glEnable(GL_TEXTURE_2D);
if(E_Verbose) { printf("%sE3dGLIMG_SUCCESS%s\n", EGreenCode, ENormalColorCode);fflush(stdout); }

    if(LGLImage->RGBA8Image)
    {
     if(L2DTexture->GLIndex==E3dGL2DTxtNONE)
     {
      glGenTextures(1, &(L2DTexture->GLIndex));
      glBindTexture(GL_TEXTURE_2D, L2DTexture->GLIndex);
      E_Current2DGLTexture=L2DTexture;

if(E_Verbose) { printf(" %dx%d Added to OpenGL as: %d\n", LGLImage->XSize, LGLImage->YSize, L2DTexture->GLIndex);fflush(stdout); }
      EGL_2DTextureSetMapping(L2DTexture);
     }
     else
     {
      if(E_Current2DGLTexture!=L2DTexture) { glBindTexture(GL_TEXTURE_2D, L2DTexture->GLIndex);E_Current2DGLTexture=L2DTexture; }
     }


     glTexImage2D(GL_TEXTURE_2D, 0, 4, LGLImage->XSize, LGLImage->YSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, LGLImage->RGBA8Image);

     return(TRUE);
    }
   break;

   case E3dGLIMG_ERROR:	
if(E_Verbose) { printf("%sE3dGLIMG_ERROR%s\n", ERedCode, ENormalColorCode);fflush(stdout); }
   return(FALSE);

   case E3dGLIMG_SET:
if(E_Verbose) { printf(" %dx%d Reusing OpenGL texture: %d\n", LGLImage->XSize, LGLImage->YSize, L2DTexture->GLIndex);fflush(stdout); }
     glEnable(GL_TEXTURE_2D);
     glBindTexture(GL_TEXTURE_2D, L2DTexture->GLIndex);E_Current2DGLTexture=L2DTexture;
     glTexImage2D(GL_TEXTURE_2D, 0, 4, LGLImage->XSize, LGLImage->YSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, LGLImage->RGBA8Image);

    return(TRUE);
   }
  }
#endif	// USEOpenGL

 return(TRUE);
}




//================================================================================
// (Re)read 2DTexture images for a Model hierarchy
//
// Argument
//  E3dModel* LRootModel       Pointer to the Root of the hierarchy
//  EBool     LForceReRead     Re-read images if they are already loaded
//
// Description
//  This function parses through the given hierarchy and (re)reads the texture
//  images for all the 2DTextures on the Geometries.
//================================================================================
void E3d_SceneModelHrcRead2DTextures(E3dScene* LScene, E3dModel* LModel, char* LPath, EBool LForceReRead, EBool LSetUpForDisplay)
{
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 E3dMaterial*	LMaterial;
 E3d2DTexture**	L2DTextures=NULL;
 unsigned int	LNumOf2DTextures=0;
 unsigned int	LGmC, LGmN, LTC, LTN;

printf(EGreenCode);

 while(LModel)
 {
  LGmN=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
  for(LGmC=0;LGmC<LGmN;LGmC++)
  {
   if((LGeometry=LGeometries[LGmC])!=NULL)
   {
    switch(LGeometry->GeoType)
    {
     caseE3dMESH():
      {
       E3dMesh*		LMesh=(E3dMesh*)LGeometry;
       E3dPolyGroup*	LPolyGroup;
       E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
       unsigned int	LGC, LGN=LMesh->NumOfPolyGroups;

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

	if((LMaterial=LPolyGroup->DrawingMaterial)!=NULL)
	{
// Collect 2DTextures first to make sure we update each one of them only once
//
	 if(LMaterial->Textures2D)
	 {
	  LTN=LMaterial->NumOf2DTextures;
	  for(LTC=0;LTC<LTN;LTC++)
	  {
	   ELst_AddPointerChk((void***)(&L2DTextures), &LNumOf2DTextures, LMaterial->Textures2D[LTC]);
	  }
	 }
	}

       }
      }
     break;
    }
   }
  }
  LModel=LModel->Next;
 }

// Now update the collected 2DTextures
//
 if(L2DTextures)
 {
  for(LTC=0;LTC<LNumOf2DTextures;LTC++)
  {
// Make sure the 2DTexture is in the Scene
//
   E3d_SceneAdd2DTexture(LScene, L2DTextures[LTC]);

   E3d_Scene2DTextureReadImage(LScene, L2DTextures[LTC], LPath, LForceReRead, LSetUpForDisplay);
  }
  EFree(L2DTextures);
 }
printf(ENormalColorCode);fflush(stdout);
}


//================================================================
// Add the Materials found in a Mesh, to a Scene
//================================================================
int E3d_MeshAddMaterialsToScene(E3dMesh* LMesh, E3dScene* LScene)
{
 E3dPolyGroup**	LPolyGroups=LMesh->PolyGroups;
 E3dPolyGroup*	LPolyGroup;
 unsigned int	LGCnt, LGNum;
 int		LMaterialsAdded=0;

 LGNum=LMesh->NumOfPolyGroups;
 for(LGCnt=0;LGCnt<LGNum;LGCnt++)
 {
  LPolyGroup=LPolyGroups[LGCnt];
  if(LPolyGroup->Material)
  {
   if(E3d_SceneAddMaterial(LScene, LPolyGroup->Material)) LMaterialsAdded++;
  }
 }
 return(LMaterialsAdded);
}


//================================================================
// Add the Materials found in a Model hierarchy to a Scene
//================================================================
int E3d_SceneAddMaterialsFromModelHrc(E3dScene* LScene, E3dModel* LRootModel)
{
 E3dModel*	LModel;
 E3dGeometry**	LGeometries;
 E3dGeometry*	LGeometry;
 int		LMaterialsAdded=0;
 unsigned int	LGmCnt, LGmNum;


 for(LModel=LRootModel;LModel;)
 {
// Only non-special Models have their Materials exposed to the user
//
  if(LModel->Type==E3dMDL_NORMAL)
  {
   if((LGeometries=LModel->Geometries)!=NULL)
   {
    LGmNum=LModel->NumOfGeometries;
    for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
    {
     LGeometry=LGeometries[LGmCnt];


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

	E3d_MeshAddMaterialsToScene(LMesh, LScene);

	if(LGeometry->NumOfLODs)
	{
	 int	LLODC;

	 for(LLODC=0;LLODC<LGeometry->NumOfLODs;LLODC++)
	 {
	  switch(LGeometry->LODs[LLODC]->GeoType)
	  {
	   caseE3dMESH():
	    LMaterialsAdded+=E3d_MeshAddMaterialsToScene((E3dMesh*)(LGeometry->LODs[LLODC]), LScene);
	   break;

	   case E3dGEO_FACE:
	    {
	     E3dFace*	LFace=(E3dFace*)(LGeometry->LODs[LLODC]);

	     if(LFace->Material)
	     {
	      if(E3d_SceneAddMaterial(LScene, LFace->Material)) LMaterialsAdded++;
	     }
	    }
	   break;
	  }
	 }

        }
       }
      break;

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

	if(LFace->Material)
	{
	 if(E3d_SceneAddMaterial(LScene, LFace->Material)) LMaterialsAdded++;
	}
       }
      break;
     }
    }
   }
  }
  LModel=LModel->Next;
 }
 return(LMaterialsAdded);
}



static int _ItemEvalSplineSegment(E3dSplineItem* LSplineItem, E3dModel* LModel, E3dSpline* LSpline)
{
 unsigned int	LKC, LKN=LSpline->NumOfCVs;

 LSplineItem->Model=LModel;

 switch(LSpline->SplineType)
 {
  case E3dSPL_BEZIER:
   {
    E3dBezierCV*	LCV=(E3dBezierCV*)(LSpline->CVs);

    for(LKC=0;LKC<LKN;LKC++, LCV++)
    {
     if(LCV->Flags&E3dKEYF_SEGMENT_SELECTED)
     {
      LSplineItem->Spline=LSpline;
      LSplineItem->PointIndex=LKC;
      return(LSplineItem->Type=E3dITEM_SPLINE_SEGMENT);
     }
    }
   }
  break;

  case E3dSPL_LINEAR:
   {
    E3dSplineCV*	LCV=(E3dSplineCV*)(LSpline->CVs);

    for(LKC=0;LKC<LKN;LKC++, LCV++)
    {
     if(LCV->Flags&E3dKEYF_SEGMENT_SELECTED)
     {
      LSplineItem->Spline=LSpline;
      LSplineItem->PointIndex=LKC;
      return(LSplineItem->Type=E3dITEM_SPLINE_SEGMENT);
     }
    }
   }
  break;

  default:
   {
    E3dSplineCV*	LCV=LSpline->CVs;

    for(LKC=0;LKC<LKN;LKC++, LCV++)
    {
     if(LCV->Flags&E3dKEYF_SEGMENT_SELECTED)
     {
      LSplineItem->Spline=LSpline;
      LSplineItem->PointIndex=LKC;
      return(LSplineItem->Type=E3dITEM_SPLINE_SEGMENT);
     }
    }
   }
  break;
 }
 return(E3dITEM_NONE);
}



//========================================================================================
// Find out what is selected in the given Scene and return it
//
// Arguments
//  E3dScene*    LModelsRet      Pointer to the Scene structure
//  E3dItem*     LSelectedItem   Selected Item to be returned
//
// Description
//  This function parses the hierarchies in the given Scene and gets the first selected
//  "Item". This can be anything from a Model, Geometry, PolyGroup, Polygon etc to an
//  edge or a Vertex.
//
// Return value
//  The type of Item selected. E.g.: E3dITEM_POLYGROUP
//========================================================================================
int E3d_SceneGetSingleSelection(E3dScene* LScene, E3dItem* LSelectedItem, int LTarget)
{
 E3dModel**	LRootModels;
 E3dModel*	LModel;
 unsigned int	LC, LN, LTotalPolyNum;


 LTotalPolyNum=0;

 LRootModels=LScene->RootModels;
 for(LC=0, LN=LScene->NumOfRootModels;LC<LN;LC++)
 {
  LModel=LRootModels[LC];

  {
   E3dGeometry**	LGeometries;
   E3dGeometry*		LGeometry;
   unsigned int		LGmCnt, LGmNum;


   LTotalPolyNum=0;
   for(;LModel;LModel=LModel->Next)
   {
    switch(LModel->Selection)
    {
     case E3dSEL_NODE:
     case E3dSEL_BRANCH:
     case E3dSEL_BRANCH_ROOT:
      if(LTarget&(E3dITEM_MESH|E3dITEM_SPLINE|E3dITEM_FACE))
      {
       LGmNum=LModel->NumOfGeometries;LGeometries=LModel->Geometries;

       for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
       {
	if((LGeometry=LGeometries[LGmCnt])!=NULL)
	{
	 switch(LGeometry->GeoType)
	 {
	  caseE3dMESH():
	   if(LTarget&E3dITEM_MESH)
	   {
	    E3dGeoItem*	LGeoItem=(E3dGeoItem*)LSelectedItem;

	    LGeoItem->Model=LModel;
	    LGeoItem->Geometry=LGeometry;
	    return(LGeoItem->Type=E3dITEM_MODEL);
	   }
	  break;

	  case E3dGEO_SPLINE:
	   if(LTarget&E3dITEM_SPLINE)
	   {
	    E3dSplineItem*	LSplineItem=(E3dSplineItem*)LSelectedItem;

	    LSplineItem->Model=LModel;
	    LSplineItem->Geometry=LGeometry;
	    LSplineItem->Spline=(E3dSpline*)LGeometry;
	    return(LSplineItem->Type=E3dITEM_MODEL);
	   }
	  break;

	  case E3dGEO_FACE:
	   if(LTarget&E3dITEM_FACE)
	   {
	    E3dSplineItem*	LSplineItem=(E3dSplineItem*)LSelectedItem;
	    E3dFace*		LFace=(E3dFace*)LGeometry;

	    LSplineItem->Model=LModel;
	    LSplineItem->Geometry=LGeometry;
	    LSplineItem->Spline=LFace->Exterior;
	    return(LSplineItem->Type=E3dITEM_MODEL);
	   }
	  break;
	 }
	}
       }
      }
      if(LTarget&E3dITEM_MODEL)
      {
       E3dGeoItem*	LGeoItem=(E3dGeoItem*)LSelectedItem;

       LGeoItem->Model=LModel;
       LGeoItem->Geometry=NULL;

       return(LGeoItem->Type=E3dITEM_MODEL);
      }
     break;

     case E3dSEL_GEOMETRY:
      LGmNum=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
      for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
      {
       if((LGeometry=LGeometries[LGmCnt])!=NULL)
       {
	EBool	LAccept=FALSE;

	switch(LGeometry->Selection)
	{
// Found selected Geometry, see if its type matches any of the targets
//
	 case E3dGSEL_GEOMETRY:
	  switch(LGeometry->GeoType)
	  {
	   caseE3dMESH():
	    if(LTarget&E3dITEM_MESH) LAccept=TRUE;
	   break;

	   case E3dGEO_SPLINE:
	    if(LTarget&E3dITEM_SPLINE) LAccept=TRUE;
	   break;

	   case E3dGEO_FACE:
	    if(LTarget&E3dITEM_FACE) LAccept=TRUE;
	   break;
	  }

	  if(LAccept)
	  {
	   E3dGeoItem*	LGeoItem=(E3dGeoItem*)LSelectedItem;

	   LGeoItem->Model=LModel;
	   LGeoItem->Geometry=LGeometry;
	   return(LGeoItem->Type=E3dITEM_GEOMETRY);
	  }
	 break;

	 case E3dGSEL_POLYGROUP:
	  if(LTarget&(E3dITEM_MESH|E3dITEM_POLYGROUP))
	  {
	   switch(LGeometry->GeoType)
	   {
	    caseE3dMESH():
	     {
	      E3dMesh*		LMesh;
	      E3dPolyGroup*	LPolyGroup;
	      E3dPolyGroup**	LPolyGroups;
	      unsigned int	LGCnt;

	      LMesh=(E3dMesh*)LGeometry;
	      LPolyGroups=LMesh->PolyGroups;
	      for(LGCnt=0;LGCnt<LMesh->NumOfPolyGroups;LGCnt++)
	      {
	       LPolyGroup=LPolyGroups[LGCnt];
	       if(LPolyGroup->Selected)
	       {
		E3dPolyItem*	LPolyItem=(E3dPolyItem*)LSelectedItem;

		LPolyItem->Model=LModel;
		LPolyItem->Geometry=LGeometry;
		LPolyItem->PolyGroup=LPolyGroup;
		return(LPolyItem->Type=E3dITEM_POLYGROUP);
	       }
	      }
	     }
	    break;
	   }
	  }
	 break;

	 case E3dGSEL_SPLINE_SEGMENT:
	  {
	   E3dSplineItem*	LSplineItem=(E3dSplineItem*)LSelectedItem;

	   LSplineItem->Geometry=LGeometry;
	   if(LGeometry->GeoType==E3dGEO_SPLINE) return(_ItemEvalSplineSegment(LSplineItem, LModel, (E3dSpline*)LGeometry));
	  }
	 break;

	 case E3dGSEL_FACE_CONTOUR:
	  if(LGeometry->GeoType==E3dGEO_FACE)
	  {
	   E3dSplineItem*	LSplineItem=(E3dSplineItem*)LSelectedItem;
	   E3dFace*		LFace=(E3dFace*)LGeometry;
	   E3dSpline*		LSpline;

	   LSplineItem->Model=LModel;
	   LSplineItem->Geometry=LGeometry;

// Check Exterior
//
	   LSpline=LFace->Exterior;
	   switch(LSpline->Selection)
	   {
	    case E3dGSEL_GEOMETRY:
	     {
	      LSplineItem->Spline=LSpline;
	      LSplineItem->ContourIndex=0;

	      return(LSplineItem->Type=E3dITEM_FACE_CONTOUR);
	     }

	    case E3dGSEL_SPLINE_SEGMENT:
	    return(_ItemEvalSplineSegment((E3dSplineItem*)LSelectedItem, LModel, LSpline));
	   }

// Check Holes
//
	   {
	    unsigned int	LHC, LHN=LFace->NumOfHoles;

	    for(LHC=0;LHC<LHN;LHC++)
	    {
	     LSpline=LFace->Holes[LHC];

	     switch(LSpline->Selection)
	     {
	      case E3dGSEL_GEOMETRY:
	      {
	       LSplineItem->Spline=LSpline;
	       LSplineItem->ContourIndex=LHC+1;
	       return(LSplineItem->Type=E3dITEM_FACE_CONTOUR);
	      }

	      case E3dGSEL_SPLINE_SEGMENT:
	      return(_ItemEvalSplineSegment((E3dSplineItem*)LSelectedItem, LModel, LSpline));
	     }
	    }
	   }

	  }
	 break;
	}
       }
      }
     break;
    }

   }
  }
 }
 return(E3dITEM_NONE);
}


//========================================================================================
// Collect selected Models in a Scene
//
// Arguments
//  E3dScene*    LScene          Pointer to the Scene structure
//  E3dModel***  LModelsRet      Pointer to the array of Model pointers to be returned
//  EBool        LBranchesAsOne  Selected branches are returned as single selection
//
// Description
//  This function parses the hierarchies in the given Scene and collects the selected
//  Models into a dynamically allocated array of pointers.
//  This array has to be freed by the application after use.
//  Example:
//   LNumOfModels=E3d_SceneGetSelectedModels(E3d_Scene, &LModels, TRUE);
//  Where LModels is the type of E3dModel**
//
// Return value
//  The number of E3dModel pointers in the array (the nuber of selected Models found).
//========================================================================================
int E3d_SceneGetSelectedModels(E3dScene* LScene, E3dModel*** LModelsRet, EBool LBranchesAsOne)
{
 E3dModel**	LRootModels=LScene->RootModels;
 E3dModel*	LModel;
 E3dModel**	LModels=NULL;
 unsigned int	LNumOfRootModels=LScene->NumOfRootModels;
 unsigned int	LNumOfSelectedModels=0;
 unsigned int	LC;


 for(LC=0;LC<LNumOfRootModels;LC++)
 {
  LModel=LRootModels[LC];

  for(;LModel;)
  {
   switch(LModel->Selection)
   {
    case E3dSEL_NODE:
    case E3dSEL_BRANCH:
    case E3dSEL_BRANCH_ROOT:
    case E3dSEL_GEOMETRY:
     ELst_AddPointerChk((void***)(&LModels), &LNumOfSelectedModels, (void*)LModel);

     if(LBranchesAsOne&&(LModel->Selection == E3dSEL_BRANCH_ROOT)) LModel=E3d_ModelHrcBranchGetNodeAfter(LModel);
     else LModel=LModel->Next;
    break;

    default:	LModel=LModel->Next;break;
   }
  }
 }

 if(LModelsRet) *LModelsRet=LModels;
 return(LNumOfSelectedModels);
}


//================================================================================================
// Collect selected Geometries in a Scene
//
// Arguments
//  E3dScene*       LScene           Pointer to the Scene structure
//  EE3dGeometry*** LGeometriesRet   Pointer to the array of Model pointers to be returned
//  EBool           LBranchesAsOne   Selected branches are returned as single selection
//
// Description
//  This function parses the hierarchies in the given Scene and collects the selected
//  Geometries into a dynamically allocated array of pointers.
//  This array has to be freed by the application after use.
//  This example collects all the selected Meshes:
//   LNumOfGeometries=E3d_SceneGetSelectedGeometries(E3d_Scene, &LGeometries, E3dGEO_MESH);
//  Where LGeometries is the type of E3dGeometry**
//  This example collects all the selected Geometries, regardless of their type:
//   LNumOfGeometries=E3d_SceneGetSelectedGeometries(E3d_Scene, &LGeometries, E3dGEO_ANY);
//
// Return value
//  The number of E3dGeometry pointers in the array (the nuber of selected Geometries found).
//================================================================================================
int E3d_SceneGetSelectedGeometries(E3dScene* LScene, E3dGeometry*** LGeometriesRet, int LGeoType)
{
 E3dModel**	LRootModels=LScene->RootModels;
 E3dModel*	LModel;
 E3dGeometry*	LGeometry;
 E3dGeometry**	LGeometries;
 E3dGeometry**	LSelectedGeometries=NULL;
 unsigned int	LNumOfRootModels=LScene->NumOfRootModels;
 unsigned int	LNumOfSelectedGeometries=0;
 unsigned int	LC, LGmC, LGmN;
 EBool		LModelSelected;


 for(LC=0;LC<LNumOfRootModels;LC++)
 {
  LModel=LRootModels[LC];

  for(;LModel;LModel=LModel->Next)
  {
   if(LModel->Selection != E3dSEL_NONE)
   {
    LGmN=LModel->NumOfGeometries;
    LGeometries=LModel->Geometries;

    E3dM_IsModelSelected(LModel, LModelSelected);

    if(LModel->Selection==E3dSEL_GEOMETRY)
    {
     for(LGmC=0;LGmC<LGmN;LGmC++)
     {
      LGeometry=LGeometries[LGmC];

      if((LGeometry->Selection!=E3dGSEL_NONE)&&((LGeoType==E3dGEO_ANY)||(LGeometry->GeoType==LGeoType)))
      {
       ELst_AddPointerChk((void***)(&LSelectedGeometries), &LNumOfSelectedGeometries, (void*)LGeometry);
      }
     }
    }
    else
    {
     for(LGmC=0;LGmC<LGmN;LGmC++)
     {
      LGeometry=LGeometries[LGmC];

      if((LGeoType==E3dGEO_ANY)||(LGeometry->GeoType==LGeoType))
      {
       ELst_AddPointerChk((void***)(&LSelectedGeometries), &LNumOfSelectedGeometries, (void*)LGeometry);
      }
     }
    }
   }

  }
 }

 if(LGeometriesRet) *LGeometriesRet=LSelectedGeometries;
 return(LNumOfSelectedGeometries);
}


//========================================
// Unselect everything in the Scene
//========================================
void E3d_SceneUnSelectAll(E3dScene* LScene, int LFlags)
{
 E3dModel**	LRootModels;
 E3dModel*	LModel;
 unsigned int	LC, LN;


 LRootModels=LScene->RootModels;
 for(LC=0, LN=E3d_Scene->NumOfRootModels;LC<LN;LC++)
 {
  LModel=LRootModels[LC];

  for(;LModel;LModel=LModel->Next)
  {

   if(LFlags&E3dSF_MODELS) LModel->Selection = E3dSEL_NONE;
   else
   {
    if(LFlags&E3dSF_GEOMETRIES) { if(LModel->Selection == E3dSEL_GEOMETRY) LModel->Selection = E3dSEL_NONE; }
   }

   E3d_UnselectModelGeometries(LModel, NULL, LFlags);
  }
 }
}


//================================================================
// Add callback to a Scene
//================================================================
void E3d_SceneAddCallback(E3dScene* LScene, unsigned int LCallbackType, E3dCallbackProc LCallbackProc, EPointer LClientData)
{
 E3dCallbackRec*	LCB;

 if(LCallbackType&E3dCALLBACK_GENERAL)
 {
  if(LScene->Callbacks==NULL)
  {
   if((LScene->Callbacks=(E3dCallbackRec*)EMalloc(sizeof(E3dCallbackRec)))!=NULL)
   {
    LScene->NumOfCallbacks=1;
    LScene->Callbacks[0].Proc=LCallbackProc;
    LScene->Callbacks[0].ClientData=LClientData;
   }
  }
  else
  {
   if((LCB=(E3dCallbackRec*)ERealloc(LScene->Callbacks, sizeof(E3dCallbackRec)*(LScene->NumOfCallbacks+1)))!=NULL)
   {
    LScene->Callbacks=LCB;
    LCB[LScene->NumOfCallbacks].Proc=LCallbackProc;
    LCB[LScene->NumOfCallbacks].ClientData=LClientData;
    LScene->NumOfCallbacks+=1;
   }
  }
 }


 if(LCallbackType&E3dCALLBACK_SELECT)
 {
  if(LScene->SelectCallbacks==NULL)
  {
   if((LScene->SelectCallbacks=(E3dCallbackRec*)EMalloc(sizeof(E3dCallbackRec)))!=NULL)
   {
    LScene->NumOfSelectCallbacks=1;
    LScene->SelectCallbacks[0].Proc=LCallbackProc;
    LScene->SelectCallbacks[0].ClientData=LClientData;
   }
  }
  else
  {
   if((LCB=(E3dCallbackRec*)ERealloc(LScene->SelectCallbacks, sizeof(E3dCallbackRec)*(LScene->NumOfSelectCallbacks+1)))!=NULL)
   {
    LScene->SelectCallbacks=LCB;
    LCB[LScene->NumOfSelectCallbacks].Proc=LCallbackProc;
    LCB[LScene->NumOfSelectCallbacks].ClientData=LClientData;
    LScene->NumOfSelectCallbacks+=1;
   }
  }
 }


 if(LCallbackType&E3dCALLBACK_ADDREMOVE)
 {
  if(LScene->AddRemoveCallbacks==NULL)
  {
   if((LScene->AddRemoveCallbacks=(E3dCallbackRec*)EMalloc(sizeof(E3dCallbackRec)))!=NULL)
   {
    LScene->NumOfAddRemoveCallbacks=1;
    LScene->AddRemoveCallbacks[0].Proc=LCallbackProc;
    LScene->AddRemoveCallbacks[0].ClientData=LClientData;
   }
  }
  else
  {
   if((LCB=(E3dCallbackRec*)ERealloc(LScene->AddRemoveCallbacks, sizeof(E3dCallbackRec)*(LScene->NumOfAddRemoveCallbacks+1)))!=NULL)
   {
    LScene->AddRemoveCallbacks=LCB;
    LCB[LScene->NumOfAddRemoveCallbacks].Proc=LCallbackProc;
    LCB[LScene->NumOfAddRemoveCallbacks].ClientData=LClientData;
    LScene->NumOfAddRemoveCallbacks+=1;
   }
  }
 }
}


//================================================================
// Remove callback from a Scene
//================================================================
void E3d_SceneRemoveCallback(E3dScene* LScene, unsigned int LCallbackType, E3dCallbackProc LCallbackProc, EPointer LClientData)
{
 E3dCallbackRec*	LCallbacks;
 int			LC, LN;


 if(LCallbackType&E3dCALLBACK_GENERAL)
 {
  LN=LScene->NumOfCallbacks;
  if(LN)
  {
   LCallbacks=LScene->Callbacks;

   for(LC=0;LC<LN;LC++)
   {
    if((LCallbacks[LC].Proc==LCallbackProc)&&(LCallbacks[LC].ClientData==LClientData))
    {
     if(LC<(LN-1)) memmove(LCallbacks+LC, LCallbacks+LC+1, sizeof(E3dCallbackRec)*(LN-LC-1));
     LScene->NumOfCallbacks-=1;
     if(LScene->NumOfCallbacks==0) { EFree(LScene->Callbacks);LScene->Callbacks=NULL; }
     else LScene->Callbacks=(E3dCallbackRec*)ERealloc(LScene->Callbacks, sizeof(E3dCallbackRec)*LScene->NumOfCallbacks);
    }
   }
  }
 }


 if(LCallbackType&E3dCALLBACK_SELECT)
 {
  LN=LScene->NumOfSelectCallbacks;
  if(LN)
  {
   LCallbacks=LScene->SelectCallbacks;

   for(LC=0;LC<LN;LC++)
   {
    if((LCallbacks[LC].Proc==LCallbackProc)&&(LCallbacks[LC].ClientData==LClientData))
    {
     if(LC<(LN-1)) memmove(LCallbacks+LC, LCallbacks+LC+1, sizeof(E3dCallbackRec)*(LN-LC-1));
     LScene->NumOfSelectCallbacks-=1;
     if(LScene->NumOfSelectCallbacks==0) { EFree(LScene->SelectCallbacks);LScene->SelectCallbacks=NULL; }
     else LScene->SelectCallbacks=(E3dCallbackRec*)ERealloc(LScene->SelectCallbacks, sizeof(E3dCallbackRec)*LScene->NumOfSelectCallbacks);
    }
   }
  }
 }


 if(LCallbackType&E3dCALLBACK_ADDREMOVE)
 {
  LN=LScene->NumOfAddRemoveCallbacks;
  if(LN)
  {
   LCallbacks=LScene->AddRemoveCallbacks;

   for(LC=0;LC<LN;LC++)
   {
    if((LCallbacks[LC].Proc==LCallbackProc)&&(LCallbacks[LC].ClientData==LClientData))
    {
     if(LC<(LN-1)) memmove(LCallbacks+LC, LCallbacks+LC+1, sizeof(E3dCallbackRec)*(LN-LC-1));
     LScene->NumOfAddRemoveCallbacks-=1;
     if(LScene->NumOfAddRemoveCallbacks==0) { EFree(LScene->AddRemoveCallbacks);LScene->AddRemoveCallbacks=NULL; }
     else LScene->AddRemoveCallbacks=(E3dCallbackRec*)ERealloc(LScene->AddRemoveCallbacks, sizeof(E3dCallbackRec)*LScene->NumOfAddRemoveCallbacks);
    }
   }
  }
 }
}


//================================================================
// Walk through the Models in a Scene and call a function
//================================================================
void E3d_SceneWalkModels(E3dScene* LScene, void (*LFunct)(E3dModel*))
{
 E3dModel**			LRootModels;
 E3dModel*			LModel;
 unsigned int			LRMC, LRMN;

 LRootModels=E3d_Scene->RootModels;LRMN=E3d_Scene->NumOfRootModels;
 for(LRMC=0;LRMC<LRMN;LRMC++)
 {
  LModel=LRootModels[LRMC];

  for(;LModel;LModel=LModel->Next) LFunct(LModel);
 }
}


//========================================================================
// Walk through the Geometries of Models in a scene and call a function
//========================================================================
void E3d_SceneWalkGeometries(E3dScene* LScene, void (*LFunct)(E3dModel*, E3dGeometry*))
{
 E3dModel**			LRootModels;
 E3dModel*			LModel;
 unsigned int			LRMC, LRMN;


 LRootModels=E3d_Scene->RootModels;LRMN=E3d_Scene->NumOfRootModels;
 for(LRMC=0;LRMC<LRMN;LRMC++)
 {
  LModel=LRootModels[LRMC];

  {
   E3dGeometry**	LGeometries;
   unsigned int		LGC, LGN;

   for(;LModel;LModel=LModel->Next)
   {
    switch(LModel->Type)
    {
     case E3dMDL_NORMAL:
      LGN=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
      for(LGC=0;LGC<LGN;LGC++) LFunct(LModel, LGeometries[LGC]);
     break;
    }
   }
  }
 }
}


static int _SplineGetTaggedCVs(E3dSpline* LSpline, void** LTransformedPointsRet, unsigned int* LPointSizeRet)
{
 int		LNumOfTaggedPoints;

 if((LNumOfTaggedPoints=E3d_SplineNumOfTaggedCVs(LSpline))>0)
 {
  unsigned int	LVN=LSpline->NumOfCVs;

  switch(LSpline->SplineType)
  {
   case E3dSPL_BEZIER:
    {
     E3dSnapshotBezierCV*	LTransformedBezierCVs=(E3dSnapshotBezierCV*)EMalloc(sizeof(E3dSnapshotBezierCV)*LNumOfTaggedPoints);

     if(LTransformedBezierCVs)
     {
      E3dBezierCV*		LBezierCV=(E3dBezierCV*)(LSpline->CVs);
      E3dSnapshotBezierCV*	LTransformedBezierCV=LTransformedBezierCVs;
      unsigned int		LVC;

      *LPointSizeRet=sizeof(E3dBezierCV);
      *LTransformedPointsRet=(void*)LTransformedBezierCVs;
      for(LVC=0;LVC<LVN;LVC++, LBezierCV++)
      {
       if(LBezierCV->Flags&E3dKEYF_TAGGED)
       {
	LTransformedBezierCV->Position=LBezierCV->Position;
	LTransformedBezierCV->Previous=LBezierCV->Previous;
	LTransformedBezierCV->Next=LBezierCV->Next;

	LTransformedBezierCV->Flags=LBezierCV->Flags;
	LTransformedBezierCV->Index=LVC;
	LTransformedBezierCV++;
       }
      }
     }
    }
   break;

   default:
    {
     E3dSnapshotSplineCV*	LTransformedSplineCVs=(E3dSnapshotSplineCV*)EMalloc(sizeof(E3dSnapshotSplineCV)*LNumOfTaggedPoints);

     if(LTransformedSplineCVs)
     {
      E3dSplineCV*		LSplineCV=(E3dSplineCV*)(LSpline->CVs);
      E3dSnapshotSplineCV*	LTransformedSplineCV=LTransformedSplineCVs;
      unsigned int		LVC;

      *LPointSizeRet=sizeof(E3dSplineCV);
      *LTransformedPointsRet=(void*)LTransformedSplineCVs;
      for(LVC=0;LVC<LVN;LVC++, LSplineCV++)
      {
       if(LSplineCV->Flags&E3dKEYF_TAGGED)
       {
	LTransformedSplineCV->Position=LSplineCV->Position;
	LTransformedSplineCV->Flags=LSplineCV->Flags;
	LTransformedSplineCV->Index=LVC;
	LTransformedSplineCV++;
       }
      }
     }
    }
   break;
  }
 }
 return(LNumOfTaggedPoints);
}


#define _M_AddTaggedPointsToList()\
 LShapeSnapshot=NULL;\
 if(LShapeSnapshots==NULL)\
 {\
  if((LShapeSnapshots=(E3dShapeSnapshot*)EMalloc(sizeof(E3dShapeSnapshot)))!=NULL) LShapeSnapshot=LShapeSnapshots;\
 }\
 else\
 {\
  E3dShapeSnapshot*	LShapeSnapshotsT;\
\
  if((LShapeSnapshotsT=(E3dShapeSnapshot*)ERealloc(LShapeSnapshots, sizeof(E3dShapeSnapshot)*(LNumOfShapeSnapshots+1)))!=NULL)\
  {\
   LShapeSnapshots=LShapeSnapshotsT;\
   LShapeSnapshot=LShapeSnapshots+LNumOfShapeSnapshots;\
  }\
 }\



//================================================
// Collect Geometries with tagged points
//================================================
E3dShapeSnapshot* E3d_SceneTaggedPointsToTransformPoints(E3dScene* LScene, unsigned int* LNumOfShapeSnapshotsRet)
{
 E3dShapeSnapshot*	LShapeSnapshots=NULL;
 E3dShapeSnapshot*	LShapeSnapshot;
 E3dModel**		LRootModels;
 E3dModel*		LModel;
 unsigned int		LRMC, LRMN, LC, LNumOfShapeSnapshots=0;
 EBool			LModelSelected;


 LRootModels=E3d_Scene->RootModels;LRMN=E3d_Scene->NumOfRootModels;
 for(LRMC=0;LRMC<LRMN;LRMC++)
 {
  LModel=LRootModels[LRMC];

  for(;LModel;LModel=LModel->Next)
  {
   if(LModel->Selection != E3dSEL_NONE)
   {
    E3dGeometry**	LGeometries;
    E3dGeometry*	LGeometry;
    unsigned int	LGmCnt, LGmNum;

    E3dM_IsModelSelected(LModel, LModelSelected);

    switch(LModel->Type)
    {
     case E3dMDL_NORMAL:
     case E3dMDL_JOINT:
     case E3dMDL_BONE:
      LGmNum=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
      for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
      {
       if((LGeometry=LGeometries[LGmCnt])!=NULL)
       {
	if(LModelSelected||(LGeometry->Selection==E3dGSEL_GEOMETRY))
	{
	 EBool	LGeoFound=FALSE;


// Check if this Geomertry is already stored
//
	 if(LNumOfShapeSnapshots>0)
	 {
	  LShapeSnapshot=LShapeSnapshots;
	  for(LC=0;LC<LNumOfShapeSnapshots;LC++, LShapeSnapshot++)
	  {
	   if(LShapeSnapshot->Geometry==LGeometry) LGeoFound=TRUE;
	  }
	 }

	 if(!LGeoFound)
	 {
	  void*		LTransformedPoints=NULL;
	  unsigned int	LNumOfTaggedPoints=0, LPointSize=0;

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

	     if((LNumOfTaggedPoints=E3d_MeshNumOfTaggedVertices(LMesh))>0)
	     {
	      E3dSnapshotVertex*	LTransformedVertices=(E3dSnapshotVertex*)EMalloc(sizeof(E3dSnapshotVertex)*LNumOfTaggedPoints);

	      LPointSize=sizeof(E3dVertex);
	      if(LTransformedVertices)
	      {
	       E3dVertex*		LVertex=LMesh->Vertices;
	       E3dSnapshotVertex*	LTransformedVertex=LTransformedVertices;
	       unsigned int		LVC, LVN=LMesh->NumOfVertices;

	       LTransformedPoints=(void*)LTransformedVertices;
	       for(LVC=0;LVC<LVN;LVC++, LVertex++)
	       {
		if(LVertex->Flags&E3dVTXF_TAGGED)
		{
		 LTransformedVertex->X=LVertex->X;
		 LTransformedVertex->Y=LVertex->Y;
		 LTransformedVertex->Z=LVertex->Z;
		 LTransformedVertex->Flags=LVertex->Flags;
		 LTransformedVertex->Index=LVC;
		 LTransformedVertex++;
		}
	       }
	      }
	     }
	    }
	    if(LPointSize)
	    {
#ifdef E_MEMDEBUG
E_MemName="E3dShapeSnapshot by E3d_SceneTaggedPointsToTransformPoints() for Mesh Vertices";
#endif
	     _M_AddTaggedPointsToList();

	     if(LShapeSnapshot)
	     {
	      E3dMeshVerticesSnapshot*	LVtxSnapshot=(E3dMeshVerticesSnapshot*)LShapeSnapshot;

	      LVtxSnapshot->Mesh=(E3dMesh*)LGeometry;LGeometry->RefCnt+=1;
	      LVtxSnapshot->SnapshotVertices=(E3dSnapshotVertex*)LTransformedPoints;
	      LVtxSnapshot->NumOfPoints=LNumOfTaggedPoints;
	      LNumOfShapeSnapshots++;
	     }
	    }
	   break;

	   case E3dGEO_SPLINE:
	    LNumOfTaggedPoints=_SplineGetTaggedCVs((E3dSpline*)LGeometry, &LTransformedPoints, &LPointSize);
	    if(LPointSize)
	    {
#ifdef E_MEMDEBUG
E_MemName="E3dShapeSnapshot by E3d_SceneTaggedPointsToTransformPoints() for Spline CVs";
#endif
	     _M_AddTaggedPointsToList();

	     if(LShapeSnapshot)
	     {
	      E3dSplineCVsSnapshot*	LCVsSnapshot=(E3dSplineCVsSnapshot*)LShapeSnapshot;

	      LCVsSnapshot->Spline=(E3dSpline*)LGeometry;LGeometry->RefCnt+=1;
	      LCVsSnapshot->SnapshotCVs=LTransformedPoints;
	      LCVsSnapshot->NumOfCVs=LNumOfTaggedPoints;
	      LCVsSnapshot->CVSize=LPointSize;
	      LCVsSnapshot->SubCV=E3dBEZ_POSITION;
	      LNumOfShapeSnapshots++;
	     }
	    }
	   break;

	   case E3dGEO_FACE:
	    {
	     E3dFace*			LFace=(E3dFace*)LGeometry;
	     unsigned int		LHC, LHN=LFace->NumOfHoles, LNumOfSplinesToStore=0;
	     E3dSplineCVsSnapshot*	LCVsSnapshots=(E3dSplineCVsSnapshot*)EMalloc(sizeof(E3dSplineCVsSnapshot)*(LHN+1));
	     E3dSplineCVsSnapshot*	LCVsSnapshot=LCVsSnapshots;

	     if(LCVsSnapshots)
	     {
	      LPointSize=0;
	      LNumOfTaggedPoints=_SplineGetTaggedCVs(LFace->Exterior, &LTransformedPoints, &LPointSize);
	      if(LPointSize)
	      {
	       LCVsSnapshot->Spline=LFace->Exterior;LFace->Exterior->RefCnt+=1;
	       LCVsSnapshot->SnapshotCVs=LTransformedPoints;
	       LCVsSnapshot->NumOfCVs=LNumOfTaggedPoints;
	       LCVsSnapshot->CVSize=LPointSize;
	       LCVsSnapshot->SubCV=E3dBEZ_POSITION;
	       LCVsSnapshot++;LNumOfSplinesToStore++;
	      }

	      for(LHC=0;LHC<LHN;LHC++)
	      {
	       LPointSize=0;
	       LNumOfTaggedPoints=_SplineGetTaggedCVs(LFace->Holes[LHC], &LTransformedPoints, &LPointSize);
	       if(LPointSize)
	       {
		LCVsSnapshot->Spline=LFace->Holes[LHC];LFace->Holes[LHC]->RefCnt+=1;
		LCVsSnapshot->SnapshotCVs=LTransformedPoints;
		LCVsSnapshot->NumOfCVs=LNumOfTaggedPoints;
		LCVsSnapshot->CVSize=LPointSize;
	        LCVsSnapshot->SubCV=E3dBEZ_POSITION;
		LCVsSnapshot++;LNumOfSplinesToStore++;
	       }
	      }

	      if(LNumOfSplinesToStore)
	      {
#ifdef E_MEMDEBUG
E_MemName="E3dShapeSnapshot by E3d_SceneTaggedPointsToTransformPoints() for Face CVs";
#endif
	       _M_AddTaggedPointsToList();

	       if(LShapeSnapshot)
	       {
		E3dFaceCVsSnapshot*	LFaceCVsSnapshot=(E3dFaceCVsSnapshot*)LShapeSnapshot;

		if(LNumOfSplinesToStore<(LHN+1))
		{
		 LCVsSnapshots=(E3dSplineCVsSnapshot*)ERealloc(LCVsSnapshots, sizeof(E3dSplineCVsSnapshot)*LNumOfSplinesToStore);
		}

		LFaceCVsSnapshot->Face=(E3dFace*)LGeometry;LGeometry->RefCnt+=1;
		LFaceCVsSnapshot->SplineCVsSnapshots=LCVsSnapshots;
		LFaceCVsSnapshot->NumOfSplineCVsSnapshots=LNumOfSplinesToStore;
		LNumOfShapeSnapshots++;
	       }
	      }
	      else EFree(LCVsSnapshots);
	     }
	    }
	   break;
	  }
	 }
	}


       }
      }
     break;
    }
   }
  }
 }

 *LNumOfShapeSnapshotsRet=LNumOfShapeSnapshots;
 return(LShapeSnapshots);
}


//================================================
// Free an array Shape snapshots
//================================================
void E3d_ShapeSnapshotsFree(E3dShapeSnapshot* LShapeSnapshots, unsigned int LNumOfShapeSnapshots)
{
 E3dShapeSnapshot*		LShapeSnapshot=LShapeSnapshots;
 unsigned int			LC;

 for(LC=0;LC<LNumOfShapeSnapshots;LC++, LShapeSnapshot++) E3d_ShapeSnapshotFree(LShapeSnapshot);

 EFree(LShapeSnapshots);
}


//========================================================================================
// Collect the names of all Models in a Scene into an array
//
// Arguments
//  E3dScene*    LScene          Pointer to the Scene structure
//  char***      LNamesRet       Pointer to the array of strings for the return value
//
// Description
//  This function parses the hierarchies in the given Scene and collects the names of
//  all Models into a dynamically allocated array of strings.
//	
// Return value
//  The number of items in the array (the nuber of Models found).
//========================================================================================
int E3d_SceneCollectModelNames(E3dScene* LScene, char*** LNamesRet)
{
 E3dModel**	LRootModels=LScene->RootModels;
 unsigned int	LNumOfRootModels=LScene->NumOfRootModels;
 E3dModel*	LModel;
 int		LMC, LC, LN;

// Count Models first
//
 LN=0;
 for(LMC=0;LMC<LNumOfRootModels;LMC++)
 {
  LModel=LRootModels[LMC];

  for(;LModel;LModel=LModel->Next) LN++;
 }

 if(LN)
 {
  char**	LNames=(char**)EMalloc(sizeof(char*)*LN);

  if(LNames)
  {
   LC=0;
   for(LMC=0;LMC<LNumOfRootModels;LMC++)
   {
    LModel=LRootModels[LMC];

    for(;LModel;LModel=LModel->Next)
    {
     LNames[LC]=LModel->Name;LC++;
    }
   }
  }
  if(LNamesRet) *LNamesRet=LNames;
 }

 return(LN);
}


//================================================================================
// Append an Animation to a Scene
//
// Arguments
//  E3dScene*     LScene          Pointer to the Scene structure
//  E3dAnimation* LAnimation      Pointer to the new Animation structure
//
// Description
//  Checks if the given Animation is in the given Scene. If not, adds it to the
//  array: Scene->Animations.
//
// See also
//  E3d_SceneRemoveAnimation
//================================================================================
EBool E3d_SceneAppendAnimation(E3dScene* LScene, E3dAnimation* LAnimation)
{
 if(LAnimation)
 {
  if(ELst_AddPointerAChk((void***)(&(LScene->Animations)), &(LScene->NumOfAnimations), &(LScene->NumOfAnimationsAllocated), 4, (void*)LAnimation))
  {
   LAnimation->RefCnt+=1;

   return(TRUE);
  }
 }
 return(FALSE);
}


//================================================================================
// Allocate and append an Animation to a Scene
//
// Arguments
//  E3dScene*     LScene          Pointer to the Scene structure
//  E3dAnimation* LAnimation      Pointer to the new Animation structure
//
// Description
//  Allocate an Animation of the given Class, initialize it and add it to the
//  array: Scene->Animations.
//
// See also
//  E3d_SceneRemoveAnimation
//================================================================================
E3dAnimation* E3d_SceneAddAnimation(E3dScene* LScene, E3dAnimationClass* LClass)
{
 E3dAnimation*	LAnimation=E3d_AnimationAllocate(LClass);

 if(LAnimation)
 {
  if(ELst_AddPointerA((void***)(&(LScene->Animations)), &(LScene->NumOfAnimations), &(LScene->NumOfAnimationsAllocated), 4, (void*)LAnimation))
  {
   LAnimation->RefCnt+=1;
  }
 }
 return(LAnimation);
}


//================================================================================
// Remove an Animation from a Scene
//
// Arguments
//  E3dScene* LScene           Pointer to the Scene structure
//  E3dAnimation* LAnimation   Pointer to the Root of the hierarchy
//
// Description
//  Checks if the given hierarchy is in the Scene (it's in Animations array of
//  the Scene). If it is, it removes it from that array.
//
// See also
//  E3d_SceneAddAnimationHrc
//================================================================================
void E3d_SceneRemoveAnimation(E3dScene* LScene, E3dAnimation* LAnimation)
{
 if(LScene->NumOfAnimations==0) return;

 if(ELst_RemovePointerA((void***)(&(LScene->Animations)), &(LScene->NumOfAnimations), &(LScene->NumOfAnimationsAllocated), LAnimation))
 {
  LAnimation->RefCnt-=1;
 }
}
