/*======================================================================================================*/
/* Render the Scene using the EQUINOX-3D renderer							*/
/*													*/
/* AUTHOR:	Gabor Nagy										*/
/* DATE:	1996-Nov-27 22:43:18									*/
/*													*/
/* EQUINOX-3D(TM), 3DPanel(TM) and 3DLib(TM) Copyright (C) 1995 By Gabor Nagy. All rights reserved.	*/
/*======================================================================================================*/
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <sys/time.h>

#include <X11/IntrinsicP.h>

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

#include <Xe/Draw.h>
#include <Xe/Form.h>
#include <Xe/Frame.h>

#include <EGUI/PushButton.h>
#include <EGUI/ToggleButton.h>
#include <EGUI/Dialog.h>
#include <EGUI/FSBox.h>

#include <Image/Panel.h>

#include <E3D/Matrix.h>

#include <E3D/3DWindow.h>

#include <E3D/Ray.h>

#include <E3D/Rendering.h>

#include <E3D/StatusPanel.h>	// For E3dp_PrintMessage



static EPlugin*		_Plugin=NULL;

static EguiItem		_MenuButton=NULL;

static Arg		Args[256];
static Cardinal		ArgCnt;

static void		_CB_Camera(void* LCam, EPointer LClientData, EPointer LCallData);
static void		_CB_Scene(void* LScn, EPointer LClientData, EPointer LCallData);

static EBool		_CanvasUp=FALSE;


int _MaxInteractiveRes=480*320;

/*--------------------------------------*/
/* Back-door to EQUINOX-3D!		*/
/*--------------------------------------*/
extern EguiItem		Eqx_TopShell;

extern E3dPanel*	Eqx_3DPanel;




#define OPENGL_CANVAS	1



#define E3dINTERNAL_BOUNCE_RAYLUMA_MIN	0.1

enum
{
 E3dCR_PREVIEW=EguiCUSTOM0
};


static EguiItem		_CanvasDialog=NULL;


#ifdef OPENGL_CANVAS
GLXContext		_GLXContext=NULL;
#else
static GC		_CanvasGC=(GC)NULL;
#endif


static Widget		_CanvasW=NULL;

static Pixmap		_Pixmap=(Pixmap)NULL;

static EBool		_Interactive=FALSE, _CallbacksAdded=FALSE;





typedef struct
{
 E3dRendererCore();
} E3dERay;

static E3dERay*		_RayTracer=NULL;

static E3dPanel		_3DPanel;	// 3DPanel for the canvas 3DWindow
static E3dWindow	_3DWindow;	// 3DWindow to render into


/*======================================================*/
/* Intersect a ray with a coordinate system-aligned box	*/
/*======================================================*/
EBool E3d_IntersectRayWithBox(E3dCoordinate* LRayOrig, E3dCoordinate* LRayDir, E3dCoordinate* LBBoxMin, E3dCoordinate* LBBoxMax)
{
 if(LRayOrig[E3dX]<=LBBoxMin[E3dX])
 {
  if(LRayDir[E3dX]<=0.0) return(FALSE);		// Ray left of box, going away from it
 }
 else
 {
  if(LRayOrig[E3dX]>=LBBoxMax[E3dX])		// Ray right of box?
  {
   if(LRayDir[E3dX]>0.0) return(FALSE);		// Ray right of box, going away from it
  }
 }

 if(LRayOrig[E3dY]<=LBBoxMin[E3dY])
 {
  if(LRayDir[E3dY]<=0.0) return(FALSE);		// Ray left of box, going away from it
 }
 else
 {
  if(LRayOrig[E3dY]>=LBBoxMax[E3dY])		// Ray right of box?
  {
   if(LRayDir[E3dY]>0.0) return(FALSE);		// Ray right of box, going away from it
  }
 }

 if(LRayOrig[E3dZ]<=LBBoxMin[E3dZ])
 {
  if(LRayDir[E3dZ]<=0.0) return(FALSE);		// Ray left of box, going away from it
 }
 else
 {
  if(LRayOrig[E3dZ]>=LBBoxMax[E3dZ])		// Ray right of box?
  {
   if(LRayDir[E3dZ]>0.0) return(FALSE);		// Ray right of box, going away from it
  }
 }
 return(TRUE);
}








static void _DCB_ImageSaveAsClose()
{
}


/*======================================*/
/* Dialog button callback		*/
/*======================================*/
void _DCB_Interactive(EguiItem LW, EPointer LClientData, EPointer LCallData)
{
 _Interactive=((EguiToggleButtonCallbackStruct*)LCallData)->Set;
 if(_Interactive)
 {
  E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;
  int			LPixels=LRenderInfo->StartPixelSize*LRenderInfo->StartPixelSize;

  if((LRenderInfo->ImageXSize*LRenderInfo->ImageYSize/LPixels)>_MaxInteractiveRes) E3dp_PrintMessage(0, 7000, "The rendering resolution is too high for interactive rates, please lower it");
 }
}


/*======================================*/
/* Dialog button callback		*/
/*======================================*/
void _DCB_Save(EguiItem LW, EPointer LClientData, EPointer LCallData)
{
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;
 char		LRealName[MAXPATHLEN];

 if(Ep_ImageSaveToFile("Preview.sgi", LRealName, MAXPATHLEN, LRenderInfo->CanvasImage, EpFileFormatFROM_EXTENSION)==EIO_SUCCESS)
 {
  E3dp_PrintMessage(0, 10000, "Image saved as: '%s'\n", LRealName);
 }
}


/*======================================*/
/* Dialog button callback		*/
/*======================================*/
void _DCB_SaveAs(EguiItem LW, EPointer LClientData, EPointer LCallData)
{
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;

 Ep_SaveImageAs(LRenderInfo->CanvasImage, "Save image", Eqx_TopShell, _DCB_ImageSaveAsClose);
}


/*======================================*/
/* Dialog button callback		*/
/*======================================*/
void _DCB_Stop(EguiItem LW, EPointer LClientData, EPointer LCallData)
{
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;

 LRenderInfo->GoRender=FALSE;
}


/*======================================*/
/* Dialog button callback		*/
/*======================================*/
void _DCB_Close(EguiItem LW, EPointer LClientData, EPointer LCallData)
{
 E3dScene*	LScene=E3d_Scene;
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;


 LRenderInfo->Rendering=FALSE;
 LRenderInfo->GoRender=FALSE;
 EGUI_UndisplayShell(_CanvasDialog);
 _CanvasUp=FALSE;

 if(_CallbacksAdded)
 {
  E3dCamera*	LCamera=&(E3dp_Main3DWindow->PerspectiveCamera);

printf("CBRemove\n");fflush(stdout);
  E3d_CameraRemoveCallback(LCamera, _CB_Camera, (EPointer)0);
  E3d_SceneRemoveCallback(LScene, E3dCALLBACK_GENERAL, _CB_Scene, (EPointer)0);
  _CallbacksAdded=FALSE;
 }

 if(LRenderInfo->CanvasImage)
 {
  Ep_ImageFree(LRenderInfo->CanvasImage, TRUE);LRenderInfo->CanvasImage=NULL;
 }

 if(_Pixmap)
 {
  XFreePixmap(XtDisplay(_CanvasW), _Pixmap);_Pixmap=(Pixmap)NULL;
 }

 LRenderInfo->WorkingImageXSize=0;LRenderInfo->WorkingImageYSize=0;

 E3dp_SetProgressIndicator(-1, NULL, NULL);

 E3d_SceneFreeVoxels(LScene);
 E3d_SceneFreeRenderTriangles(LScene);

 _Plugin->LockCount=0;
}


/*======================================*/
/* Abort callback			*/
/*======================================*/
static void E3d_AbortCallback(EPointer LClientData)
{
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;

 LRenderInfo->GoRender=FALSE;
}


/*======================================*/
/* Redraw canvas window with OpenGL	*/
/*======================================*/
void _GLRedrawCanvas(E3dRenderInfo* LRenderInfo)
{
 Window LWindow=XtWindow(_CanvasW);

 if(E3dp_CurrentGLXWindow!=LWindow)
 {
  glXMakeCurrent(E3dp_Display, LWindow, _GLXContext);
  E3dp_CurrentGLXWindow=LWindow;
 }


 if(LRenderInfo->CanvasImage)
 {
  float	LZoomF;
  int	LPicXSize, LPicYSize;

  if(LRenderInfo->CurrentPixelSize==0) LRenderInfo->CurrentPixelSize=1;

// Must extend the view-port upward by 1 pixel, so glDrawPixels won't reject our draw request
//
  glViewport(0, 0, LRenderInfo->WorkingImageXSize, LRenderInfo->WorkingImageYSize+1);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0, (GLdouble)LRenderInfo->WorkingImageXSize, 0.0, (GLdouble)(LRenderInfo->WorkingImageYSize+1), -1.0, 1.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glDrawBuffer(GL_FRONT);

  glDisable(GL_TEXTURE_2D);
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);

  glDisable(GL_BLEND);


  LZoomF=(float)(LRenderInfo->CurrentPixelSize);
  glPixelZoom(LZoomF, -LZoomF);

  LPicXSize=LRenderInfo->WorkingImageXSize/LRenderInfo->CurrentPixelSize;
  LPicYSize=LRenderInfo->WorkingImageYSize/LRenderInfo->CurrentPixelSize;

  {
   int	LX;

   glPixelStorei(GL_UNPACK_ROW_LENGTH, LRenderInfo->WorkingImageXSize*LRenderInfo->CurrentPixelSize);

   if(LRenderInfo->CurrentPixelSize>1)
   {
    for(LX=0;LX<LRenderInfo->WorkingImageXSize;LX+=LRenderInfo->CurrentPixelSize)
    {
     glRasterPos2i(LX, LRenderInfo->WorkingImageYSize);

     if((LRenderInfo->LinesDone>0)&&(LRenderInfo->LinesDone<LRenderInfo->WorkingImageYSize)) glDrawPixels(1, LRenderInfo->LinesDone/LRenderInfo->CurrentPixelSize+1, EpGLPIXELFORMAT, EpGLPIXELTYPE, LRenderInfo->CanvasImage->RGBA8Image+LX);
     else glDrawPixels(1, LRenderInfo->WorkingImageYSize/LRenderInfo->CurrentPixelSize, EpGLPIXELFORMAT, EpGLPIXELTYPE, LRenderInfo->CanvasImage->RGBA8Image+LX);
    }
   }
   else
   {
    glRasterPos2i(0, LRenderInfo->WorkingImageYSize);

    if((LRenderInfo->LinesDone>0)&&(LRenderInfo->LinesDone<LRenderInfo->WorkingImageYSize)) glDrawPixels(LRenderInfo->WorkingImageXSize, LRenderInfo->LinesDone/LRenderInfo->CurrentPixelSize+1, EpGLPIXELFORMAT, EpGLPIXELTYPE, LRenderInfo->CanvasImage->RGBA8Image);
    else glDrawPixels(LRenderInfo->WorkingImageXSize, LRenderInfo->WorkingImageYSize/LRenderInfo->CurrentPixelSize, EpGLPIXELFORMAT, EpGLPIXELTYPE, LRenderInfo->CanvasImage->RGBA8Image);
   }
  }

//  glXSwapBuffers(EGUI_Display, LWindow);

 }
 glDrawBuffer(GL_BACK);
}


/*======================================*/
/* XeDraw event-handler			*/
/*======================================*/
void _EH_CanvasWidget(Widget LW, XtPointer LClientData, XEvent* LXEv, Boolean* LCdr)
{
 E3dRenderInfo*	LRenderInfo=(E3dRenderInfo*)LClientData;


 switch(LXEv->type)
 {
  case Expose:
   LRenderInfo->WindowXSize=LW->core.width;LRenderInfo->WindowYSize=LW->core.height;

#ifdef OPENGL_CANVAS
   _GLRedrawCanvas(LRenderInfo);
#else
   {
    Display*		LDisplay;
    Window		LWindow;
    XExposeEvent*	LXExpEv=(XExposeEvent*)LXEv;

    LDisplay=XtDisplay(LW);
    LWindow=XtWindow(LW);

    if(_CanvasGC==(GC)NULL)
    {
     XGCValues	LGCValues;

     if((_CanvasGC=XCreateGC(LDisplay, LWindow, 0, &LGCValues))==(GC)NULL) return;
    }

    if(_Pixmap==(Pixmap)NULL)
    {
     _Pixmap=XCreatePixmap(LDisplay, LWindow, LW->core.width, LW->core.height, LW->core.depth);
    }
    XCopyArea(LDisplay, _Pixmap, LWindow, _CanvasGC, LXExpEv->x, LXExpEv->y, LXExpEv->width, LXExpEv->height, LXExpEv->x, LXExpEv->y);
   }
#endif	// OPENGL_CANVAS

  break;
 }
}


/*==============================================*/
/* Create dialog with 2DWindow			*/
/*==============================================*/
void E3d_PopupCanvasDialog(E3dRenderInfo* LRenderInfo)
{
 EguiArg	LArgs[32];
 EguiItem	LDialog, LI;
 EguiDialogRec*	LD;
 int		LArgCnt;
// EpWindow*	LPWindow;
 Widget		LFrameW;

 if(_CanvasDialog==NULL)
 {
#ifdef OPENGL_CANVAS
  if(E3dp_GLVisualId)
  {
// We must create a separate GLXContext for this window
//
   _GLXContext=glXCreateContext(EGUI_Display, E3dp_GLVisualId, None, GL_TRUE);


   if(_GLXContext==NULL)
   {
    E3dp_PrintMessage(0, 5000, "Couldn't create GLX context for preview window");
    return;
   }
  }
  else
  {
   printf("glXChooseVisual() Couldn't find appropriate visual\n");fflush(stdout);
   return;
  }
#else
  EguiVisual*	LVisual=EGUI_TruecolorVisual;
#endif




  LArgCnt=0;
  EguiSetArg(EguiNallowShellResize, True, LArgs, LArgCnt);
//  EguiSetArg(EguiNwmDecorations, EguiWM_DECOR_BORDER|EguiWM_DECOR_TITLE|EguiWM_DECOR_MINIMIZE, LArgs, LArgCnt);
  EguiSetArg(EguiNwmDecorations, EguiWM_DECOR_TITLE|EguiWM_DECOR_MINIMIZE, LArgs, LArgCnt);
  EguiSetArg(EguiNminXSize, 120, LArgs, LArgCnt);EguiSetArg(EguiNminYSize, 80, LArgs, LArgCnt);
  EguiSetArg(EguiNtitle, "EQUINOX-3D renderer", LArgs, LArgCnt);EguiSetArg(EguiNiconName, "Render", LArgs, LArgCnt);
  EguiSetArg(EguiNseparatorType, EguiSINGLE_LINE_TOP, LArgs, LArgCnt);
  EguiSetArg(EguiNworkAreaType, EguiNONE, LArgs, LArgCnt);
  EguiSetArg(EguiNstretchButtons, False, LArgs, LArgCnt);
  if((LDialog=EGUI_CreateDialog(EguiDIALOG_TEMPLATE, "Render", E3dp_TopShell, LArgs, LArgCnt))==NULL) return;

  _CanvasDialog=LDialog;

  LI=EGUI_CreateToggleButton("Interactive", LDialog, NULL, 0);
  EGUI_AddCallback(LI, EguiNvalueChangedCallback, _DCB_Interactive, (EPointer)LRenderInfo);

//  LI=EGUI_CreatePushButton("Save", LDialog, NULL, 0);
//  EGUI_AddCallback(LI, EguiNactivateCallback, _DCB_Save, (EPointer)LRenderInfo);

  LI=EGUI_CreatePushButton("Save", LDialog, NULL, 0);
  EGUI_AddCallback(LI, EguiNactivateCallback, _DCB_SaveAs, (EPointer)LRenderInfo);

  LI=EGUI_CreatePushButton("Stop", LDialog, NULL, 0);
  EGUI_AddCallback(LI, EguiNactivateCallback, _DCB_Stop, (EPointer)LRenderInfo);

  LArgCnt=0;
  EguiSetArg(EguiNshowAsDefault, True, LArgs, LArgCnt);
  LI=EGUI_CreatePushButton("Close", LDialog, LArgs, LArgCnt);
  EGUI_AddCallback(LI, EguiNactivateCallback, _DCB_Close, (EPointer)LRenderInfo);

  LD=(EguiDialogRec*)LDialog;

  EXtStart;
  EXtSetArg(XeNworkAreaMarginWidth, 3);EXtSetArg(XeNworkAreaMarginHeight, 3);
  XtSetValues(LD->Dialog.MessageBoxW, Args, ArgCnt);



// XeFrame and XeDraw
//
  EXtStart;
  EXtSetArg(XeNtopShadowColor, EGUI_TopShadowColor);
  EXtSetArg(XeNbottomShadowColor, EGUI_BottomShadowColor);
  EXtSetArg(XeNshadowType, XeSHADOW_IN);
  EXtSetArg(XeNshadowThickness, 2);
  EXtSetArg(XeNbackground, EGUI_BackgroundColor);
  EXtSetArg(XeNforeground, EGUI_ForegroundColor);
  EXtSetArg(XeNfontList, EGUI_LabelFontList);
  EXtSetArg(XeNadjustEntryWidths, False);
  EXtSetArg(XeNadjustEntryHeights, False);
//  LFrameW=XtCreateManagedWidget(" ", xeFrameWidgetClass, EGUI_DialogGetChild(LDialog, EguiDIALOG_WORK_AREA), Args, ArgCnt);
  LFrameW=XtCreateManagedWidget(" ", xeFrameWidgetClass, EGUI_DialogGetChild(LDialog, EguiDIALOG_MAIN), Args, ArgCnt);


// Create image to render into
//
  if(LRenderInfo->CreateHDRImage) LRenderInfo->CanvasImage=Ep_ImageAllocate(LRenderInfo->ImageXSize, LRenderInfo->ImageYSize, EpPixelRGBA8|EpPixelRGBAf32);
  else LRenderInfo->CanvasImage=Ep_ImageAllocate(LRenderInfo->ImageXSize, LRenderInfo->ImageYSize, EpPixelRGBA8);

  LRenderInfo->WorkingImageXSize=LRenderInfo->ImageXSize;LRenderInfo->WorkingImageYSize=LRenderInfo->ImageYSize;


#ifdef OPENGL_CANVAS
  EXtStart;
  EXtSetArg(XeNwidth, LRenderInfo->ImageXSize);EXtSetArg(XeNheight, LRenderInfo->ImageYSize);
  EXtSetArg(XeNvisualInfo, E3dp_GLVisualId);
  _CanvasW=XtCreateManagedWidget(" ", xeGLDrawWidgetClass, LFrameW, Args, ArgCnt);
  XtAddEventHandler(_CanvasW, ExposureMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|Button1MotionMask|Button2MotionMask|Button3MotionMask|KeyPressMask|KeyReleaseMask, True, _EH_CanvasWidget, (XtPointer)LRenderInfo);
#else
  EXtStart;
  EXtSetArg(XeNwidth, LRenderInfo->ImageXSize);EXtSetArg(XeNheight, LRenderInfo->ImageYSize);
  if(LVisual)
  {
   EXtSetArg(XeNvisualInfo, LVisual->X_VisualInfo);EXtSetArg(XeNcolormap, LVisual->X_Colormap);
  }
  EXtSetArg(XeNforeground, EGUI_ForegroundColor);
  _CanvasW=XtCreateManagedWidget(" ", xeDrawWidgetClass, LFrameW, Args, ArgCnt);
  XtAddEventHandler(_CanvasW, ExposureMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|Button1MotionMask|Button2MotionMask|Button3MotionMask|KeyPressMask|KeyReleaseMask, True, _EH_CanvasWidget, (XtPointer)LRenderInfo);
#endif	// OPENGL_CANVAS

/*
  Ep_Prefs.WindowDisplayType=EpWDT_TRUECOLOR;
  if((LPWindow=Ep_CreateViewWindow("Preview", EGUI_DialogGetChild(LDialog, EguiDIALOG_WORK_AREA), EpWDT_TRUECOLOR, FALSE, EpWFLAG_INNER_FRAME))!=NULL)
  {
   _CanvasWindow=LPWindow;
   LPWindow->Picture=Ep_PictureCreate(LXSize, LYSize, 0);
   LPWindow->XSize=LXSize;
   LPWindow->YSize=LYSize;

   LPWindow->InfoPanelFlags=EpINFOP_CHANNELS;
   Ep_SetInfoPanel(LPWindow, FALSE);
   Ep_SetPictureParameters(LPWindow);
   Ep_InfoPanelUpdate(LPWindow);
   Ep_SetPictureWindowLayout(LPWindow);
   EXtStart;
   EXtSetArg(XtNwidth, LPWindow->XSize);EXtSetArg(XtNheight, LPWindow->YSize);
   XtSetValues(LPWindow->MainW, Args, ArgCnt);

   XtManageChild(LPWindow->MainW);
   if(LPWindow->Picture)
   {
printf("PicOK\n");fflush(stdout);
    Ep_WindowSetDrawWidget(LPWindow);
    Ep_WindowCenterPicture(LPWindow, LPWindow->Picture);
    Ep_WindowFullRedraw(LPWindow);
   }
  }
 }
 else
 {
  if(_CanvasWindow)
  {
   EpPicture*	LPicture=_CanvasWindow->Picture;

   if((LPicture->Image->XSize!=LXSize)||(LPicture->Image->YSize!=LYSize))
   {
    Ep_PictureResize(LPicture, LXSize, LYSize, EpIMGBUFFER_RGBA8_ARRAY, TRUE);
   }
  }
*/

  EGUI_RaiseShell(_CanvasDialog);
  EGUI_CenterShellToItem(_CanvasDialog, Eqx_TopShell);
 }
 else
 {
  unsigned int	LXSize=LRenderInfo->ImageXSize, LYSize=LRenderInfo->ImageYSize;


  EXtStart;
  EXtSetArg(XeNwidth, LXSize);EXtSetArg(XeNheight, LYSize);
  XtSetValues(_CanvasW, Args, ArgCnt);

// Resize Image if necessary
//
  if(LRenderInfo->CanvasImage)
  {
   EpImage*	LImage=LRenderInfo->CanvasImage;

   if((LImage->XSize!=LXSize)||(LImage->YSize!=LYSize))
   {
    if(LRenderInfo->CreateHDRImage) Ep_ImageResize(LImage, LXSize, LYSize, EpPixelRGBA8|EpPixelRGBAf32, TRUE);
    else Ep_ImageResize(LImage, LXSize, LYSize, EpPixelRGBA8, TRUE);
   }
  }
  else
  {
   if(LRenderInfo->CreateHDRImage) LRenderInfo->CanvasImage=Ep_ImageAllocate(LXSize, LYSize, EpPixelRGBA8|EpPixelRGBAf32);
   else LRenderInfo->CanvasImage=Ep_ImageAllocate(LXSize, LYSize, EpPixelRGBA8);
  }


// Resize Pixmap if necessary
//
#ifndef OPENGL_CANVAS
  if(_Pixmap)
  {
   if((LRenderInfo->ImageXSize!=LXSize)||(LRenderInfo->ImageYSize!=LYSize))
   {
    XFreePixmap(XtDisplay(_CanvasW), _Pixmap);
    _Pixmap=XCreatePixmap(XtDisplay(_CanvasW), XtWindow(_CanvasW), LXSize, LYSize, _CanvasW->core.depth);
   }
  }
  else _Pixmap=XCreatePixmap(XtDisplay(_CanvasW), XtWindow(_CanvasW), LXSize, LYSize, _CanvasW->core.depth);
#endif

  LRenderInfo->WorkingImageXSize=LXSize;LRenderInfo->WorkingImageYSize=LYSize;

  if(EGUI_ShellDisplayStatus(_CanvasDialog)!=EguiDISPLAYED)
  {
   EGUI_RaiseShell(_CanvasDialog);
   EGUI_CenterShellToItem(_CanvasDialog, Eqx_TopShell);
  }
 }


// Set up _3DWindow
//
 {
  E3dWindow*	L3DWindow=&_3DWindow;

  _3DPanel.EGLXContext=_GLXContext;
  memcpy(&(L3DWindow->Settings), &(E3dp_Main3DWindow->Settings), sizeof(E3dWindowSettings));

  L3DWindow->DisplayMode=E3dp_Main3DWindow->DisplayMode;
  L3DWindow->ViewMode=E3dVM_PERSPECTIVE;
  L3DWindow->XSize=LRenderInfo->WindowXSize;
  L3DWindow->YSize=LRenderInfo->WindowYSize;

  L3DWindow->AspectRatio=(float)(L3DWindow->XSize)/(float)(L3DWindow->YSize);

  L3DWindow->XWindow=XtWindow(_CanvasW);
 }


 _CanvasUp=TRUE;

 _Plugin->LockCount=1;


// Ep_2DWindowFitToItsPicture(_CanvasWindow, 0, 0, TRUE);
}



#define NUMDIM  3
#define RIGHT   0
#define LEFT    1
#define MIDDLE  2

/*==============================================================*/
/* Determine whether a ray hits an axis-aligned bounding box	*/
/*==============================================================*/
char E3d_RayHitBoundingBox(E3dCoordinate origin[NUMDIM], E3dCoordinate dir[NUMDIM], E3dCoordinate minB[NUMDIM], E3dCoordinate maxB[NUMDIM])
{
 char		inside = TRUE;
 char		quadrant[NUMDIM];
 register int	i;
 int		whichPlane;
 E3dCoordinate	maxT[NUMDIM];
 E3dCoordinate	candidatePlane[NUMDIM];

// Find candidate planes; this loop can be avoided if
// rays cast all from the eye (assume perpsective view)
//
 for(i=0; i<NUMDIM; i++)
 {
   if(origin[i] < minB[i])
   {
     quadrant[i] = LEFT;
     candidatePlane[i] = minB[i];
     inside = FALSE;
   } else if (origin[i] > maxB[i])
	  {
	    quadrant[i] = RIGHT;
	    candidatePlane[i] = maxB[i];
	    inside = FALSE;
   } else quadrant[i] = MIDDLE;
 }


// Ray origin inside bounding box
//
 if(inside) return(TRUE);


// Calculate T distances to candidate planes
//
 for(i = 0; i < NUMDIM; i++)
 {
  if(quadrant[i] != MIDDLE && dir[i] !=0.) maxT[i] = (candidatePlane[i]-origin[i]) / dir[i];
  else maxT[i] = -1.0;
 }

// Get largest of the maxT's for final choice of intersection
//
 whichPlane = 0;
 for(i = 1; i < NUMDIM; i++) if(maxT[whichPlane] < maxT[i]) whichPlane = i;

// Check if final candidate is actually inside the box
//
 if(maxT[whichPlane] < 0.0) return(FALSE);
 for(i = 0; i < NUMDIM; i++)
 {
   if(whichPlane != i)
   {
    E3dCoordinate	LCoord;

     LCoord = origin[i] + maxT[whichPlane] * dir[i];
     if(LCoord < minB[i] || LCoord > maxB[i]) return(FALSE);
   }
 }

 return(TRUE);	// Ray hits box
}       



char E3d_RayIntersectBoundingBox(E3dCoordinate origin[NUMDIM], E3dCoordinate dir[NUMDIM], E3dCoordinate minB[NUMDIM], E3dCoordinate maxB[NUMDIM], E3dCoordinate coord[NUMDIM])
{
 char		inside = TRUE;
 char		quadrant[NUMDIM];
 register int	i;
 int		whichPlane;
 E3dCoordinate	maxT[NUMDIM];
 E3dCoordinate	candidatePlane[NUMDIM];

// Find candidate planes; this loop can be avoided if
// rays cast all from the eye(assume perpsective view)
//
 for (i=0; i<NUMDIM; i++)
 {
   if(origin[i] < minB[i])
   {
     quadrant[i] = LEFT;
     candidatePlane[i] = minB[i];
     inside = FALSE;
   } else if (origin[i] > maxB[i])
	  {
	    quadrant[i] = RIGHT;
	    candidatePlane[i] = maxB[i];
	    inside = FALSE;
   } else quadrant[i] = MIDDLE;
 }


// Ray origin inside bounding box
//
 if(inside)
 {
  coord = origin;
  return(TRUE);
 }


// Calculate T distances to candidate planes
//
 for(i = 0; i < NUMDIM; i++)
 {
  if(quadrant[i] != MIDDLE && dir[i] !=0.) maxT[i] = (candidatePlane[i]-origin[i]) / dir[i];
  else maxT[i] = -1.0;
 }

// Get largest of the maxT's for final choice of intersection
//
 whichPlane = 0;
 for (i = 1; i < NUMDIM; i++) if(maxT[whichPlane] < maxT[i]) whichPlane = i;

// Check if final candidate is actually inside the box
//
 if(maxT[whichPlane] < 0.) return (FALSE);
 for(i = 0; i < NUMDIM; i++)
 {
   if(whichPlane != i)
   {
     coord[i] = origin[i] + maxT[whichPlane] *dir[i];
     if(coord[i] < minB[i] || coord[i] > maxB[i]) return (FALSE);
   }
   else coord[i] = candidatePlane[i];
 }

 return(TRUE);	// Ray hits box
}       


/*==============================================*/
/* Compute color of a point			*/
/*==============================================*/
void E3d_ShadePoint(E3dScene* LScene, E3dSurfacePoint* LPoint, E3dRTriangle* LRTriangle, E3dCoordinate* LUV, E3dMaterial* LMaterial, EcRGBAfColor* LIllumination)
{
 if(LMaterial->NumOf2DTextures==0)
 {
  E3d_IlluminatePoint(LScene, LPoint, LMaterial, LIllumination);
 }
 else
 {
  E3d2DTexture*		L2DTexture=LMaterial->Textures2D[0];
  E3dCoordinate		LU=LUV[0], LV=LUV[1];
  E3dCoordinate		LNUV=1.0-LU-LV;
  float			LFS, LFT;
  float			LSRepeatsF=(float)(L2DTexture->SCount), LTRepeatsF=(float)(L2DTexture->TCount);


  LFS=LNUV*LRTriangle->S[0]+LRTriangle->S[1]*LU+LRTriangle->S[2]*LV;
  LFT=LNUV*LRTriangle->T[0]+LRTriangle->T[1]*LU+LRTriangle->T[2]*LV;

// Texture repeat/tiling/clamping
//
  if((LFS>=0.0)&&(LFT>=0.0)&&(LFS<=LSRepeatsF)&&(LFT<=LTRepeatsF))
  {
   E3dMaterial		LTmpMaterial;
   EpImage*		LImage=L2DTexture->Image;
   EpRGBA8Pixel*	LRGBA8Image;
   EpRGBA8Pixel*	LRGBA8Pixel;
   float		L2DTxtXSizeF, L2DTxtYSizeF,
			LAmbientFactorI, LAmbientFactor2, LDiffuseFactorI,
			LDiffuseFactor2, LSpecularFactorI, LSpecularFactor2;
   int			L2DTxtXSize, L2DTxtYSize, LTX, LTY;
   float		LTFR, LTFG, LTFB;
   int			LTC;


// E3d_Scene2DTextureReadImage() (A) should at least set the default texture Image,
// so if it's still NULL we are in trouble...
//
   if(LImage==NULL) return;


   memcpy(&LTmpMaterial, LMaterial, sizeof(E3dMaterial));

// Texture blending factors might be more than 1.0...
//
   LAmbientFactorI=1.0-L2DTexture->AmbientFactor;if(LAmbientFactorI<0.0) LAmbientFactorI=0.0;
   LAmbientFactor2=L2DTexture->AmbientFactor*0.003915686;		// 0.003915686 = 1/255

   LDiffuseFactorI=1.0-L2DTexture->DiffuseFactor;if(LDiffuseFactorI<0.0) LDiffuseFactorI=0.0;
   LDiffuseFactor2=L2DTexture->DiffuseFactor*0.003915686;

   LSpecularFactorI=1.0-L2DTexture->SpecularFactor;if(LSpecularFactorI<0.0) LSpecularFactorI=0.0;
   LSpecularFactor2=L2DTexture->SpecularFactor*0.003915686;



   LRGBA8Image=LImage->RGBA8Image;

   L2DTxtXSize=LImage->XSize;L2DTxtXSizeF=(float)L2DTxtXSize;
   L2DTxtYSize=LImage->YSize;L2DTxtYSizeF=(float)L2DTxtYSize;

   LTC=LFS;LFS-=(float)LTC;		// Modulo...
   LTC=LFT;LFT-=(float)LTC;		// Modulo...

   LTX=(int)(LFS*L2DTxtXSizeF);
   LTY=(int)(LFT*L2DTxtYSizeF);
//printf("1-ST: %f, %f\n", LFS, LFT);fflush(stdout);

   LRGBA8Pixel=LRGBA8Image+LTX+LTY*L2DTxtXSize;
   LTFR=(float)(LRGBA8Pixel->R);
   LTFG=(float)(LRGBA8Pixel->G);
   LTFB=(float)(LRGBA8Pixel->B);

   LTmpMaterial.Ambient.R=LMaterial->Ambient.R*LAmbientFactorI+LTFR*LAmbientFactor2;
   LTmpMaterial.Ambient.G=LMaterial->Ambient.G*LAmbientFactorI+LTFG*LAmbientFactor2;
   LTmpMaterial.Ambient.B=LMaterial->Ambient.B*LAmbientFactorI+LTFB*LAmbientFactor2;

   LTmpMaterial.Diffuse.R=LMaterial->Diffuse.R*LDiffuseFactorI+LTFR*LDiffuseFactor2;
   LTmpMaterial.Diffuse.G=LMaterial->Diffuse.G*LDiffuseFactorI+LTFG*LDiffuseFactor2;
   LTmpMaterial.Diffuse.B=LMaterial->Diffuse.B*LDiffuseFactorI+LTFB*LDiffuseFactor2;

   LTmpMaterial.Specular.R=LMaterial->Specular.R*LSpecularFactorI+LTFR*LSpecularFactor2;
   LTmpMaterial.Specular.G=LMaterial->Specular.G*LSpecularFactorI+LTFG*LSpecularFactor2;
   LTmpMaterial.Specular.B=LMaterial->Specular.B*LSpecularFactorI+LTFB*LSpecularFactor2;

   E3d_IlluminatePoint(LScene, LPoint, &LTmpMaterial, LIllumination);
  }
  else
  {
   E3d_IlluminatePoint(LScene, LPoint, LMaterial, LIllumination);
  }
 }
}


/*==============================================*/
/* Compute color of a point			*/
/*==============================================*/
void E3d_ShadePointShadows(E3dScene* LScene, E3dSurfacePoint* LPoint, E3dRTriangle* LRTriangle, E3dCoordinate* LUV, E3dMaterial* LMaterial, EcRGBAfColor* LIllumination)
{
 if(LMaterial->NumOf2DTextures==0)
 {
  E3d_IlluminatePointShadowing(LScene, LPoint, LMaterial, LIllumination, LRTriangle);
 }
 else
 {
  E3d2DTexture*		L2DTexture=LMaterial->Textures2D[0];
  E3dCoordinate		LU=LUV[0], LV=LUV[1];
  E3dCoordinate		LNUV=1.0-LU-LV;
  float			LFS, LFT;
  float			LSRepeatsF=(float)(L2DTexture->SCount), LTRepeatsF=(float)(L2DTexture->TCount);


  LFS=LNUV*LRTriangle->S[0]+LRTriangle->S[1]*LU+LRTriangle->S[2]*LV;
  LFT=LNUV*LRTriangle->T[0]+LRTriangle->T[1]*LU+LRTriangle->T[2]*LV;

// Texture repeat/tiling/clamping
//
  if((LFS>=0.0)&&(LFT>=0.0)&&(LFS<=LSRepeatsF)&&(LFT<=LTRepeatsF))
  {
   E3dMaterial		LTmpMaterial;
   EpImage*		LImage=L2DTexture->Image;
   EpRGBA8Pixel*	LRGBA8Image;
   EpRGBA8Pixel*	LRGBA8Pixel;
   float		L2DTxtXSizeF, L2DTxtYSizeF,
			LAmbientFactorI, LAmbientFactor2, LDiffuseFactorI,
			LDiffuseFactor2, LSpecularFactorI, LSpecularFactor2;
   int			L2DTxtXSize, L2DTxtYSize, LTX, LTY;
   float		LTFR, LTFG, LTFB;
   int			LTC;


// E3d_Scene2DTextureReadImage() (A) should at least set the default texture Image,
// so if it's still NULL we are in trouble...
//
   if(LImage==NULL) return;


   memcpy(&LTmpMaterial, LMaterial, sizeof(E3dMaterial));


// Texture blending factors might be more than 1.0...
//
   LAmbientFactorI=1.0-L2DTexture->AmbientFactor;if(LAmbientFactorI<0.0) LAmbientFactorI=0.0;
   LAmbientFactor2=L2DTexture->AmbientFactor*0.003915686;		// 0.003915686 = 1/255

   LDiffuseFactorI=1.0-L2DTexture->DiffuseFactor;if(LDiffuseFactorI<0.0) LDiffuseFactorI=0.0;
   LDiffuseFactor2=L2DTexture->DiffuseFactor*0.003915686;

   LSpecularFactorI=1.0-L2DTexture->SpecularFactor;if(LSpecularFactorI<0.0) LSpecularFactorI=0.0;
   LSpecularFactor2=L2DTexture->SpecularFactor*0.003915686;



   LRGBA8Image=LImage->RGBA8Image;

   L2DTxtXSize=LImage->XSize;L2DTxtXSizeF=(float)L2DTxtXSize;
   L2DTxtYSize=LImage->YSize;L2DTxtYSizeF=(float)L2DTxtYSize;


   LTC=LFS;LFS-=(float)LTC;		// Modulo...
   LTC=LFT;LFT-=(float)LTC;		// Modulo...

   LTX=(int)(LFS*L2DTxtXSizeF);
   LTY=(int)(LFT*L2DTxtYSizeF);
//printf("1-ST: %f, %f\n", LFS, LFT);fflush(stdout);

   LRGBA8Pixel=LRGBA8Image+LTX+LTY*L2DTxtXSize;
   LTFR=(float)(LRGBA8Pixel->R);
   LTFG=(float)(LRGBA8Pixel->G);
   LTFB=(float)(LRGBA8Pixel->B);

   LTmpMaterial.Ambient.R=LMaterial->Ambient.R*LAmbientFactorI+LTFR*LAmbientFactor2;
   LTmpMaterial.Ambient.G=LMaterial->Ambient.G*LAmbientFactorI+LTFG*LAmbientFactor2;
   LTmpMaterial.Ambient.B=LMaterial->Ambient.B*LAmbientFactorI+LTFB*LAmbientFactor2;

   LTmpMaterial.Diffuse.R=LMaterial->Diffuse.R*LDiffuseFactorI+LTFR*LDiffuseFactor2;
   LTmpMaterial.Diffuse.G=LMaterial->Diffuse.G*LDiffuseFactorI+LTFG*LDiffuseFactor2;
   LTmpMaterial.Diffuse.B=LMaterial->Diffuse.B*LDiffuseFactorI+LTFB*LDiffuseFactor2;

   LTmpMaterial.Specular.R=LMaterial->Specular.R*LSpecularFactorI+LTFR*LSpecularFactor2;
   LTmpMaterial.Specular.G=LMaterial->Specular.G*LSpecularFactorI+LTFG*LSpecularFactor2;
   LTmpMaterial.Specular.B=LMaterial->Specular.B*LSpecularFactorI+LTFB*LSpecularFactor2;

   E3d_IlluminatePointShadowing(LScene, LPoint, &LTmpMaterial, LIllumination, LRTriangle);
  }
  else
  {
   E3d_IlluminatePointShadowing(LScene, LPoint, LMaterial, LIllumination, LRTriangle);
  }
 }
}



void E3d_ReflectRay(E3dRay* LRay, E3dCoordinate LNormalX, E3dCoordinate LNormalY, E3dCoordinate LNormalZ)
{
 E3dCoordinate	LD, LDirX, LDirY, LDirZ;

 LDirX=-LRay->DirX;
 LDirY=-LRay->DirY;
 LDirZ=-LRay->DirZ;

 LD=LNormalX*LDirX+LNormalY*LDirY+LNormalZ*LDirZ;
// LD=fabs(LNormalX*LDirX+LNormalY*LDirY+LNormalZ*LDirZ);
//Printf(" LD %f ", LD);fflush(stdout);
 LRay->DirX=(LNormalX*LD-LDirX)*2.0+LDirX;
 LRay->DirY=(LNormalY*LD-LDirY)*2.0+LDirY;
 LRay->DirZ=(LNormalZ*LD-LDirZ)*2.0+LDirZ;

/*
// Environtment mapping
//
 E3dCoordinate	LB, LC, LD, LM, LP, LDirX, LDirY, LDirZ;
 E3d3DPosition	LPV, LPUV;

 LPUV.X=-LDirX;
 LPUV.Y=-LDirY;
 LPUV.Z=-LDirZ;
 LB=2.0*(LPUV.X*LPosition->X+LPUV.Y*LPosition->Y+LPUV.Z*LPosition->Z);

// LC = LPosition->X*LPosition->X + LPosition->Y*LPosition->Y + LPosition->Z*LPosition->Z - EnvSphereRadius^2;

 LC = LPosition->X*LPosition->X + LPosition->Y*LPosition->Y + LPosition->Z*LPosition->Z - EnvSphereRadius^2;

 LM=LB*LB-4.0*LC;
 if(LM>=0.0)
 {
  LM=sqrt(LM);
  LP=-LB+LM;

  if(LP<0.0) LP=-LB-LM;

// In the quadratic equation, a = LPUVX^2+LPUVY^2+LPUVZ^2 = 1.0,
// because LPUV is a unit vector, so the /(2*a) becomes just /2, or *0.5
//
  LP*=0.5;

// Get the intersection point using the LP parameter
//
	MULx.xyz  VF03,VF03,VF12x	NOP				; LPUV[XYZ] *= LP
	ADD.xyz	  VF03,VF03,VF29	NOP				; LPUV[XYZ] += LGV[XYZ]

	MULw.xyz  VF03,VF03,VF07w	NOP				; LPUV[XYZ] *= CDs_Sph (1.0/EnvSphereRadius*0.5)
	ADDy.xyz  VF03,VF03,VF00y	NOP				; LPUV[XYZ] += 0.5
	MULz.y	  VF03,VF03,VF00z	NOP				; LPUV[Y] *= -1.0
 }
*/



}


static E3dLight**	_Lights;
static unsigned int	_NumOfLights;
static E3dCamera*	_Camera;


/*======================================================*/
/* Trace a ray and get its illumination contribution	*/
/*======================================================*/
int E3d_TraceRayOpaque(E3dScene* LScene, E3dRay* LRay, int LRayIter, EcRGBAfColor* LColor, E3dRenderInfo* LRenderInfo)
{
 EcRGBAfColor		LIllumination;
 E3dPolyGroup*		LPolyGroup;
 E3dSurfacePoint	LPoint;
 E3dCoordinate		LUV[2], LU, LV;
 E3dRTriangle*		LRTriangle;
 void*			LFace=NULL;
 float			LIntensityR, LIntensityG, LIntensityB;
 E3dCoordinate		LFX, LFY, LFZ;
 int			LHits=0;


 LIntensityR=1.0;
 LIntensityG=1.0;
 LIntensityB=1.0;

 while((--LRayIter)>0)
 {
  if((LRTriangle=E3d_RayIntersectVoxelsBackFaceCull(LScene, LRay, LUV, LFace))!=NULL)
  {
   E3dMaterial* LMaterial;


   LU=LUV[0], LV=LUV[1];

   LPolyGroup=LRTriangle->PolyGroup;

   LFace=LRTriangle->Face;

   LPoint.X=LRay->HitX;
   LPoint.Y=LRay->HitY;
   LPoint.Z=LRay->HitZ;


// Interpolate normal
//
   if(LPolyGroup->Flags&E3dUSE_VERTEX_NORMALS)
   {
    E3dCoordinate	LNUV=1.0-LU-LV, LL;

    LFX=LNUV*LRTriangle->Normal0.X+LRTriangle->Normal1.X*LU+LRTriangle->Normal2.X*LV;
    LFY=LNUV*LRTriangle->Normal0.Y+LRTriangle->Normal1.Y*LU+LRTriangle->Normal2.Y*LV;
    LFZ=LNUV*LRTriangle->Normal0.Z+LRTriangle->Normal1.Z*LU+LRTriangle->Normal2.Z*LV;

// We must re-normalize the interpolated normal
//
    LL=sqrt(LFX*LFX + LFY*LFY + LFZ*LFZ);if(LL>0.0f) LL=1.0/LL;
    LPoint.NormalX=LFX*LL;
    LPoint.NormalY=LFY*LL;
    LPoint.NormalZ=LFZ*LL;
   }
   else
   {
    LPoint.NormalX=LRTriangle->Normal.X;
    LPoint.NormalY=LRTriangle->Normal.Y;
    LPoint.NormalZ=LRTriangle->Normal.Z;
   }
   LMaterial=LPolyGroup->DrawingMaterial;

   if(LRenderInfo->Shadows) E3d_ShadePointShadows(LScene, &LPoint, LRTriangle, LUV, LMaterial, &LIllumination);
   else E3d_ShadePoint(LScene, &LPoint, LRTriangle, LUV, LMaterial, &LIllumination);


   LColor->R+=LIntensityR*LIllumination.R;
   LColor->G+=LIntensityG*LIllumination.G;
   LColor->B+=LIntensityB*LIllumination.B;

   LHits++;

   if(LMaterial->Reflectivity==0.0) break;

   LIntensityR*=LMaterial->Reflectivity*LMaterial->Reflective.R;
   LIntensityG*=LMaterial->Reflectivity*LMaterial->Reflective.G;
   LIntensityB*=LMaterial->Reflectivity*LMaterial->Reflective.B;

   E3d_ReflectRay(LRay, LPoint.NormalX, LPoint.NormalY, LPoint.NormalZ);

   LRay->X=LPoint.X;
   LRay->Y=LPoint.Y;
   LRay->Z=LPoint.Z;
  }
  else break;
 }
 return(LHits);
}


/*======================================================*/
/* Refract a ray crossing an interface			*/
/* Notes: esrc/libsrc/E3D/Notes/Refract.sxi		*/
/*======================================================*/
EBool E3d_RefractRay(E3dRay* LRay, E3dRay* LNewRay, E3dCoordinate LNX, E3dCoordinate LNY, E3dCoordinate LNZ, float LRefrIndex12)
{
 E3dCoordinate	La, Lci,
		LSiX, LSiY, LSiZ,
		LIX=LRay->DirX, LIY=LRay->DirY, LIZ=LRay->DirZ;


// ci = N . -I
//
 Lci=-LNX*LIX - LNY*LIY - LNZ*LIZ;

// Si = -I - Ci = -I - ci * N
//
 LSiX= -LIX - Lci*LNX;
 LSiY= -LIY - Lci*LNY;
 LSiZ= -LIZ - Lci*LNZ;

 La=1.0-LRefrIndex12*LRefrIndex12 * (LSiX*LSiX + LSiY*LSiY + LSiZ*LSiZ);

 if(La<0.0) return(FALSE);

// T= sqrt(a)*-LNormal - LRefrIndex12 * Si
//
 La=sqrt(La);

 LNewRay->DirX=-La*LNX - LRefrIndex12 * LSiX;
 LNewRay->DirY=-La*LNY - LRefrIndex12 * LSiY;
 LNewRay->DirZ=-La*LNZ - LRefrIndex12 * LSiZ;


 return(TRUE);
}


/*======================================================*/
/* Trace a ray and get its illumination contribution	*/
/*======================================================*/
int E3d_TraceRay(E3dScene* LScene, E3dRay* LRay, int LRayIter, EcRGBAfColor* LColor, E3dRenderInfo* LRenderInfo)
{
 E3dMaterial**		LMaterialStack=LRay->MaterialStack;
 E3dPolyGroup*		LPolyGroup;
 EcRGBAfColor		LIllumination;
 E3dSurfacePoint	LPoint;
 E3dCoordinate		LUV[2], LU, LV;
 E3dRTriangle*		LRTriangle;
 void*			LFace=NULL;
 E3dCoordinate		LFX, LFY, LFZ;
 E3dRay			LRayStack[42];
 int			LRayStackPtr=0;
 int			LHits=0;



 while(1)
 {
  if((LRTriangle=E3d_RayIntersectVoxels(LScene, LRay, LUV, LFace))!=NULL)
  {
   E3dMaterial* LMaterial;

   LHits++;

   LU=LUV[0], LV=LUV[1];

//assert(LT>0.00001);

   LPolyGroup=LRTriangle->PolyGroup;

   LFace=LRTriangle->Face;

   LPoint.X=LRay->HitX;
   LPoint.Y=LRay->HitY;
   LPoint.Z=LRay->HitZ;


// Absorption
//
   LMaterial=LMaterialStack[LRay->MaterialIndex];
   if(LMaterial)
   {
    float	LDistSqr=LRay->LastT*LRay->LastT;
    float	LAbsorptionR=1.0+LMaterial->Absorption.R*LDistSqr,
		LAbsorptionG=1.0+LMaterial->Absorption.G*LDistSqr,
		LAbsorptionB=1.0+LMaterial->Absorption.B*LDistSqr;

    LRay->IntensityR/=LAbsorptionR;
    LRay->IntensityG/=LAbsorptionG;
    LRay->IntensityB/=LAbsorptionB;
   }


// Interpolate normal
//
   if(LPolyGroup->Flags&E3dUSE_VERTEX_NORMALS)
   {
    E3dCoordinate	LNUV=1.0-LU-LV, LL;

    LFX=LNUV*LRTriangle->Normal0.X+LRTriangle->Normal1.X*LU+LRTriangle->Normal2.X*LV;
    LFY=LNUV*LRTriangle->Normal0.Y+LRTriangle->Normal1.Y*LU+LRTriangle->Normal2.Y*LV;
    LFZ=LNUV*LRTriangle->Normal0.Z+LRTriangle->Normal1.Z*LU+LRTriangle->Normal2.Z*LV;

// We must re-normalize the interpolated normal
//
    LL=sqrt(LFX*LFX + LFY*LFY + LFZ*LFZ);if(LL>0.0f) LL=1.0/LL;
    LPoint.NormalX=LFX*LL;
    LPoint.NormalY=LFY*LL;
    LPoint.NormalZ=LFZ*LL;
   }
   else
   {
    LPoint.NormalX=LRTriangle->Normal.X;
    LPoint.NormalY=LRTriangle->Normal.Y;
    LPoint.NormalZ=LRTriangle->Normal.Z;
   }
   LMaterial=LPolyGroup->DrawingMaterial;

// Only do shading for front-face hits
//
   if(LRay->FrontFaceHit)
   {
    if(LRenderInfo->Shadows) E3d_ShadePointShadows(LScene, &LPoint, LRTriangle, LUV, LMaterial, &LIllumination);
    else E3d_ShadePoint(LScene, &LPoint, LRTriangle, LUV, LMaterial, &LIllumination);
   }
   else
   {
// Back-face was hit, no shading
//
    LIllumination.R=0.0;
    LIllumination.G=0.0;
    LIllumination.B=0.0;

// Reverse the normal for reflecting or refracting the ray
//
    LPoint.NormalX*=-1.0;
    LPoint.NormalY*=-1.0;
    LPoint.NormalZ*=-1.0;
   }

   if((LRayIter>0)&&(LMaterial->Transparency>0.0))
   {
    E3dMaterial*	LOtherMaterial;
    E3dRay		LTRay;
    float		LRefrIndex1, LRefrIndex2;


    if(LRay->FrontFaceHit)
    {
// If ray hit a front-face (entering a Material)
// - LRefrIndex1 = Ray's current Material's RefractiveIndex
// - LRefrIndex2 = LMaterial's RefractiveIndex
//
     LOtherMaterial=LMaterialStack[LRay->MaterialIndex];

     if(LOtherMaterial) LRefrIndex1=LOtherMaterial->RefractiveIndex;
     else LRefrIndex1=1.0;

     LRefrIndex2 = LMaterial->RefractiveIndex;
    }
    else
    {
// If ray hit a back-face (exiting a Material)
// LRefrIndex1 = LMaterial's RefractiveIndex
// LRefrIndex2 = Ray's last Material's RefractiveIndex
//
     LRefrIndex1 = LMaterial->RefractiveIndex;

     LOtherMaterial=LMaterialStack[LRay->MaterialIndex];
     if(LOtherMaterial) LRefrIndex2=LOtherMaterial->RefractiveIndex;
     else LRefrIndex2=1.0;
    }


// RefractRay() returns FALSE if there's a total internal
// reflection (ray hits the surface below critical the angle).
// If that occurs, we won't transmit a ray.
//
//
    if(E3d_RefractRay(LRay, &LTRay, LPoint.NormalX, LPoint.NormalY, LPoint.NormalZ, LRefrIndex1 / LRefrIndex2))
    {
     E3dRay*		LNewRay;
     float		LTransparency=LMaterial->Transparency, LOpacity=1.0-LTransparency;


     LColor->R+=(LRay->IntensityR*LIllumination.R*LOpacity);
     LColor->G+=(LRay->IntensityG*LIllumination.G*LOpacity);
     LColor->B+=(LRay->IntensityB*LIllumination.B*LOpacity);

     LRayStackPtr++;
     LNewRay=LRayStack+LRayStackPtr;
     LNewRay->X=LPoint.X;
     LNewRay->Y=LPoint.Y;
     LNewRay->Z=LPoint.Z;
     LNewRay->DirX=LTRay.DirX;
     LNewRay->DirY=LTRay.DirY;
     LNewRay->DirZ=LTRay.DirZ;

// Just re-cast ray from this position for now
//
/*
     LNewRay->DirX=LRay->DirX;
     LNewRay->DirY=LRay->DirY;
     LNewRay->DirZ=LRay->DirZ;
*/


     if(LRay->FrontFaceHit)
     {
// If ray hit a front-face (entering a Material), store the Material
// of that face in MaterialStack and increment MaterialIndex
//
      LNewRay->MaterialIndex=LRay->MaterialIndex+1;
      LMaterialStack[LNewRay->MaterialIndex]=LMaterial;
     }
     else
     {
//Printf("BackFace\n");fflush(stdout);
// If ray hit a back-face (exiting a Material), decrement MaterialIndex
//
      if(LRay->MaterialIndex) LNewRay->MaterialIndex=LRay->MaterialIndex-1;
      else LNewRay->MaterialIndex=0;
//else return(0);
//else assert(0);
     }

     LNewRay->ExcludeFace=LFace;

// Add a new ray that enters the object
//
     LNewRay->IntensityR = LRay->IntensityR * LTransparency * LMaterial->Transparent.R;
     LNewRay->IntensityG = LRay->IntensityG * LTransparency * LMaterial->Transparent.G;
     LNewRay->IntensityB = LRay->IntensityB * LTransparency * LMaterial->Transparent.B;
     LRayIter--;
    }
    else
    {
// We have Total Internal Reflection, don't multiply with opacity...
//
     LColor->R+=(LRay->IntensityR*LIllumination.R);
     LColor->G+=(LRay->IntensityG*LIllumination.G);
     LColor->B+=(LRay->IntensityB*LIllumination.B);
    }
   }
   else
   {
    LColor->R+=LRay->IntensityR*LIllumination.R;
    LColor->G+=LRay->IntensityG*LIllumination.G;
    LColor->B+=LRay->IntensityB*LIllumination.B;
   }


// Create reflected ray
//
   if((LRayIter>0)&&(LMaterial->Reflectivity>0.0))
   {
    float	LReflectivity=LMaterial->Reflectivity,
		LIntensityR, LIntensityG, LIntensityB;
    EBool	LReflectRay=TRUE;


    LIntensityR=LRay->IntensityR*LReflectivity*LMaterial->Reflective.R;
    LIntensityG=LRay->IntensityG*LReflectivity*LMaterial->Reflective.G;
    LIntensityB=LRay->IntensityB*LReflectivity*LMaterial->Reflective.B;

// Avoid endless bounces...
//
    if(!LRay->FrontFaceHit)
    {
// This one is a hack to reduce back-face reflections
//
     LIntensityR*=LReflectivity*LMaterial->Reflective.R*0.5;
     LIntensityG*=LReflectivity*LMaterial->Reflective.G*0.5;
     LIntensityB*=LReflectivity*LMaterial->Reflective.B*0.5;
    }
// If the reflected ray's luma would be less than E3dINTERNAL_BOUNCE_RAYLUMA_MIN, don't add this ray
//
    if(EcM_Luma(LIntensityR, LIntensityG, LIntensityB)<E3dINTERNAL_BOUNCE_RAYLUMA_MIN) LReflectRay=FALSE;


    if(LReflectRay)
    {
     E3dRay*	LNewRay;

     LRayStackPtr++;
     LNewRay=LRayStack+LRayStackPtr;

     LNewRay->IntensityR=LIntensityR;
     LNewRay->IntensityG=LIntensityG;
     LNewRay->IntensityB=LIntensityB;


     LNewRay->MaterialIndex=LRay->MaterialIndex;
     LNewRay->ExcludeFace=LFace;



//Printf("Reflection %f,%f,%f\n", LIllumination.R, LIllumination.G, LIllumination.B);fflush(stdout);

//Printf("Reflection %f,%f,%f\n", LIntensityR, LIntensityG, LIntensityB);fflush(stdout);

// Reflect ray
//
     {
      E3dCoordinate	LD, LDirX, LDirY, LDirZ;

      LDirX=-LRay->DirX;
      LDirY=-LRay->DirY;
      LDirZ=-LRay->DirZ;

      LD=LPoint.NormalX*LDirX+LPoint.NormalY*LDirY+LPoint.NormalZ*LDirZ;
      LNewRay->DirX=(LPoint.NormalX*LD-LDirX)*2.0+LDirX;
      LNewRay->DirY=(LPoint.NormalY*LD-LDirY)*2.0+LDirY;
      LNewRay->DirZ=(LPoint.NormalZ*LD-LDirZ)*2.0+LDirZ;

//    E3d_ReflectRay(&LRay, &LNormal);

      LNewRay->X=LPoint.X;
      LNewRay->Y=LPoint.Y;
      LNewRay->Z=LPoint.Z;
     }


     LRayIter--;
    }
   }
  }


  if(LRayStackPtr)
  {
   E3dRay*	LNewRay;

   LNewRay=LRayStack+LRayStackPtr;
   LRayStackPtr--;
   LRay->X=LNewRay->X;
   LRay->Y=LNewRay->Y;
   LRay->Z=LNewRay->Z;
   LRay->DirX=LNewRay->DirX;
   LRay->DirY=LNewRay->DirY;
   LRay->DirZ=LNewRay->DirZ;
   LRay->IntensityR=LNewRay->IntensityR;
   LRay->IntensityG=LNewRay->IntensityG;
   LRay->IntensityB=LNewRay->IntensityB;
   LRay->MaterialIndex=LNewRay->MaterialIndex;

   LFace=LNewRay->ExcludeFace;
  }
  else break;
 }

 return(LHits);
}


//========================================
// Set pixel color
//========================================
static void _SetPixelGlow(EpImage* LImage, int LX, int LY, float LGlowThreshold, float LGlowRadius, EcRGBAfColor* LColor, EpRGBAf32Pixel* LRGBAf32Pixel, EpRGBA8Pixel* LRGBA8Pixel)
{
 int	LR, LG, LB, LA;

// Tone-mapping
//
/*
 {
  float	LExp=1.2;

  LColor->R=pow(LColor->R, LExp);
  LColor->G=pow(LColor->G, LExp);
  LColor->B=pow(LColor->B, LExp);
 }
*/


 if(LImage->RGBAf32Image)
 {
// Store color in HDR image
//
  LRGBAf32Pixel->R=LColor->R;
  LRGBAf32Pixel->G=LColor->G;
  LRGBAf32Pixel->B=LColor->B;
  LRGBAf32Pixel->A=LColor->A;
 }

// Clamp color and store it in RGBA8 image
//
 if(LColor->R>=LGlowThreshold)
 {
 }
 LR=(int)(LColor->R*255.0);
 LG=(int)(LColor->G*255.0);
 LB=(int)(LColor->B*255.0);
 LA=(int)(LColor->A*255.0);
 if(LR<0) LR=0;if(LR>255) LR=255;
 if(LG<0) LG=0;if(LG>255) LG=255;
 if(LB<0) LB=0;if(LB>255) LB=255;
 if(LA<0) LA=0;if(LA>255) LA=255;
 LRGBA8Pixel->R=LR;
 LRGBA8Pixel->G=LG;
 LRGBA8Pixel->B=LB;
 LRGBA8Pixel->A=LA;
}


//========================================
// Set pixel color
//========================================
static void _SetPixel(EpImage* LImage, EcRGBAfColor* LColor, EpRGBAf32Pixel* LRGBAf32Pixel, EpRGBA8Pixel* LRGBA8Pixel)
{
 int	LR, LG, LB, LA;

// Tone-mapping
//
/*
 {
  float	LExp=1.2;

  LColor->R=pow(LColor->R, LExp);
  LColor->G=pow(LColor->G, LExp);
  LColor->B=pow(LColor->B, LExp);
 }
*/

 if(LImage->RGBAf32Image)
 {
// Store color in HDR image
//
  LRGBAf32Pixel->R=LColor->R;
  LRGBAf32Pixel->G=LColor->G;
  LRGBAf32Pixel->B=LColor->B;
  LRGBAf32Pixel->A=LColor->A;
 }

// Clamp color and store it in RGBA8 image
//
 LR=(int)(LColor->R*255.0);
 LG=(int)(LColor->G*255.0);
 LB=(int)(LColor->B*255.0);
 LA=(int)(LColor->A*255.0);
 if(LR<0) LR=0;if(LR>255) LR=255;
 if(LG<0) LG=0;if(LG>255) LG=255;
 if(LB<0) LB=0;if(LB>255) LB=255;
 if(LA<0) LA=0;if(LA>255) LA=255;
 LRGBA8Pixel->R=LR;
 LRGBA8Pixel->G=LG;
 LRGBA8Pixel->B=LB;
 LRGBA8Pixel->A=LA;
}


//========================================
// Renderer
//========================================
int _Render(E3dWindow* L3DWindow, E3dScene* LScene, E3dRenderInfo* LRenderInfo, EBool LStartFromFirstIteration, EBool LFirstPassAbortable)
{
 E3dCamera*		LCamera=LRenderInfo->Camera;
 EpRGBA8Pixel*		LRGBA8Line;
 EpRGBA8Pixel*		LRGBA8Pixel;
 EpRGBAf32Pixel*	LRGBAf32Line;
 EpRGBAf32Pixel*	LRGBAf32Pixel;
 EpRGBA8Pixel		LBackgroundRGBA8Pixel;

 EpImage*		LImage=LRenderInfo->CanvasImage;
 EcRGBAfColor		LColor;
 E3dRay			LRay;

#ifndef OPENGL_CANVAS
 Display*		LDisplay=EGUI_Display;
 Window			LWindow=XtWindow(_CanvasW);
 unsigned long*		LRedTable=EGUI_TruecolorVisual->RedTable;
 unsigned long*		LGreenTable=EGUI_TruecolorVisual->GreenTable;
 unsigned long*		LBlueTable=EGUI_TruecolorVisual->BlueTable;
 Pixel			LBackgroundXPixel;
#endif

 E3dMatrix		LViewerToWorldMatrix;

 E3d3DPosition*		LBBoxMin=&(LScene->BBoxMin);
 E3d3DPosition*		LBBoxMax=&(LScene->BBoxMax);

 E3dCoordinate		LCameraX, LCameraY, LCameraZ,
			LFOVMul, LWindowFXSize, LWindowFYSize, LAspectRatio;
 E3dCoordinate		LRayStartX, LRayStartY;
 E3dCoordinate		LRayXStep=0.0, LRayYStep=0.0;
 E3dCoordinate		mX, mY, mZ, LRF;
 int			LX, LY, LRayIter,
			LPixelsDone=0, LTotalPixels;

 float			LGlowRadius;

// For fish-eye lens
//
 E3dCoordinate		LRadiusInv;
 int			LWindowXMiddle, LWindowYMiddle;

 unsigned int		LCurrentPixelSizeMask, LImageXSize, LImageYSize, LXSize, LYSize;

 int			(*LTraceRayProc)(E3dScene*, E3dRay*, int, EcRGBAfColor*, E3dRenderInfo*);
 EBool			LReflections=LRenderInfo->Reflections,
			LRefractions=LRenderInfo->Refractions;


 if(LImage==NULL) return(-1);



 E3d_SceneResetForRendering(LScene);

 LRenderInfo->FirstIteration=LStartFromFirstIteration;
 LRenderInfo->LinesDone=0;

 LCurrentPixelSizeMask=(LRenderInfo->CurrentPixelSize<<1)-1;
 E3d_MatrixInvert3x3(LCamera->WorldToViewerMatrix, LViewerToWorldMatrix);

 LCameraX=LCamera->Position.X;
 LCameraY=LCamera->Position.Y;
 LCameraZ=LCamera->Position.Z;


// FieldOfView/2 * (PI/180)
//
 LFOVMul=tan((LCamera->FieldOfView)*0.5*0.017453293);

 LImageXSize=LXSize=LRenderInfo->ImageXSize;LImageYSize=LYSize=LRenderInfo->ImageYSize;

 LGlowRadius=LCamera->GlowRadius*(float)LYSize;

 LWindowXMiddle=LXSize>>1;LWindowYMiddle=LYSize>>1;
 if(LWindowXMiddle>LWindowYMiddle) LRadiusInv=1.0/(E3dCoordinate)(LWindowYMiddle);
 else LRadiusInv=1.0/(E3dCoordinate)(LWindowXMiddle);

 LWindowFXSize=(E3dCoordinate)LImageXSize;
 LWindowFYSize=(E3dCoordinate)LImageYSize;

 LAspectRatio=LWindowFXSize/LWindowFYSize;

 switch(LCamera->LensType)
 {
  case E3dLENS_PERSPECTIVE:
   LRayXStep=LFOVMul/LWindowFXSize*2.0*(E3dCoordinate)(LRenderInfo->CurrentPixelSize)*LAspectRatio;LRayYStep=LFOVMul/LWindowFYSize*2.0*(E3dCoordinate)(LRenderInfo->CurrentPixelSize);
  break;
 }

 LTotalPixels=LImageXSize*LImageYSize;

// Select TraceRay proc, depending on LRenderInfo
//
 if(LRenderInfo->Transparency) LTraceRayProc=E3d_TraceRay;
 else LTraceRayProc=E3d_TraceRayOpaque;


 LBackgroundRGBA8Pixel.R=(LRenderInfo->BackgroundRGBAiColor.R)>>8;
 LBackgroundRGBA8Pixel.G=(LRenderInfo->BackgroundRGBAiColor.G)>>8;
 LBackgroundRGBA8Pixel.B=(LRenderInfo->BackgroundRGBAiColor.B)>>8;
 LBackgroundRGBA8Pixel.A=(LRenderInfo->BackgroundRGBAiColor.A)>>8;

#ifndef OPENGL_CANVAS
 LBackgroundXPixel=LRedTable[LBackgroundRGBA8Pixel.R]|LGreenTable[LBackgroundRGBA8Pixel.G]|LBlueTable[LBackgroundRGBA8Pixel.B];
#endif





// Progressive resolution loop
//
if(LRenderInfo->AntiAliasing)
{
 int		LSubXN, LSubYN, LSubXC, LSubYC;
 E3dCoordinate	LSubX, LSubY, LSubXStep=0.0, LSubYStep=0.0,
		LSuperSamplingDivFactor;
// E3dCoordinate	LAASamples[9];



 switch(LRenderInfo->AntiAliasing)
 {
  default:
   LSubXN=2;LSubYN=2;
   LSuperSamplingDivFactor=1.0/1.0;
  break;

  case E3dAA_4x:
   LSubXN=2;LSubYN=2;
   LSuperSamplingDivFactor=1.0/4.0;
  break;

  case E3dAA_9x:
   LSubXN=3;LSubYN=3;
   LSuperSamplingDivFactor=1.0/9.0;
  break;
 }


 switch(LCamera->LensType)
 {
  case E3dLENS_PERSPECTIVE:
   LSubXStep=LFOVMul/LWindowFXSize*2.0/(E3dCoordinate)LSubXN*LAspectRatio;
   LSubYStep=LFOVMul/LWindowFYSize*2.0/(E3dCoordinate)LSubYN;

   LRayXStep=LFOVMul/LWindowFXSize*2.0*(E3dCoordinate)(LRenderInfo->CurrentPixelSize)*LAspectRatio;LRayYStep=LFOVMul/LWindowFYSize*2.0*(E3dCoordinate)(LRenderInfo->CurrentPixelSize);
  break;

  default:
   LSubXStep=1.0/(E3dCoordinate)LSubXN;
   LSubYStep=1.0/(E3dCoordinate)LSubYN;
  break;
 }



 LRay.MaterialStack=(E3dMaterial**)EMalloc(sizeof(E3dMaterial*)*LRenderInfo->MaxRayDepth+1);
 do
 {
  LRGBA8Line=LImage->RGBA8Image;
  LRGBAf32Line=LImage->RGBAf32Image;
  LRayStartY=LFOVMul;


  if(LFirstPassAbortable || (!LRenderInfo->FirstIteration))
  {
   E3dp_SetProgressMessage("Rendering pass %dx%d", LRenderInfo->CurrentPixelSize, LRenderInfo->CurrentPixelSize);
  }

  for(LY=0;LY<LImageYSize;LY+=LRenderInfo->CurrentPixelSize, LRayStartY-=LRayYStep, LRGBA8Line+=(LImage->XSize*LRenderInfo->CurrentPixelSize), LRGBAf32Line+=(LImage->XSize*LRenderInfo->CurrentPixelSize))
//    for(LY=0;LY<LImageYSize;LY+=LRenderInfo->CurrentPixelSize, LRayStartY-=LRayYStep, LRGBA8Line+=LImage->XSize, LRGBAf32Line+=LImage->XSize)
  {
   if((LFirstPassAbortable || (!LRenderInfo->FirstIteration)) && ((LY&1)==0))
   {
    EGUI_Sync(E3dp_EventLoopFunction);
    if(!LRenderInfo->GoRender) break;
   }

   LRayStartX=-LFOVMul*LAspectRatio;

   LRGBA8Pixel=LRGBA8Line;
   LRGBAf32Pixel=LRGBAf32Line;

   for(LX=0;LX<LImageXSize;LX+=LRenderInfo->CurrentPixelSize, LRayStartX+=LRayXStep, LRGBA8Pixel+=LRenderInfo->CurrentPixelSize, LRGBAf32Pixel+=LRenderInfo->CurrentPixelSize)
//     for(LX=0;LX<LImageXSize;LX+=LRenderInfo->CurrentPixelSize, LRayStartX+=LRayXStep, LRGBA8Pixel++, LRGBAf32Pixel++)
   {

// The top-left pixel of the previous macro-pixel is already computed in the previous iteration...
//
    if( (!LRenderInfo->FirstIteration) && ((LX&LCurrentPixelSizeMask)==0) && ((LY&LCurrentPixelSizeMask)==0) ) continue;


    LColor.R=LColor.G=LColor.B=LColor.A=0.0;

// Supersampling loops
//
    LSubY=0.0;
    for(LSubYC=0;LSubYC<LSubYN;LSubYC++, LSubY+=LSubYStep)
    {
     LSubX=0.0;
     for(LSubXC=0;LSubXC<LSubXN;LSubXC++, LSubX+=LSubXStep)
     {

    switch(LCamera->LensType)
    {
     default:
// Normal perspective camera
//
// Generate primary ray
//
      mX=LRayStartX+LSubX;mY=LRayStartY+LSubY;mZ=-1.0;

// Normalize ray
//
      LRF=1.0/sqrt(mX*mX+mY*mY+mZ*mZ);
      mX*=LRF;mY*=LRF;mZ*=LRF;
     break;

     case E3dLENS_REAR_FISHEYE:
      {
       E3dCoordinate	LDist;

// Intersect unit hemisphere in front of eye with line from current 'pixel'
//
       mX=-(((E3dCoordinate)(LWindowXMiddle-LX))+LSubX)*LRadiusInv, mY=(((E3dCoordinate)(LWindowYMiddle-LY))+LSubY)*LRadiusInv;

       LDist=sqrt(mX*mX+mY*mY);

       if(LDist>1.0) continue;

       mZ=sqrt(1.0-mX*mX-mY*mY);
      }
     break;

     case E3dLENS_ANGLE_MAP:		// 360x360 degree angle map
// u=[-1,1], v=[-1,1], we have theta=atan2(v,u), phi=pi*sqrt(u*u+v*v).
// The unit vector pointing in the corresponding direction is obtained by
// rotating (0,0,-1) by phi degrees around the y (up) axis and then theta
// degrees around the -z (forward) axis. If for a direction vector in the
// world (Dx, Dy, Dz), the corresponding (u,v) coordinate in the light probe
// image is (Dx*r,Dy*r) where r=(1/pi)*acos(Dz)/sqrt(Dx^2 + Dy^2).
//
      mX=-(((E3dCoordinate)(LWindowXMiddle-LX))+LSubX)*LRadiusInv, mY=(((E3dCoordinate)(LWindowYMiddle-LY))+LSubY)*LRadiusInv;
      {
       E3dCoordinate	LDist=sqrt(mX*mX+mY*mY);

       if(LDist>1.0) continue;

       {
	E3dMatrix	LMatrix;
	E3dCoordinate	theta=atan2(mY,mX), phi=E3dPI*LDist,
			LSinTheta=sin(theta), LCosTheta=cos(theta),
			LSinPhi=sin(phi), LCosPhi=cos(phi);


	E3d_MatrixLoadIdentity(LMatrix);
	E3d_MatrixRotateSinCos(LMatrix, 'y', LSinPhi, LCosPhi);

	mX=0.0;mY=0.0;mZ=-1.0;
	E3dM_MatrixTransform3x3(LMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);
	mX=LRay.DirX;
	mY=LRay.DirY;
	mZ=LRay.DirZ;

	E3d_MatrixLoadIdentity(LMatrix);
	E3d_MatrixRotateSinCos(LMatrix, 'z', LSinTheta, LCosTheta);
	E3dM_MatrixTransform3x3(LMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);
	mX=LRay.DirX;
	mY=LRay.DirY;
	mZ=LRay.DirZ;
       }
      }
     break;

// Fish-eye lens
//
     case E3dLENS_FISHEYE:
      {
       E3dCoordinate	LDist;

// Intersect unit hemisphere in front of eye with line from current 'pixel'
//
       mX=-(((E3dCoordinate)(LWindowXMiddle-LX))+LSubX)*LRadiusInv, mY=(((E3dCoordinate)(LWindowYMiddle-LY))+LSubY)*LRadiusInv;

       LDist=sqrt(mX*mX+mY*mY);

       if(LDist>1.0) continue;

       mZ=-sqrt(1.0-mX*mX-mY*mY);
      }
     break;
    }


// Transform the ray into world space
//
    E3dM_MatrixTransform3x3(LViewerToWorldMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);





    LRay.X=LCameraX;
    LRay.Y=LCameraY;
    LRay.Z=LCameraZ;
    LRay.LastT=-1.0;

// Eventually we might want to figure out if we're inside
// a participating medium, but for now, start from vacuum
//
    LRay.MaterialStack[0]=NULL;
    LRay.MaterialIndex=0;


    LRay.IntensityR=1.0;
    LRay.IntensityG=1.0;
    LRay.IntensityB=1.0;

// If the Camera is outside the grid, compute the intersection of the ray
// with the Scene BBox and re-originate the ray at the intersection
//
    if((LRay.X<LBBoxMin->X)||(LRay.X>=LBBoxMax->X)||(LRay.Y<LBBoxMin->Y)||(LRay.Y>=LBBoxMax->Y)||(LRay.Z<LBBoxMin->Z)||(LRay.Z>=LBBoxMax->Z))
    {
     if(!E3d_RayGridBoundsIntersect(LScene, &LRay)) { *LRGBA8Pixel=LBackgroundRGBA8Pixel;continue; }
    }

    if(LReflections||LRefractions) LRayIter=LRenderInfo->MaxRayDepth+1;
    else LRayIter=2;

    if(LTraceRayProc(LScene, &LRay, LRayIter, &LColor, LRenderInfo))
    {
LColor.A+=1.0;
    }
    else
    {
// No first hit: background
//
LColor.R+=0.0;
LColor.G+=0.0;
LColor.B+=0.0;
    }



    }	// Supersampling loops
   }


LColor.R*=LSuperSamplingDivFactor;
LColor.G*=LSuperSamplingDivFactor;
LColor.B*=LSuperSamplingDivFactor;



   if(LCamera->Glow) _SetPixelGlow(LImage, LX, LY, LCamera->GlowThreshold, LGlowRadius, &LColor, LRGBAf32Pixel, LRGBA8Pixel);
   else _SetPixel(LImage, &LColor, LRGBAf32Pixel, LRGBA8Pixel);




#ifndef OPENGL_CANVAS
     XSetForeground(LDisplay, _CanvasGC, LRedTable[LR]|LGreenTable[LG]|LBlueTable[LB]);
     XDrawPoint(LDisplay, _Pixmap, _CanvasGC, LX, LY);
     XDrawPoint(LDisplay, LWindow, _CanvasGC, LX, LY);
#endif


    LPixelsDone++;
   }



   LRenderInfo->LinesDone=LY;
   if((LFirstPassAbortable || (!LRenderInfo->FirstIteration)) && ((LY&0x0F)==0))
   {
    E3dp_SetProgressIndicator((LPixelsDone*1000)/LTotalPixels, E3d_AbortCallback, (EPointer)LRenderInfo);
#ifdef OPENGL_CANVAS
    if(LY) _GLRedrawCanvas(LRenderInfo);
#endif
   }
  }

  LRenderInfo->LinesDone=LImageYSize;

#ifdef OPENGL_CANVAS
  if(LRenderInfo->FirstIteration) _GLRedrawCanvas(LRenderInfo);
#endif

  LRenderInfo->FirstIteration=FALSE;
  LRenderInfo->CurrentPixelSize=LRenderInfo->CurrentPixelSize>>1;
  LCurrentPixelSizeMask=LCurrentPixelSizeMask>>1;
  LRayXStep*=0.5;
  LRayYStep*=0.5;

  if(!LRenderInfo->GoRender) break;
 } while(LRenderInfo->CurrentPixelSize);

}
else
{
// No anti-aliasing
//
 LRay.MaterialStack=(E3dMaterial**)EMalloc(sizeof(E3dMaterial*)*LRenderInfo->MaxRayDepth+1);
 do
 {
  LRGBA8Line=LImage->RGBA8Image;
  LRGBAf32Line=LImage->RGBAf32Image;
  LRayStartY=LFOVMul;


  if(LFirstPassAbortable || (!LRenderInfo->FirstIteration))
  {
   E3dp_SetProgressMessage("Rendering pass %dx%d", LRenderInfo->CurrentPixelSize, LRenderInfo->CurrentPixelSize);
  }

  for(LY=0;LY<LImageYSize;LY+=LRenderInfo->CurrentPixelSize, LRayStartY-=LRayYStep, LRGBA8Line+=(LImage->XSize*LRenderInfo->CurrentPixelSize), LRGBAf32Line+=(LImage->XSize*LRenderInfo->CurrentPixelSize))
//    for(LY=0;LY<LImageYSize;LY+=LRenderInfo->CurrentPixelSize, LRayStartY-=LRayYStep, LRGBA8Line+=LImage->XSize, LRGBAf32Line+=LImage->XSize)
  {
   if((LFirstPassAbortable || (!LRenderInfo->FirstIteration)) && ((LY&1)==0))
   {
    EGUI_Sync(E3dp_EventLoopFunction);
    if(!LRenderInfo->GoRender) break;
   }

   LRayStartX=-LFOVMul*LAspectRatio;

   LRGBA8Pixel=LRGBA8Line;
   LRGBAf32Pixel=LRGBAf32Line;

   for(LX=0;LX<LImageXSize;LX+=LRenderInfo->CurrentPixelSize, LRayStartX+=LRayXStep, LRGBA8Pixel+=LRenderInfo->CurrentPixelSize, LRGBAf32Pixel+=LRenderInfo->CurrentPixelSize)
//     for(LX=0;LX<LImageXSize;LX+=LRenderInfo->CurrentPixelSize, LRayStartX+=LRayXStep, LRGBA8Pixel++, LRGBAf32Pixel++)
   {

// The top-left pixel of the previous macro-pixel is already computed in the previous iteration...
//
    if((!LRenderInfo->FirstIteration) && ((LX&LCurrentPixelSizeMask)==0) && ((LY&LCurrentPixelSizeMask)==0)) continue;


    switch(LCamera->LensType)
    {
     default:
// Normal perspective camera
//
// Generate primary ray
//
      mX=LRayStartX;mY=LRayStartY;mZ=-1.0;

// Normalize ray
//
      LRF=1.0/sqrt(mX*mX+mY*mY+mZ*mZ);
      mX*=LRF;mY*=LRF;mZ*=LRF;
     break;


// Fish-eye lens
//
     case E3dLENS_FISHEYE:
      {
       E3dCoordinate	LDist;

// Intersect unit hemisphere in front of eye with line from current 'pixel'
//
       mX=-(E3dCoordinate)(LWindowXMiddle-LX)*LRadiusInv, mY=(E3dCoordinate)(LWindowYMiddle-LY)*LRadiusInv;

       LDist=sqrt(mX*mX+mY*mY);

       if(LDist>=1.0) continue;

       mZ=-sqrt(1.0-mX*mX-mY*mY);
/*

// The origin and the direction of the eye-ray will be the same (unit hemisphere)
//
       LRay.X=mX;LRay.Y=mY;LRay.Z=mZ;
       LRay.DirX=mX;LRay.DirY=mY;LRay.DirZ=mZ;
*/
      }
     break;

     case E3dLENS_REAR_FISHEYE:
      {
       E3dCoordinate	LDist;

// Intersect unit hemisphere in front of eye with line from current 'pixel'
//
       mX=-(E3dCoordinate)(LWindowXMiddle-LX)*LRadiusInv, mY=(E3dCoordinate)(LWindowYMiddle-LY)*LRadiusInv;

       LDist=sqrt(mX*mX+mY*mY);

       if(LDist>=1.0) continue;

       mZ=sqrt(1.0-mX*mX-mY*mY);
      }
     break;

     case E3dLENS_ANGLE_MAP:		// 360x360 degree angle map
      mX=-((E3dCoordinate)(LWindowXMiddle-LX))*LRadiusInv, mY=((E3dCoordinate)(LWindowYMiddle-LY))*LRadiusInv;
      {
       E3dCoordinate	LDist=sqrt(mX*mX+mY*mY);

       if(LDist>1.0) continue;

       {
	E3dMatrix	LMatrix;
	E3dCoordinate	theta=atan2(mY,mX), phi=E3dPI*LDist,
			LSinTheta=sin(theta), LCosTheta=cos(theta),
			LSinPhi=sin(phi), LCosPhi=cos(phi);


	E3d_MatrixLoadIdentity(LMatrix);
	E3d_MatrixRotateSinCos(LMatrix, 'y', LSinPhi, LCosPhi);

	mX=0.0;mY=0.0;mZ=-1.0;
	E3dM_MatrixTransform3x3(LMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);
	mX=LRay.DirX;
	mY=LRay.DirY;
	mZ=LRay.DirZ;

	E3d_MatrixLoadIdentity(LMatrix);
	E3d_MatrixRotateSinCos(LMatrix, 'z', LSinTheta, LCosTheta);
	E3dM_MatrixTransform3x3(LMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);
	mX=LRay.DirX;
	mY=LRay.DirY;
	mZ=LRay.DirZ;
       }
      }
     break;
    }


// Transform the ray into world space
//
    E3dM_MatrixTransform3x3(LViewerToWorldMatrix, LRay.DirX, LRay.DirY, LRay.DirZ);





    LRay.X=LCameraX;
    LRay.Y=LCameraY;
    LRay.Z=LCameraZ;
    LRay.LastT=-1.0;

// Eventually we might want to figure out if we're inside
// a participating medium, but for now, start from vacuum
//
    LRay.MaterialStack[0]=NULL;
    LRay.MaterialIndex=0;


    LRay.IntensityR=1.0;
    LRay.IntensityG=1.0;
    LRay.IntensityB=1.0;

// If the Camera is outside the grid, compute the intersection of the ray
// with the Scene BBox and re-originate the ray at the intersection
//
    if((LRay.X<LBBoxMin->X)||(LRay.X>=LBBoxMax->X)||(LRay.Y<LBBoxMin->Y)||(LRay.Y>=LBBoxMax->Y)||(LRay.Z<LBBoxMin->Z)||(LRay.Z>=LBBoxMax->Z))
    {
     if(!E3d_RayGridBoundsIntersect(LScene, &LRay)) { *LRGBA8Pixel=LBackgroundRGBA8Pixel;continue; }
    }

    LColor.R=LColor.G=LColor.B=LColor.A=0.0;
    if(LReflections||LRefractions) LRayIter=LRenderInfo->MaxRayDepth+1;
    else LRayIter=2;

    if(LTraceRayProc(LScene, &LRay, LRayIter, &LColor, LRenderInfo))
    {
     LColor.A=1.0;

     if(LCamera->Glow) _SetPixelGlow(LImage, LX, LY, LCamera->GlowThreshold, LGlowRadius, &LColor, LRGBAf32Pixel, LRGBA8Pixel);
     else _SetPixel(LImage, &LColor, LRGBAf32Pixel, LRGBA8Pixel);



#ifndef OPENGL_CANVAS
     XSetForeground(LDisplay, _CanvasGC, LRedTable[LR]|LGreenTable[LG]|LBlueTable[LB]);
     XDrawPoint(LDisplay, _Pixmap, _CanvasGC, LX, LY);
     XDrawPoint(LDisplay, LWindow, _CanvasGC, LX, LY);
#endif
    }
    else
    {
// No first hit: background
//

// If we already cleared the window and the Pixmap with the background color, so we don't have to do this,
// but if we don't, it gets slower (!?)
//
     *LRGBA8Pixel=LBackgroundRGBA8Pixel;
/*
     LRGBAf32Pixel->R=0.0;
     LRGBAf32Pixel->G=0.0;
     LRGBAf32Pixel->B=0.0;
     LRGBAf32Pixel->A=1.0;
*/
#ifndef OPENGL_CANVAS
     XSetForeground(LDisplay, _CanvasGC, LBackgroundXPixel);
     XDrawPoint(LDisplay, _Pixmap, _CanvasGC, LX, LY);
     XDrawPoint(LDisplay, LWindow, _CanvasGC, LX, LY);
#endif
    }
    LPixelsDone++;

   }


   LRenderInfo->LinesDone=LY;
   if((LFirstPassAbortable || (!LRenderInfo->FirstIteration)) && ((LY&0x0F)==0))
   {
    E3dp_SetProgressIndicator((LPixelsDone*1000)/LTotalPixels, E3d_AbortCallback, (EPointer)LRenderInfo);
#ifdef OPENGL_CANVAS
    if(LY) _GLRedrawCanvas(LRenderInfo);
#endif
   }
  }


  LRenderInfo->LinesDone=LImageYSize;

#ifdef OPENGL_CANVAS
  if(LRenderInfo->FirstIteration) _GLRedrawCanvas(LRenderInfo);
#endif

  LRenderInfo->FirstIteration=FALSE;
  LRenderInfo->CurrentPixelSize=LRenderInfo->CurrentPixelSize>>1;
  LCurrentPixelSizeMask=LCurrentPixelSizeMask>>1;
  LRayXStep*=0.5;
  LRayYStep*=0.5;

  if(!LRenderInfo->GoRender) break;
 } while(LRenderInfo->CurrentPixelSize);
}

 EFree(LRay.MaterialStack);

 return(0);
}


/*======================================*/
/* Start preview			*/
/*======================================*/
static void _Preview(E3dRenderInfo* LRenderInfo, unsigned int LUpdateFlags, EBool LFirstPassAbortable)
{
 E3dCamera*		LCamera;
 unsigned int		LNumOfPolyGroups;
 struct timeval		LTV;
 struct timezone	LTZ;


 if(E3dp_Main3DWindow)
 {
  E3dScene*	LScene=E3d_Scene;
  E3dRenderer*	LRenderer;


  if((LRenderInfo->Renderer==NULL)&&(E3d_NumOfRenderers>0))
  {
   LRenderInfo->Renderer=E3d_RendererFindByName("Ray-tracer");

   if(LRenderInfo->Renderer==NULL) LRenderInfo->Renderer=E3d_Renderers[0];
  }

  LRenderer=LRenderInfo->Renderer;

  if(LRenderer==NULL) return;

  LCamera=&(E3dp_Main3DWindow->PerspectiveCamera);


  while(LRenderInfo->RestartRender)
  {
   LRenderInfo->RestartRender=FALSE;

   LRenderInfo->Rendering=TRUE;

// Get staring time
//
   if(gettimeofday(&LTV, &LTZ)!=-1)
   {
    LRenderInfo->FrameStartTime=LTV.tv_sec;
   }

// Work in world space, so we can move the Camera
// without having to re-voxelize the Scene
//

   if(LRenderer==(E3dRenderer*)_RayTracer)
   {
    if(((LUpdateFlags&E3dUF_VOXELIZE)!=0)||(LScene->RenderPolyGroups==NULL)) LNumOfPolyGroups=E3d_SceneGetRenderTriangles(LScene, LRenderInfo, NULL);
    else LNumOfPolyGroups=LScene->NumOfRenderPolyGroups;
   }
   else LNumOfPolyGroups=1;

   if(LNumOfPolyGroups)
   {
 #ifndef OPENGL_CANVAS
    Display*		LDisplay=EGUI_Display;
    EpRGBA8Pixel	LBackgroundRGBA8Pixel;
    Pixel		LBackgroundXPixel;
    unsigned long*	LRedTable=EGUI_TruecolorVisual->RedTable;
    unsigned long*	LGreenTable=EGUI_TruecolorVisual->GreenTable;
    unsigned long*	LBlueTable=EGUI_TruecolorVisual->BlueTable;
 #endif
    E3dVoxel*		LVoxels=NULL;
    int			LC, LN;


    LRenderInfo->CurrentPixelSize=LRenderInfo->StartPixelSize;


    if(LRenderer==(E3dRenderer*)_RayTracer)
    {
     if(((LUpdateFlags&E3dUF_VOXELIZE)!=0)||(LScene->Voxels==NULL)) LVoxels=E3d_SceneVoxelize(LScene);
     else LVoxels=LScene->Voxels;
    }

    _Lights=LScene->Lights;
    _NumOfLights=LScene->NumOfLights;
    _Camera=LCamera;


 #ifndef OPENGL_CANVAS
    while(_Pixmap==(Pixmap)NULL) EGUI_Sync(E3dp_EventLoopFunction);

    LBackgroundRGBA8Pixel.R=(LRenderInfo->BackgroundRGBAiColor.R)>>8;
    LBackgroundRGBA8Pixel.G=(LRenderInfo->BackgroundRGBAiColor.G)>>8;
    LBackgroundRGBA8Pixel.B=(LRenderInfo->BackgroundRGBAiColor.B)>>8;
    LBackgroundRGBA8Pixel.A=(LRenderInfo->BackgroundRGBAiColor.A)>>8;
    LBackgroundXPixel=LRedTable[LBackgroundRGBA8Pixel.R]|LGreenTable[LBackgroundRGBA8Pixel.G]|LBlueTable[LBackgroundRGBA8Pixel.B];
 #endif


// Single frame rendering: clear Pixmap
//
    if(LFirstPassAbortable)
    {
     Ep_ImageClear(LRenderInfo->CanvasImage, NULL, &(E3d_Scene->RenderInfo.BackgroundRGBAiColor), EpRED|EpGREEN|EpBLUE|EpALPHA);
 #ifndef OPENGL_CANVAS
     XSetForeground(LDisplay, _CanvasGC, LBackgroundXPixel);
     XFillRectangle(LDisplay, _Pixmap, _CanvasGC, 0, 0, LRenderInfo->WorkingImageXSize, LRenderInfo->WorkingImageYSize);
     XFillRectangle(LDisplay, XtWindow(_CanvasW), _CanvasGC, 0, 0, LRenderInfo->WorkingImageXSize, LRenderInfo->WorkingImageYSize);
 #endif
    }

    LRenderInfo->GoRender=TRUE;

// Read 2DTexture images (A)
//
    if(LUpdateFlags&E3dUF_READ_TEXTURES)
    {
     LN=LScene->NumOf2DTextures;
     for(LC=0;LC<LN;LC++)
     {
      E3d_Scene2DTextureReadImage(LScene, LScene->Textures2D[LC], E3dp_Prefs.TexturePath, FALSE, TRUE);
     }
    }

    if(LRenderer->RenderProc) LRenderer->RenderProc(&_3DWindow, LScene, LRenderInfo, TRUE, LFirstPassAbortable);
   }
   else E3dp_PrintMessage(0, 3000, "There is nothing to preview");

   if(LRenderInfo->RestartRender)
   {
//Printf("Restart %dx%d, %dx%d\n", LRenderInfo->ImageXSize, LRenderInfo->ImageYSize, LRenderInfo->WorkingImageXSize, LRenderInfo->WorkingImageYSize);fflush(stdout);
    if((LRenderInfo->ImageXSize!=LRenderInfo->WorkingImageXSize)||(LRenderInfo->ImageYSize!=LRenderInfo->WorkingImageYSize)) E3d_PopupCanvasDialog(LRenderInfo);
   }
  }

// Print out stats if all passes are done
//
  if(LRenderInfo->CurrentPixelSize==0)
  {
// Get finish time
//
   if(gettimeofday(&LTV, &LTZ)!=-1)
   {
    int	LSeconds=(int)(LTV.tv_sec-LRenderInfo->FrameStartTime);

    switch(LSeconds)
    {
     case 0:
     break;

     case 1:	E3dp_PrintMessage(0, 5000, "%d second", (int)(LTV.tv_sec-LRenderInfo->FrameStartTime));break;

     default:
      E3dp_PrintMessage(0, 5000, "%d seconds", (int)(LTV.tv_sec-LRenderInfo->FrameStartTime));
     break;
    }
   }

   E3dp_SetProgressIndicator(-1, NULL, NULL);
  }


  LRenderInfo->UpdateFlags=0;
  LRenderInfo->GoRender=FALSE;
  LRenderInfo->Rendering=FALSE;


// If we didn't start from pixel-size of 1, the last scanlines weren't updated
//
  if((LRenderInfo->StartPixelSize>1)&&(LRenderInfo->LinesDone==LRenderInfo->WorkingImageYSize)&&(_Interactive))
  {
#ifdef OPENGL_CANVAS
   _GLRedrawCanvas(LRenderInfo);
#endif

   E3dp_SetProgressIndicator(-1, NULL, NULL);
  }
 }
}


/*======================================*/
/* Redraw Scene				*/
/*======================================*/
static void _Redraw(unsigned int LUpdateFlags, EBool LFirstPassAbortable)
{
 E3dScene*	LScene=E3d_Scene;
 E3dRenderInfo*	LRenderInfo=&(LScene->RenderInfo);


 if(LRenderInfo->Rendering)
 {
  LRenderInfo->GoRender=FALSE;
  LRenderInfo->RestartRender=TRUE;
 }
 else
 {
  if((LRenderInfo->ImageXSize!=LRenderInfo->WorkingImageXSize)||(LRenderInfo->ImageYSize!=LRenderInfo->WorkingImageYSize)) E3d_PopupCanvasDialog(LRenderInfo);
  LRenderInfo->RestartRender=TRUE;
  _Preview(&(LScene->RenderInfo), LRenderInfo->UpdateFlags, LFirstPassAbortable);
 }
}


/*======================================*/
/* Camera changed callback		*/
/*======================================*/
static void _CB_Camera(void* LCam, EPointer LClientData, EPointer LCallData)
{
 E3dScene*	LScene=E3d_Scene;
 E3dRenderInfo*	LRenderInfo=&(LScene->RenderInfo);
 int		LPixels=LRenderInfo->StartPixelSize*LRenderInfo->StartPixelSize;


 if((LRenderInfo->ImageXSize*LRenderInfo->ImageYSize/LPixels)>_MaxInteractiveRes) return;

 if(_Interactive) _Redraw(0, FALSE);
}


/*======================================*/
/* Scene changed callback		*/
/*======================================*/
static void _CB_Scene(void* LScn, EPointer LClientData, EPointer LCallData)
{
 E3dScene*	LScene=(E3dScene*)LScn;
 E3dRenderInfo*	LRenderInfo=&(LScene->RenderInfo);
 int		LPixels=LRenderInfo->StartPixelSize*LRenderInfo->StartPixelSize;


 if((LRenderInfo->ImageXSize*LRenderInfo->ImageYSize/LPixels)>_MaxInteractiveRes) return;

 switch(((E3dSceneCallbackStruct*)LCallData)->Reason)
 {
  case E3dCR_NODE_SCALE:
   LRenderInfo->UpdateFlags|=E3dUF_VOXELIZE;
   if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
  break;

  case E3dCR_NODE_ROTATE:
   LRenderInfo->UpdateFlags|=E3dUF_VOXELIZE;
   if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
  break;

  case E3dCR_NODE_TRANSLATE:
   {
    E3dSceneModelsCallbackStruct*	LCBS=(E3dSceneModelsCallbackStruct*)LCallData;
    E3dModel**				LModels=LCBS->Models;
    unsigned int			LC, LNumOfModels=LCBS->NumOfModels,
					LUpdateFlags=0;

// Don't re-voxelize Scene if only a Light, Camera, etc is moved.
//
    for(LC=0;LC<LNumOfModels;LC++) if(LModels[LC]->Type==E3dMDL_NORMAL) LUpdateFlags|=E3dUF_VOXELIZE;

    LRenderInfo->UpdateFlags|=LUpdateFlags;

    if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
   }
  break;

  case E3dCR_GEOMETRY_CHANGED:
   LRenderInfo->UpdateFlags|=E3dUF_VOXELIZE;
   if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
  break;

  case E3dCR_MATERIAL_ADDED:
  break;

  case E3dCR_MATERIAL_REMOVED:
  break;

  case E3dCR_MATERIAL_CHANGED:
   if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
  break;

  case E3dCR_2DTEXTURE_ADDED:
  break;

  case E3dCR_2DTEXTURE_REMOVED:
  break;

  case E3dCR_2DTEXTURE_CHANGED:
  break;

  case E3dCR_HIDE_UNHIDE:
   LRenderInfo->UpdateFlags|=E3dUF_VOXELIZE;
   if(_Interactive) _Redraw(LRenderInfo->UpdateFlags, FALSE);
  break;
 }
}


/*======================================*/
/* Icon callback			*/
/*======================================*/
static void _MCB_Preview(EguiItem LI, EPointer LClientData, EPointer LCallData)
{
 E3dScene*	LScene=E3d_Scene;
 E3dRenderInfo*	LRenderInfo=&(LScene->RenderInfo);
 E3dCamera*	LCamera=&(E3dp_Main3DWindow->PerspectiveCamera);


 if(LRenderInfo->Rendering)
 {
  LRenderInfo->GoRender=FALSE;LRenderInfo->RestartRender=TRUE;
  return;
 }

 E3d_PopupCanvasDialog(LRenderInfo);

 E3d_SceneRefreshLights(LScene, LCamera->WorldToViewerMatrix);

 LRenderInfo->RestartRender=TRUE;
 _Preview(&(LScene->RenderInfo), LRenderInfo->UpdateFlags, TRUE);
 LRenderInfo->UpdateFlags=0;


 if(_CanvasUp)	// A lot could've happened during rendering...
 {
#ifdef OPENGL_CANVAS
  _GLRedrawCanvas(&(LScene->RenderInfo));
#endif


  if(!_CallbacksAdded)
  {
   E3dCamera*	LCamera=&(E3dp_Main3DWindow->PerspectiveCamera);

   E3d_CameraAddCallback(LCamera, _CB_Camera, (EPointer)0);
   E3d_SceneAddCallback(LScene, E3dCALLBACK_GENERAL, _CB_Scene, (EPointer)0);

   _CallbacksAdded=TRUE;
  }
 }
}


/*======================================*/
/* Key callback				*/
/*======================================*/
static void _KCB_Preview(unsigned short LKey, unsigned int LModifiers, int LKeyEventType, EPointer LClientData)
{
 _MCB_Preview(NULL, LClientData, NULL);
}


/*======================================*/
/* Entry point of the plugin		*/
/*======================================*/
int Plugin_Init(EPlugin* LPlugin)
{
 E3dRenderer	LRendererTemplate=
   {
    "Ray-tracer",		// Name
    sizeof(E3dERay),		// StructSize
    _Render			// RenderProc
   };



 _Plugin=LPlugin;


 E3d_3DWindowDefault(&_3DWindow);
 _3DWindow.Panel=&_3DPanel;

 if((_RayTracer=(E3dERay*)E3d_RendererRegister(&LRendererTemplate))!=NULL)
 {
 }




 _MenuButton=EGUI_AddPushButton("Menu->Render", "Show preview", 'S', "S", "S", FALSE, NULL, _MCB_Preview, (EPointer)0);

 E_KeySetCallback("Preview", (unsigned short)'S', 0, EKeyPRESS, _KCB_Preview, (EPointer)E3dCR_PREVIEW);

 return(0);
}


/*======================================*/
/* Exit method of the plugin		*/
/*======================================*/
int Plugin_Exit()
{
 if(_MenuButton) EGUI_DestroyItem(_MenuButton);_CallbacksAdded=FALSE;

 if(_RayTracer) E3d_RendererDeactivate((E3dRenderer*)_RayTracer);

 E_KeyRemoveCallback((unsigned short)'S', 0, EKeyPRESS, _KCB_Preview, (EPointer)E3dCR_PREVIEW);

 return(0);
}
