//================================================================
// E3D
//
// Core Rendering-related features
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Apr-11 18:02:08
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _E3D_Rendering_h
#define _E3D_Rendering_h

#include <assert.h>
#include <unistd.h>
#include <stdint.h>    // For uint32_t etc. for E_Random

#ifndef _EClass_h
#include <EClass.h>
#endif

#ifndef _EThreads_h
#include <EThreads.h>
#endif

#ifndef _E3D_Node_h
#include <E3D/Node.h>
#endif

#ifndef _E3D_Camera_h
#include <E3D/Camera.h>
#endif

#ifndef _E3D_Window_h
#include <E3D/UI/Window.h>
#endif

#ifndef _E3D_KDTree_h
#include <E3D/Accelerators/KDTree.h>
#endif

#include <Image/GL/EpGL.h>

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif


#define E3dRenderDEBUG  1

#define E3dLIGHTS_MULTIPLIED_BY_NDOTL  0  // If not 0, Shader->ShadeSurface() methods expect light Power values to be already multiplied by Ndot:

// Default SSAA subpixel count (4x means 2x resolution off-screen buffer in X and Y directions)
#ifdef EpGL_HAS_FBO
#define E3d_DefaultSSAANSamples  4
#else
#define E3d_DefaultSSAANSamples  1
#endif


#define _E3dM_Modulate3(pColor, pFactor, pChannelMask, pClampFunc) do\
{\
	float*  lColor = (float*)(pColor);\
	switch(pChannelMask)\
	{\
		case EpRED:\
			{\
				const float  lV = pFactor * LTextureColor.R;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpGREEN:\
			{\
				const float  lV = pFactor * LTextureColor.G;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpBLUE:\
			{\
				const float  lV = pFactor * LTextureColor.B;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpALPHA:\
			{\
				const float  lV = pFactor * LTextureColor.A;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpRED | EpGREEN | EpBLUE:\
			{\
				const float  lV = pFactor;\
				lColor[0] += LTextureColor.R * lV;\
				lColor[1] += LTextureColor.G * lV;\
				lColor[2] += LTextureColor.B * lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpRED | EpGREEN | EpBLUE | EpALPHA:\
			{\
				const float  lV = pFactor * LTextureColor.A;\
				lColor[0] += LTextureColor.R * lV;\
				lColor[1] += LTextureColor.G * lV;\
				lColor[2] += LTextureColor.B * lV;\
\
				pClampFunc\
			}\
		break;\
\
		case EpINTENSITY:\
			{\
				const float  lV = pFactor * (LTextureColor.R + LTextureColor.G + LTextureColor.B) * 0.333333;\
				lColor[0] += lV;\
				lColor[1] += lV;\
				lColor[2] += lV;\
\
				pClampFunc\
			}\
		break;\
	}\
} while(0)


//================================================================================================
// Add Emission (direct radiance) of area light, when directly looking into an area light.
//
// Emission colors are in radiance (not total power of light).
// Also, luckily, we only need to compute radiant exitance (radiosity) because:
// - the cosine term due to the viewing angle is cancelled out by looking at a larger area at lower angles.
// - the distance inverse-square term (due to the solid angle) is cancelled out by the same solid angle looking at a larger area of the area light from farther away.
//================================================================================================
static inline void _E3d_AddEmission(const E3dSurfacePoint* PPoint, const E3dMaterial* PMaterial, const float* PEmission,  const int PSpectralIndex, const int PNSpectralSamples,  float* PColor)
{
	const E3dMatLight*  LMatLight = &PMaterial->Light;const E3dCoordinate  LCutoffAngle = LMatLight->CutoffAngle;
	if(LCutoffAngle == 0.0)
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples) PColor[LSI] += PEmission[LSI];
	}
	else if(E_Dot3_vv(PPoint->Normal, PPoint->PointToViewer) > LMatLight->CutoffAngleCos)
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples) PColor[LSI] += PEmission[LSI] * 90.0 / LCutoffAngle;
	}
}


//================================================
// Beer-Lambert law of absorption
//================================================
static inline void _E3d_ExtinctionN(const EIndex PSpectralIndex, const EIndex PNSpectralSamples,  const float* PExctinction, const E3dCoordinate PDistance,  float* PColor)
{
	{
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * PExctinction[LSI]);
	}
}


//================================================
// Beer-Lambert law of absorption
//================================================
static inline void _E3d_Extinction(const EIndex PSpectralIndex, const EIndex PNSpectralSamples,  const E3dShader* PVolumeShader, const E3dCoordinate PDistance, float* PColor)
{
	if(PVolumeShader && PVolumeShader->HasVolumeAbsorption)
	{
		const float*  LAbsorption = PVolumeShader->Volume.Absorption.Samples;const float*  LScattering = PVolumeShader->Volume.Scattering.Samples;
		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * (LAbsorption[LSI] + LScattering[LSI]));

//		const float*  LExtinction = PVolumeShader->Volume.Extinction.Samples;
//		E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)  PColor[LSI] *= E3d_Exp(-PDistance * LExtinction[LSI]);
	}
}


#define _E3d_MIPMap(pRay, pRTriangle, pIndex) do\
{\
	LMipRay.Direction = pRay->PixelCornerDirections[pIndex];\
	const E3dCoordinate	 lT = E3dRay_IntersectTriangle_KnownnHit_UV(&LMipRay, pRTriangle, &LUVs[pIndex]);\
\
	E3dAnisoPoint*  lAnisoPoint = LAnisoPoints + pIndex;\
	E3dCoord3*      lPosition = &lAnisoPoint->Position;\
/* Set hit position, for procedural texture-mappers	*/\
	E_AddScaled3_vvp(LMipRay.Origin, LMipRay.Direction, lT,  lPosition);\
} while(0)


//==========================================================================================
// Normalize texture coordinates (convert to 0.0...1.0 range) by applying tiling etc.
//==========================================================================================
#define _M_TexcoordsNormalize(pMapper) do\
{\
	const E3dCoordinate  lSRepeatsF = (E3dCoordinate)(pMapper->SCount), lTRepeatsF = (E3dCoordinate)(pMapper->TCount);\
\
	if((pMapper->Class == E3dTextureMapperFreehand_class) || (LS >= 0.0f && LT >= 0.0f && LS <= lSRepeatsF && LT <= lTRepeatsF))\
/*	if(0)*/\
	{\
printf("%s() %s:%d  _M_TexcoordsNormalize()\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);\
		if(LS >= 0.0) LS -= E3d_Floor(LS);  /* Modulo...	*/\
		else LS += (1.0 - E3d_Ceil(LS));\
\
		if(LT >= 0.0) LT -= E3d_Floor(LT);  /* Modulo...	*/\
		else LT += (1.0 - E3d_Ceil(LT));\
	}\
	else LS = E3dST_OFF_TEXTURE;\
} while(0)


#define E3d_Span4_vv(pS, pField, pD) do\
{\
	E3dCoordinate  lSpan;\
\
	pD.pField = E3d_Abs(pS[0].pField - pS[1].pField);\
	lSpan = E3d_Abs(pS[0].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[0].pField - pS[2].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[1].pField - pS[2].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[1].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
	lSpan = E3d_Abs(pS[2].pField - pS[3].pField);if(pD.pField < lSpan) pD.pField = lSpan;\
} while(0)


// Temporary solution to RendererClass re-activation...
// We'll always allocate this size, regardless of StructSize, so if we need to deactivate/reactivate
// a Class and its Renderers and the new StructSize is greater, we don't need to go throught all the
// references of Renderers.
// Eventually we'll do this though
//
#define E3dRENDERER_MAX_SIZE  262144


#define E3dAE_OFF            0x00
#define E3dAE_SPOT_METERING  0x01


// Anti-aliasing
//
enum
{
	E3dAA_NONE = 0,
	E3dAA_02x,
	E3dAA_04x,
	E3dAA_06x,
	E3dAA_09x,
	E3dAA_02xR,
	E3dAA_03xR,
	E3dAA_04xR,
	E3dAA_05xR,
	E3dAA_07xR,
	E3dAA_09xR,
	E3dAA_16xR,
	E3dAA_25xR,
	E3dAA_36xR,
	E3dAA_49xR,
	E3dAA_64xR,
	E3dAA_ADAPTIVE_2x2,
	E3dAA_ADAPTIVE_3x3,
	E3dAA_ADAPTIVE_4x4
};


enum
{
	E3dUNIFORM = 1,
	E3dSTRATIFIED
};


#ifndef _E3dScene
#define _E3dScene
typedef struct E3dScene_S  E3dScene;
#endif


#ifndef _E3dRenderer
#define _E3dRenderer
typedef struct E3dRenderer_S         E3dRenderer;
typedef struct E3dRenderer_thread_S  E3dRenderer_thread;
#endif

#ifndef _E3dRayTracer
#define _E3dRayTracer
typedef struct E3dRayTracer_S         E3dRayTracer;
typedef struct E3dRayTracer_thread_S  E3dRayTracer_thread;
#endif

typedef struct E3dRendererSet_S  E3dRendererSet;


enum
{
	E3dGET = 1,
	E3dSET
};

enum
{
	E3dSHADER_ID = 1,
	E3dVERTEX_PROGRAM_RESOURCES,
	E3dVERTEX_PROGRAM_SOURCE,
	E3dFRAGMENT_PROGRAM_RESOURCES,
	E3dFRAGMENT_PROGRAM_SOURCE,
};



#define E3dPhotonCore\
	E3dKDTreePointCore;\
	char            Phi, Theta  /* quantized angle  */

typedef struct
{
	E3dPhotonCore;
} E3dCorePhoton;


typedef struct
{
	E3dPhotonCore;

	float  PowerRGB[3];
} E3dRGBPhoton;

typedef struct
{
	E3dPhotonCore;

	float           Power;
	unsigned short  SpectralIndex;
} E3dSpectralPhoton;


typedef struct
{
	E3dShadowMap*  ShadowMap;
	E3dCoordinate  LODSizeFactor;
	E3dMatrix      LightViewMatrix, LightViewMatrixInv,
	               LightFrustumMatrix, LightFrustumMatrixInv,
	               ShadowProjectionMatrix;         // World-space shadow Matrix
	EBool          Valid;
} E3dShadowMapT;


// E3dLights can be instantiated from multiple Models. This struct is used to store unique instances with spectrally sampled colors.
// Position, Direction etc. are cached here vs. accessing the Model and Light, so Renderers can run independently without worrying
// about Models and Lights changing in the Scene.
//
typedef struct 
{
	E3dCoord3      Position, Direction;                   // Position and direction (for spotlights) in world-space

	int            Type;                                  // Light source type
	EcRGBf32Color  RGBColor;
	float          Power[EcMAX_NUM_OF_SPECTRAL_SAMPLES];  // Spectral color of the light source
	float          DiffuseFactor, SpecularFactor;
	float          SpotAngle, SpotPenumbraAngle, SpotCutoffAngleCos, SpotExponent,
	               AttenuationConstant,
	               AttenuationLinear,
	               AttenuationQuadratic,
	               ShadowBrightness;       // If a point is in shadow from this light, its diffuse color will be multiplied by this value

	E3dShadowMapT  ShadowMap;
} E3dRLightInstance;
EListA_t(E3dRLightInstance);


// Renderers
//
typedef void   (*E3dRendererStatsUpdateFunc)(E3dRenderer* PRenderer);
typedef int    (*E3dRenderFunc)(E3dWindow* P3DWindow, E3dRenderer* PRenderer, E3dScene* PScene,  E3dCamera* PCamera, E3dModel* PCameraModel,  E3dRendererStatsUpdateFunc PStatsUpdateFunc);
typedef int    (*E3dRendererInfoFunc)(E3dRenderer* PRenderer,  E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable,  int, ...);
typedef EBool  (*E3dRendererUpdateFunc)(E3dRenderer*, E3dRenderer*);

typedef int    (*E3dTraceRayFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PResult);

typedef void   (*E3dShadePointFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

typedef void*  (*E3dBindMaterialFunc)(E3dMaterial* PMaterial, E3dRenderer* PRenderer, E3dGPUContext* PGPUContext,  E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable,  const E3dRLightInstance* PLightInstances, const EIndex PNLightInstances);
typedef void   (*E3dUnbindMaterialFunc)(E3dMaterial* PMaterial, void* PHwShader,  E3dRenderer* PRenderer, E3dGPUContext* PGPUContext);

typedef struct
{
	E3dCamera*    Camera;               // Currently used Camera. This is a COPY (not a reference) of the active Camera, to make sure
	                                    // that it does not change while a frame is being rendered, even in interactive/multi-threaded mode.
	E3dModel*     CameraModel;          // The Model that instantiates the Camera used - COPY

	EChangeFlags  SceneChangeFlags;     // What changed in the Scene
	EChangeFlags  RendererChangeFlags;  // What changed in this Renderer
} E3dChanges;

typedef struct E3dShaderDevice_S    E3dShaderDevice;
typedef struct E3dRendererClass_S   E3dRendererClass;
typedef struct E3dRendererClass_S*  E3dRendererClassPtr;
EList_t(E3dRendererClassPtr);


typedef void  (*E3dShaderClassRendererFunc)(E3dShaderClass* PShaderClass, E3dShaderClassRendererBinding* PShaderClassRendererBinding);

typedef uint32_t  E3dRendererFeatureFlags;

#define E3dRendererClassCore\
	EBaseClassCore(E3dRenderer);\
\
	void				(*ThreadInit)(const E3dRenderer* PRenderer, E3dRenderer_thread* PThread, const EBool PNew);  /* RendererThread constructor / resetter */\
	void				(*ThreadFree)(E3dRenderer_thread* PThread, const E3dChanges* PChanges);                      /* RendererThread destructor */\
	void				(*FreeCaches)(E3dRenderer* PRenderer);                                                       /* Free cached data (accelerators, triangles etc.) */\
\
	E3dRendererUpdateFunc       Update;             /* Called when Resources change                              */\
	E3dRenderFunc               Render;\
	E3dBindMaterialFunc         BindMaterial;\
	E3dUnbindMaterialFunc       UnbindMaterial;\
	ECoreObjectGUIFunc          GUI;                /* Update/free GUI panel for renderer instance               */\
	E3dShaderClassRendererFunc  ShaderClassFree;    /* Called when a ShaderClass is freed                                       */\
\
	E3dRendererInfoFunc         Info;               /* Get source code of a shader for this renderer class etc.  */\
	unsigned int                ShaderClassRendererBindingSize;\
	unsigned int                ShaderVariantSize;  /* Size of structure representing a shader variant for this renderer class  */\
	E3dShaderDevice*            ShaderDevice

struct E3dRendererClass_S
{
	E3dRendererClassCore;
};


// Display flags for 3DWindows
// Display-mode flags
//
#define E3dDF_NONE                  0x00000000
#define E3dDF_WIREFRAME_UNSELECTED  0x00000001  // Draw Edges of unselected objects
#define E3dDF_WIREFRAME_SELECTED    0x00000002  // Draw Edges of selected objects
#define E3dDF_SOLID                 0x00000010  // Draw surfaces
#define E3dDF_Z_BUFFER              0x00000100  // Use Z-buffer

#define E3dDF_COLOR                 0x00000400  // Color surfaces flat
#define E3dDF_LIGHTING              0x00000800  // Use lighting
#define E3dDF_TEXTURES              0x00001000
#define E3dDF_TRANSPARENCY          0x00002000

#define E3dDF_CONTROLS              0x00004000  // Drawing controls (we are after tone-mapping, so call GLDrawControls)

#define E3dDF_MODEMASK              0x00007FFF



// Display modes for type-selective window redrawing and picking
//
#define E3dDM_Wireframe    (E3dDF_WIREFRAME_UNSELECTED | E3dDF_WIREFRAME_SELECTED)
#define E3dDM_WireAndZ     (E3dDM_Wireframe | E3dDF_Z_BUFFER)
#define E3dDM_ColorWire    (E3dDF_WIREFRAME_SELECTED | E3dDF_Z_BUFFER | E3dDF_COLOR | E3dDF_Z_BUFFER)
#define E3dDM_ShadedWire   (E3dDF_WIREFRAME_SELECTED | E3dDF_Z_BUFFER | E3dDF_LIGHTING | E3dDF_Z_BUFFER)
#define E3dDM_Solid        (E3dDF_SOLID | E3dDF_Z_BUFFER)
#define E3dDM_ColorSolid   (E3dDF_SOLID | E3dDF_Z_BUFFER | E3dDF_COLOR)
#define E3dDM_ShadedSolid  (E3dDF_SOLID | E3dDF_Z_BUFFER | E3dDF_LIGHTING)


// Display flags for 3D windows
// Node flags
//
#define E3dDF_SELECTED   0x01000000
#define E3dDF_LOCKED     0x02000000
#define E3dDF_PICK       0x04000000

#define E3dDF_NODEFLAGS  0x07000000


#define E3dDF_ALL  (E3dDF_MODEMASK|E3dDF_NODEFLAGS)



enum
{
	E3dAreaLightsNONE = 0,
	E3dAreaLightsHEMISPHERE,
	E3dAreaLightsSAMPLE_AT_VTX,
	E3dAreaLightsSAMPLE,
	E3dAreaLightsSAMPLE_FOR_PHOTONS
};


enum
{
	E3dSampleRANDOM = 1,
	E3dSampleHALTON
};


typedef struct
{
	EInt32_2	Size;
	EInt32_2	RegionStart, RegionSize;
} E3dImageSpecs;




#define E3dLOD_MULT	(1.0 / 200.0)

// Renderer states
//
enum
{
	E3dIDLE = 0,
	E3dPREPARING_FRAME,
	E3dRENDERING,
	E3dPAUSED,    // Incomplete frame, Renderer stopped, but able to resume
	E3dSTOPPED    // Incomplete frame, Renderer stopped, not able to resume (but may not have to rebuild acceleration structures etc.)
};


typedef struct
{
	E3dCoord3  Position;
} E3dAnisoPoint;

typedef struct
{
	E3dCoord2      UVs[E3dMAX_TEXTURE_ANISOTROPY];
	E3dAnisoPoint  Points[E3dMAX_TEXTURE_ANISOTROPY];
	E3dST          STs[E3dMAX_TEXTURE_ANISOTROPY];
} E3dAniso;


enum
{
	E3dRADIANCE = 1,
	E3dIRRADIANCE_DIFFUSE,
	E3dIRRADIANCE_SHI,     // Spherical harmonics irradiance map
	E3dIRRADIANCE_SHR,     // Spherical harmonics radiance map / light field
	E3dIRRADIANCE_SHO,     // Spherical harmonics occlusion map
	E3dIRRADIANCE_WAVELET
};


typedef struct
{
	int      NumOfSubFrames;    // Render this many sub-frames and average them, e.g. for motion blur
	uint8_t  StartPixelSize;    // 2, 4, 8, 16 etc. for progressive, pixelized rendering
	uint8_t  PixelMeaning;      // E3dRADIANCE (normal rendering), E3dIRRADIANCE_DIFFUSE, E3dIRRADIANCE_SHI etc.
} E3dRendererSettables;



typedef struct
{
	float     Radius, Power, Strength;  // User-settable values

	float     Work_Power;               // Power that matches the kernel (kernel needs to be rebuilt if this changes)
	float     Work_Strength;            // Strength that matches the kernel (kernel needs to be rebuilt if this changes)
#ifdef USEOpenGL
	GLuint    GLKernelTexture;
	EInt32_2  GLFBOSize;
#endif
} E3dBloom;

extern EArg  E3dBloomUI_Radius[];
extern EArg  E3dBloomUI_Strength[];
extern EArg  E3dBloomUI_Power[];


#define E3dMAX_REFINEMENT_PASSES  8   // Supports starting rendering from 256x256 macro-pixels


//--------------------------------
// Lightmap rendering
//--------------------------------
typedef struct
{
	E3dTextureMapper*  TextureMapper;  /* Current texture-mapper and its:   */\
	E3dModel*          Model;          /* ... Model, ...                    */\
	E3dGeometry*       Geometry;       /* ... Geometry and ...              */\
	EObject*           Shadeable;      /* ... Shadeable (e.g. PolyGroup) for rendering light-maps  */\
} E3dLightMap;


#define E3dDrawNODE           0x01
#define E3dDrawNODE_CUSTOM    0x02
#define E3dDrawNODE_CONTROLS  0x04

typedef struct
{
	E3dNode*           Node;
	E3dNodeComponent*  NodeComponent;
	uint32_t           DrawFlags;      // E3dDrawNODE etc.
} E3dDrawItem;


// E.g. last stage: Items to draw after others, possibly in a sorted order (e.g. transparent objects, overdraw skeletons with no Z test,  etc.). Lazily grown array of Items
typedef struct
{
	EObjectCore(ECore);
	EListA(, E3dDrawItem, DrawItems);
} E3dDrawingStage;

extern EObjectClass  E3dDrawingStage_class[];

extern const char  E3dDrawingStage_Transparent[];
extern const char  E3dDrawingStage_Last[];


typedef struct
{
	uint16_t  MSAA;                 // Number of MSAA samples
	uint16_t  SSAA;                 // Supersampling / full-screen antialiasing (render at higher res, filter down), offscreen render target resolution subpixels. E.g. 4 means 2x resolution in both directions
	EBool     LineAA, PolygonAA;    // Traditional, blending-based line and polygon AA (e.g. on SGI)
} E3dGPUAA;


typedef struct
{
	uint32_t   Textures:1, HDRImage:1, ZBuffer:1, SolidSurfaces:1, FlatColor:1, Lighting:1, ProgressiveRefinement:1;
} E3dRendererFeatures;

#define E3dRendererCore\
	EObjectCore(E3dRenderer);\
\
	E3dCamera*  Camera;       /* Currently used Camera. This must be a COPY, not a reference to an  */\
	                          /* active Camera to make sure that it does not change while a frame   */\
	                          /* is being rendered, even in interactive/multi-threaded mode.        */\
	E3dModel*   CameraModel;  /* The Model that instantiates the Camera used                        */\
\
\
	E3dRendererSettables  User, Work;        /* "User" contains fields settable by the user. Those are copied over to "Work" when a frame starts. This is used for settings that could not be safely changed during rendering, and those that we want to reset after the frame started (e.g. we don't want "PixelMeaning" to stick when rendering irradiance maps). */\
\
	uint16_t  ColorModel;                    /* EpRGB or EpSPECTRAL             */\
	uint16_t  NumOfSpectralSamples;          /* E.g. 3 for RGB                  */\
	float     WavelengthMin, WavelengthMax;  /* Wavelength range in nanometers  */\
	float*    SpectralToR;                   /* RGB weights                     */\
	float*    SpectralToG;\
	float*    SpectralToB;\
	float     RGBtoXYZ[16];                  /* RGB to CIE XYZ Matrix    */\
	float     XYZtoRGB[16];                  /* CIE XYZ to RGB Matrix    */\
\
	E3dMaterial*  CameraIsInThisMaterial;    /* The Camera is inside this Material. Needed for volume effects */\
/* Point / spot etc. lights that are visible and have a non-black color */\
\
	EBool     UsePointLights;\
	EListA_s(, E3dRLightInstance, VisibleLights);\
\
	EIndex       FrameCnt;            /* Frames rendered since this Renderer was created.        */\
	EIndex       WorkingFrameCnt;     /* Frame currently being rendered (FrameCnt may be ahead)  */\
\
	E3dRendererSet*    RendererSet;\
	E3dRendererClass*  PreVizRendererClass;  /* E.g. wireframe to see the outline for long renders  */\
\
\
	E3dLightMap      LightMap;\
\
/* Image settings */\
\
	E3dImageSpecs    ImageSpecs;            /* Copied from the RendererSet when starting a render, so changes won't interfere with a running Renderer */\
\
	uint8_t          AntiAliasingType;      /* 0, E3dUNIFORM, E3dSTRATIFIED     */\
	int              NAnisotropicTextureSamples;  /* For anisotropic texture sampling  */\
\
	E3dBloom         Bloom;\
\
	EpToneMapper     HDRToneMapper;\
	EpExposure       Exposure;\
	int              ExposureMode;\
	EBool            KeepHDRImage;                 /* Create High Dynamic Range image  */\
	EBool            KeepHyperSpectralImage;       /* Keep hyperspectral PixelBiffer in Image0 / Image1, so it can be saved (e.g. to a TIFF file) */\
	EBool            SaveDepthImage;\
	EBool            SaveShadowImage;\
\
	double           SizeFactor;                   /* atan(FieldOfView / 2) * WindowSizeY, for LODs    */\
\
	EcRGBAf32Color   BackgroundRGBAfColor;\
\
	float            AmbientLightColor[EcMAX_NUM_OF_SPECTRAL_SAMPLES];\
\
\
	EIndex           NumOfExtraShaderLights;       /* Other than AreaLight samples       */\
\
	EListA_s(, E3dRPolyGroupPtr, RPolyGroups);     /* Pre-transformed RPolyGroups for rendering */\
\
	EListA_s(, E3dMaterialPtr, Materials);         /* This lookup table allows us to only duplicate each Material once, for rendering (rather than for every Shadeable. We identify Materials by their ID field. It also allows for optimizing Material updates in the Renderer. */\
\
	EList(, E3dAreaLight*, AreaLights);\
	EList(, E3dRTriangle*, LightTriangles);\
\
	uint8_t          AreaLightType;\
\
	EBool            TexturesOn, ShadowsOn, ReflectionsOn, TransparencyOn, FogOn;\
\
\
/* Internal variables */\
\
	E3dRendererFeatures  Features;\
\
	E3dDisplayFlags      HiddenObjectDisplayFlags;\
\
	EIndex2*         PixelRemappingGrid;             /* Image->Size.X * Size.Y grid with pseudo-randomly remapped pixel coordinates for frameless rendering  */\
	EIndex2*         PixelRemappingGridProgressive;  /* Image->Size.X * Size.Y grid with pseudo-randomly remapped pixel coordinates for frameless rendering  */\
\
	EpImage*         Image0;          /* Double buffering : the two buffers                         */\
	EpImage*         Image1;\
\
	EpImage*         ImageRend;       /* Double buffering : the image being rendered (back-buffer)  */\
	EpImage*         ImageDraw;       /* Double buffering : the image being shown (front-buffer)    */\
\
	EpImage*         AccumImage;      /* Temporal sub-frames are accumulated into this Image        */\
\
	EpImage*         ShadowImage;\
	EpImage*         ShadowAccumImage;\
\
	EpImage*         DepthImage;\
\
	E3dChanges  CurrentChanges;      /* Current changes being processed    */\
	E3dChanges  RequestedChanges;    /* Render queue for requesting changes while Renderer is busy, inactive etc. (i.e. the Ray-tracer in a 3DWindow's RendererSet is inactive when the current Renderer is GLSL)  */\
\
	EIndex               NumOfPixelsComputed, NumOfPixelsDrawn;\
\
	E3dOverlaySettings   Overlay;\
	E3dShow              Show;\
\
/* Internal variables */\
	uint32_t     State : 4,                /* E3dIDLE, E3dPREPARING_FRAME, E3dRENDERING, E3dPAUSED, E3dSTOPPED */\
	             BuildingAccelerator : 1,\
	             GoRender : 1, FirstIteration : 1, ShowProgress : 1,\
	             GoSequenceRender : 1;\
	ETime        StartMilliseconds, FinishMilliseconds,\
	             LastRedrawMilliseconds, LastStatsUpdateMilliseconds;\
	ETime        PrevFrameMicroSeconds, FrameTimeMicroseconds, LastDisplayedFrameTimeMicroseconds;\
\
	ETime        Start_TimeStamp;      /* The last time a frame started. Used to detect whether a change should trigger a re-render. */\
	int          SubFrame;             /* Current sub-frame        */\
	EIndex       NumOfShaderLights;\
\
	EListA(, E3dDrawingStage*, DrawingStages);  /* For example, "Opaque", "Transparent", "Last".  */\
\
/* Multi-threading */\
\
	EThreadSet       ThreadSet;\
	int              NumOfThreadLocks;\
	pthread_mutex_t  Workload_mutex, Progress_mutex, Exposure_mutex,\
	                 Debug_mutex;      /* Used for printfs etc. */\
\
	EIndex           CurrentPixelSizeR;\
	EIndex           NewPixelsPerPass[E3dMAX_REFINEMENT_PASSES];int  Pass;\
	int              AASubFrame;       /* Current anti-aliasing sub-frame            */\
	EIndex2          ImageSizeP;       /* Macro pixels, depends on CurrentPixelSizeR */\
	EIndex           NextPixel, PixelsPerPacket, EndPixel;\
	EBool            AreaLightsEnabled

struct E3dRenderer_S
{
	E3dRendererCore;
};



#define E3dGPURendererCore\
	E3dRendererCore;\
	E3dGPUAA  AntiAliasing

typedef struct
{
	E3dGPURendererCore;
} E3dGPURenderer;

extern E3dRendererClass  E3dGPURenderer_class[];



//#define E3dRENDERER_THREAD_MAX_STATESTACK_SIZE  16

#ifdef E3dRENDERER_THREAD_MAX_STATESTACK_SIZE
	#define E3dRendererThreadStateStack\
		char  StateStr[E3dRENDERER_THREAD_MAX_STATESTACK_SIZE][1024];\
		int   StatePtr
#else
	#define E3dRendererThreadStateStack
#endif  // E3dRENDERER_THREAD_MAX_STATESTACK_SIZE


//
// Data writable by individual rendering threads
//
#define E3dRenderer_thread_core\
	EThreadCore;\
\
	E3dWindow*       L3DWindow;\
	E3dRenderer*     Renderer;\
	E3dScene*        Scene;\
	E3dCamera*       Camera;E3dModel*  CameraModel;\
	E3dTraceRayFunc  TraceRayFunc;\
	E3dRendererThreadStateStack;\
\
	E3dLightMap      LightMap;\
\
	E3dCoord2        StratifiedUV;                             /* Anti-aliased renderers pass down stratified 0...1 U/V values  */\
/* For MIP-mapping / anisotropic texture filtering	*/\
	E3dAniso          Aniso;\
\
	E3dShaderLight*  ShaderLights;EIndex  NumOfShaderLights;  /* During rendering, we collect lights here that affect shaders (visible from a given point)  */\
	EIndex           NumOfLightSamplesFacingThisWay,          /* These are used for determining light occlusion ratio    */\
	                 NumOfUnshadowedLightSamples;\
	ERandomState     RandomState[5];\
	ERandomState     LightSamplingRandomState[5]



struct E3dRenderer_thread_S
{
	E3dRenderer_thread_core;
};



// RendererSets group varous Renderer instances (of different Classes) per view port (3DWindow)
//
struct E3dRendererSet_S
{
	EObjectCore(EObject);

	char*          FileName;            // For saving image(s)/animations
	double         StartFrame, EndFrame, FrameStep;

	E3dImageSpecs  ImageSpecs;          // Changeable by user

	EpImage*       ScaledBackgroundImage;

	EList(, E3dRenderer*, Renderers);   // Set of Renderers (one for each registered RendererClass)
	E3dRenderer*   Renderer;            // The current Renderer
	E3dRenderer*   LastRenderer;        // The user can toggle between this one and Renderer (middle button)
	EBool          InteractiveRedraw;
};


extern EResourceList       E3dRendererSet_resources;

EList_s(extern, E3dRendererClassPtr, E3dRenderer_Classes);

extern E3dRendererClass    E3dRenderer_class[];
extern E3dRendererClass    E3dRayTracer_class[];

extern E3dRendererClass*   E3dWireframeRenderer_class;
extern E3dRendererClass*   E3dWireAndZRenderer_class;
extern E3dRendererClass*   E3dHiddenLineRenderer_class;
extern E3dRendererClass*   E3dShadedSolidRenderer_class;
extern E3dRendererClass*   E3dGLSLRenderer_class;



extern const char*    E3dGL_SemanticCULL_FACE_ENABLE;
extern const char*    E3dGL_SemanticDEPTH_TEST_ENABLE;
extern const char*    E3dGL_SemanticBLEND_SRC_FACTOR;
extern const char*    E3dGL_SemanticBLEND_DST_FACTOR;



// Callback struct for 3DWindow redraws
//
typedef struct
{
	E3dCallbackStructCore;

	E3dScene*     Scene;
	E3dModel*     CameraModel;E3dCamera*  Camera;
	EChangeFlags	SceneChanged;
	EChangeFlags	RendererChanged;
} E3dDrawCallbackStruct;



#include <math.h>
#include <E3D/Math.h>


#define _E3d_ShadeSurface(pShadeSurfaceFunc,  pSurfacePoint,  pSurfaceMaterial, pVolumeShader, pShaderBeyondSurface,  pLightSamples, pNLightSamples,  pSpectralIndex, pNSpectralSamples,  pResult) do\
{\
	E3dSurfacePoint*  lSurfacePoint = pSurfacePoint;\
	const E3dShader*  lSurfaceShader = pSurfaceMaterial->Shader;\
	uint8_t  lTmpShader[lSurfaceShader->Class->StructSize];const E3dShader*  lTextureModulatedSurfaceShader = E3dShader_ModulateWithTextures(pSurfaceMaterial, lSurfaceShader, (E3dShader*)&lTmpShader,  lSurfacePoint->TextureColors,  pSpectralIndex, pNSpectralSamples);\
	pShadeSurfaceFunc(NULL,  lSurfacePoint,  pSurfaceMaterial, lTextureModulatedSurfaceShader, pVolumeShader, pShaderBeyondSurface,  pLightSamples, pNLightSamples,  pResult);\
} while(0)


#define E3dRenderer_ApplyChange(pField, pChangeFlags, pRendererChangeFlags) do\
{\
	if(LSettingsPanel->ApplyChangesToAllWindows)\
	{\
		EIndex  lPI = 0;for(;lPI < E3dp_Panels_Count;lPI++)\
		{\
			E3dPanel*  l3DPanel = E3dp_Panels[lPI];EIndex  lWC = 0;const EIndex  lWN = l3DPanel->Windows_Count;for(;lWC < lWN;lWC++)\
			{\
				E3dWindow*  l3DWindow = l3DPanel->Windows[lWC];E3dpWindow*  l3DpWindow = E_DynamicCast(l3DWindow, E3dpWindow);if(l3DpWindow && l3DpWindow != LEdited3DWindow)\
				{\
					if(l3DWindow->StatusFlags & E3dWF_CREATED)\
					{\
						if(l3DpWindow->RendererSet->Renderer && LEdited3DWindow->RendererSet->Renderer)\
						{\
							const E3dRenderer*  lSrcRenderer = LEdited3DWindow->RendererSet->Renderer;\
							E3dRenderer*        lDstRenderer = l3DpWindow->RendererSet->Renderer;\
							lDstRenderer->pField = lSrcRenderer->pField;\
						}\
					}\
				}\
			}\
		}\
		E3dWindow_Redraw((E3dWindow*)LEdited3DWindow,  pChangeFlags, pRendererChangeFlags,  TRUE);\
		E3dWindows_RedrawByChangeFlagsExt(pChangeFlags, (E3dWindow*)LEdited3DWindow, FALSE, TRUE);\
	}\
	else E3dWindow_Redraw((E3dWindow*)LEdited3DWindow,  pChangeFlags, pRendererChangeFlags,  TRUE);\
} while(0)



//----------------------------------------
// Rendering/Rendering.c
//----------------------------------------
extern E3dDrawingStage*   E3dRenderer_DrawingStageByName(E3dRenderer* PRenderer, const char* PName);

extern int                E3d_ShadowPixelType, E3d_ShadowComponentsPerPixel, E3d_ShadowPixelComponentType;
extern void               E3dp_RenderingHWInfoInit(EpGPUInfo* PHardwareInfo);
extern void               E3d_RenderingInit(void);

extern void               _E3d_ImageSpecsCopy(E3dImageSpecs* PSrc, E3dImageSpecs* PDst);

extern void               E3d_RendererUpdateSpectrum(E3dRenderer* PRenderer);

extern E3dRendererClass*  E3dRenderer_ClassByName(const char* PName);
extern E3dRendererClass*  E3dRenderer_ClassRegister(E3dRendererClass* PTemplate);
extern E3dRendererClass*  E3dRenderer_ClassRegisterInactive(const char* PName);
extern void               E3dRenderer_ClassDeactivate(E3dRendererClass* PClass);
extern void               E3dRenderer_ClassesFree(void);
extern EIndex             E3dRenderer_ClassActiveSubClasses(E3dRendererClass* PClass);


extern E3dRenderer*     E3dRenderer_New(E3dRendererClass* PRendererClass);
extern E3dRenderer*     E3dRenderer_CloneNew(E3dRendererClass* PRendererClass);

extern void             E3dRender_QueueClear(E3dChanges* PEntry);
extern void             E3dRenderer_ClearChangeQueues(E3dRenderer* PRenderer);

extern void             E3dRenderer_ImagesFree(E3dRenderer* PRenderer);
extern void             E3dRenderer_FreeCaches(E3dRenderer* PRenderer);

extern void             E3dRenderer_ThreadsFree(E3dRenderer* PRenderer, const E3dChanges* PChanges, const EBool PFreeArray);

extern void             E3dRenderer_DrawingStagesReset(E3dRenderer* PRenderer);

extern int              E3dRenderer_Finish(E3dRenderer* PRenderer, const ETime PPollMicroseconds, const ETime PTimeoutMilliseconds, const EBool PSyncGUI);
extern EBool            E3dRenderer_Stop(E3dRenderer* PRenderer);
extern void             E3dRenderers_Stop(void);
extern void             E3dRenderers_FreeCaches(void);
extern void             E3dRenderers_MarkChanges(const EChangeFlags PSceneChangeFlags);

extern void             E3dRendererSet_Init(E3dRendererSet* PRendererSet);
extern E3dRendererSet*  E3dRendererSet_New(void);
extern int              E3dRendererSet_InitToRayTracer(E3dRendererSet* PRendererSet);
extern E3dRenderer*     E3dRendererSet_RendererByClass(const E3dRendererSet* PRendererSet, const E3dRendererClass* PRendererClass);

extern void             E3dRendererSet_CreateRenderers(E3dRendererSet* PRendererSet);
extern E3dRenderer*     E3dRendererSet_RendererByClassName(E3dRendererSet* PRendererSet, const char* PRendererClassName);
extern void             E3dRendererSet_MarkChanges(E3dRendererSet* PRendererSet, E3dRenderer* PExceptThisRenderer, const EChangeFlags PSceneChangedFlags);
extern void             E3dRenderer_CheckImageBuffers(E3dRenderer* PRenderer);
extern void             E3d_RendererUpdatePixelRemappingGrids(E3dRenderer* PRenderer);

extern EBool            E3dRenderer_NeedsToneMapping(const E3dRenderer* PRenderer);
extern E3dDisplayFlags  E3dRenderer_DisplayFlags(E3dRenderer* PRenderer);

extern EBool            E3d_MultiThreadedRendererRedraw(E3dWindow* P3DWindow, E3dRayTracer* PRenderer, E3dScene* PScene);

extern void       E3dModels_ShadowMatrix(E3dModel** PModels, const EIndex PNumOfModels,  E3dShadowMap* PShadowMap, E3dRLightInstance* PLight,  E3dMatrix PFrustumMatrix, E3dMatrix PReceiverSTMatrix);


extern int         E3d_TraceRayOpaque(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PColor);
extern int         E3d_TraceRay(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, E3dStackRay* PRay, int PRayIter, E3dShaderResult* PColor);

extern int         E3dWindow_RenderFrameWithRenderer(E3dWindow* P3DWindow, E3dScene* PScene, E3dRenderer* PRenderer,  E3dCamera* PCamera, E3dModel* PCameraModel,  const EBool PSwapBuffers);

extern void        E3dRenderer_FreeLightSamples(E3dRenderer* PRenderer);

extern E3dCoord2*  E3d_CreateStratifiedSamples2D(const EIndex PNumOfSamples, const char PType, ERandomState* PState, E3dCoord2* PSamples);


extern void        E3d_HemisphereSampleCosineWeightTBN(const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoord3* PTangent, const E3dCoord3* PBitangent, const E3dCoord3* PNormal, E3dCoord3* PResult);
extern E3dCoord3*  E3d_CreateSphereSamples(const EIndex PNumOfSphereSamples, const char PType, ERandomState* PRandomState, const EBool PCosineWeight);


extern char*       E3dDisplayFlags_ToString(const E3dDisplayFlags PFlags, char* PString);
extern char*       E3d_ItemFlagsToString(const E3dDisplayFlags PFlags, char* PString);

extern void        E3dGPUAA_Init(E3dGPUAA* PAA);

extern char*       E3dRenderer_StateToString(const E3dRenderer* PRenderer, char* PStr, const EIndex PMaxLen);

extern EBool       E3dChanges_HasChanges(const E3dChanges* PChanges);


//----------------------------------------
// Rendering/Raytracing.c
//----------------------------------------
extern int         E3dRenderer_NSubPixels(const E3dRenderer* PRenderer);
extern EBool       E3dScene_CollectAreaLights(const E3dScene* PScene, E3dRenderer* PRenderer, const EBool PForce);
extern void        E3dScene_SampleLights(const E3dScene* PScene, E3dRenderer* PRenderer, const int PAreaLightType, const EIndex PNumOfSamplesPerLight, const EBool PForce);



//----------------------------------------
// Accelerator.c
//----------------------------------------
typedef struct
{
	EIndex  TotalNumOfPolygons, TotalNumOfTriangles, GlobalTriangleIndex;
} E3dGetRenderTrianglesStats;

extern EObjectClass  E3dAccelerator_class[];

// Flags for E3dObject_GetRenderShapes()
//
#define E3dGrtINVERTED             0x0001
#define E3dGrtDO_INVISIBLE         0x0002
#define E3dGrtDO_WIREFRAME         0x0004
#define E3dGrtSHOW_FACES           0x0008
#define E3dGrtAREALIGHTS_ONLY      0x0010
#define E3dGrtUSE_LOD              0x0020
#define E3dGrtPREP_FOR_RAYTRACING  0x0040  // Prep RTriangles for CPU ray-tracing

extern int     E3dObject_GetRenderShapes(E3dObject* PObject, E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,  const uint16_t PFlags,  const E3dCoordinate* PVertexMatrix, const E3dCoordinate* PNormalMatrix,  E3dRPolyGroupPtrListA* PRenderPolyGroups, E3dGetRenderTrianglesStats* PStats);

#define E3dWORLD  0x01
#define E3dVIEW   0x02

extern EIndex  E3dScene_GetRenderShapes(E3dScene* PScene, E3dRenderer* PRenderer, const uint16_t PFlags, const EBool PFreeOld, const EBool PAreaLightsOnly);
extern EBool   E3dRenderer_ShapesFree(E3dRenderer* PRenderer);
extern EBool   E3dRPolyGroups_Free(E3dRPolyGroupPtrListA* PRPolyGroups);

extern EIndex  E3dObject_RPolyGroupsUpdate(const E3dObject* PObject, const E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,  const E3dCoordinate* PVertexMatrix, const E3dCoordinate* PNormalMatrix, E3dRPolyGroupPtrListA* PRPolyGroups, EIndex* PGlobalTriangleIndex);

extern int     E3dObject_RPolyGroupsUpdateMaterials(E3dObject* PObject, E3dModel* PModel,  E3dRenderer* PRenderer, E3dMaterialPtrListA* POldMaterials,  const E3dCoordinate* PWorldToViewerMatrix, const E3dCoordinate PSizeFactor, E3dRPolyGroupPtrListA* PRPolyGroups);
extern int     E3dScene_RPolyGroupsUpdateMaterials(E3dScene* PScene, E3dRayTracer* PRenderer);

extern void    E3dRenderer_BuildAccelerator(E3dRayTracer* PRenderer);


//----------------------------------------
// Rendering/Lights.c
//----------------------------------------
#define E3dSAMPLING_TILES_PER_SET  42

extern void    E3d_SamplingTilesCreate(void);
extern void    E3d_SamplingTilesFree(void);

extern EIndex  _E3d_AddPointLights(const E3dRenderer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);
extern EIndex  _E3d_AddPointLightsSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracerThread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  E3dAreaLight_NumOfElements(const E3dAreaLight* PAreaLight);

extern EIndex  _E3d_AddAreaLightSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace, const E3dAreaLight* PLight, const float PShadowBrightness, E3dGridRay* PShadowRay, const EInt32_3* PStartVoxelC,  const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PShaderLightStartIndex);

extern EIndex  _E3d_AddAreaLights(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const int PSpectralIndex, const int PNSpectralSamples, const EIndex PShaderLightStartIndex);
extern EIndex  _E3d_AddAreaLightsSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace, const int PSpectralIndex, const int PNSpectralSamples, const EIndex PShaderLightStartIndex);
extern EIndex  _E3d_AddAreaLightsOneSampleSh(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);

extern EIndex  _E3d_AddAreaLights_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint,  const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PLightSampleStartIndex, const EIndex PSampleNumDivider);
extern EIndex  _E3d_AddAreaLightsSh_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  const EIndex PLightSampleStartIndex, const EIndex PSampleNumDivider);
extern EIndex  _E3d_AddAreaLightsOneSample_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);
extern EIndex  _E3d_AddAreaLightsOneSampleSh_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PRayTracer_thread, const E3dSurfacePoint* PPoint, const void* PExcludeFace,  const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderLight* PShaderLight);


extern void    E3d_ShadePoint(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);
extern void    E3d_ShadePointShadows(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);
extern void    E3d_ShadePointShadowsNoBRDF(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadePoint_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);
extern void    E3d_ShadePointShadows_Volume(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PPoint, E3dStackRay* PRay,  const E3dMaterial* PSurfaceMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadeSurfaceIrradiance(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsIrradianceFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsIrradianceFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsRadianceFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsRadianceFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    (*E3d_ShadeSurfaceSphericalHarmonicsOcclusionFunc)(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);
extern void    (*E3d_ShadePointSphericalHarmonicsOcclusionFunc)(const E3dRayTracer* PRenderer, E3dRayTracer_thread* PThread, const E3dRTriangle* PRTriangle, E3dSurfacePoint* PSurfacePoint, E3dStackRay* PRay,  const E3dMaterial* PMaterial, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface,  E3dShaderResult* PResultTmp, E3dShaderResult* PResult);

extern void    E3d_ShadeSurfaceWavelets(const E3dShaderLight* PLights, const int PNLights,  E3dShaderResult* PResult);


extern E3dShaderClassRendererBinding*  E3dShader_ClassAddRenderer(E3dShaderClass* PShaderClass, E3dRendererClass* PRendererClass, const E3dShaderClassRendererBinding* PDescriptor);
extern E3dShaderClassRendererBinding*  E3dShader_ClassGetRendererBinding(const E3dShaderClass* PShaderClass, const E3dRendererClass* PRendererClass);
extern void                            E3dShader_ClassRemoveRenderer(E3dShaderClass* PShaderClass, const E3dRendererClass* PRendererClass);

// PFlags - which stages are used by the ShaderClass
#define E3dShaderStageVERTEX         0x0001    // Shader class has a hand-wrirtten VertexProgram
#define E3dShaderStageVERTEX_AUTO    0x0002    // Shader class has auto-generated VertexPrograms
#define E3dShaderStageGEOMETRY       0x0004    // Shader class has a hand-wrirtten GeometryProgram
#define E3dShaderStageGEOMETRY_AUTO  0x0008    // Shader class has auto-generated GeometryPrograms
#define E3dShaderStageFRAGMENT       0x0010    // Shader class has a hand-wrirtten FragmentProgram
#define E3dShaderStageFRAGMENT_AUTO  0x0020    // Shader class has auto-generated FragmentPrograms
#define E3dShaderStageALL            0x003F
extern void                            E3dShaderClass_RendererBindingsAdd(E3dShaderClass* PShaderClass, const EResourceList* PResources, const uint32_t PFlags, const char* PSource);

extern void                            E3dShaderVariant_CoreFree(E3dShaderVariant* PVariant);


// Language-agnostic shader cores for built-in shaders
extern const char*  Material_Constant_core_sl;
extern const char*  Material_Lambert_core_sl;
extern const char*  Material_Phong_core_sl;


extern EResourceList  E3dSL_ConstantResources;
extern EResourceList  E3dSL_LambertResources;
extern EResourceList  E3dSL_PhongResources;


extern E3dShaderVariant*  (*E3dShader_GenerateCode)(const E3dModel* PModel, const E3dGeometry* PGeometry, const EObject* PShadeable,
 E3dRenderer* PRenderer,
 E3dShaderClassRendererBinding* PShaderClassBinding,
 const E3dShaderPassesMask PPassesMask,
 const E3dRLightInstance* PLights, const EIndex PNLights, E3dShadowMap** PShadowMaps,
 EResourceList* PFrgResources, EResourceList* PVtxResources,
 const EBool PAddComments);

extern void  E3dShader_UpdateNormalMap(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial, E3dShader* PShader, E3d2DTexture* PTexture, const E3dTextureMapper* PTextureMapper);
extern EBool E3dMaterial_SLUpdateNormalMaps(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial,  const E3dTextureMapperPtrList* PTextureMappers, const E3dTextureMapperPtrList* PTextureMapperRefs);


extern void  (*E3dShader_GLUpdateNormalMap)(E3dScene* PScene, E3dRenderer* PRenderer, E3dMaterial* PMaterial, E3dShader* PShader, E3d2DTexture* PTexture, const E3dTextureMapper* PMapper);

extern void  E3dNodeHrc_GLDraw(E3dWindow* P3DWindow, E3dScene* PScene, E3dRenderer* PRenderer, const uint32_t PSubCameraID, E3dDrawContext* PDrawContext,  E3dNode* PRootNode, const E3dMatrix PWorldToViewerMatrix, const EBool PDrawCamera);
extern void  E3dGL_WindowSetViewTransform(E3dWindow* P3DWindow, E3dRenderer* PRenderer, E3dScene* PScene, const uint32_t PSubCameraID, const EIndex PAAMult,  E3dMatrix PWorldToViewerMatrix);


//----------------------------------------
// GL/DrawMesh.c
//----------------------------------------
extern int  E3dGL_DrawUnlit(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,  E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);
extern int  E3dGL_DrawLambert(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,  E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);
extern int  E3dGL_DrawPhong(E3dRenderer* PRenderer, const E3dDrawContext* PDrawContext, E3dGPUContext* PGPUContext,  E3dScene* PScene, E3dModel* PModel, E3dGeometry* PGeometry, EObject* PShadeable);


//----------------------------------------
// GL/FrameBuffer.c
//----------------------------------------
enum
{
	E3dBloom2D_POWER = 1,
	E3dBloomSEPARABLE_POWER,
	E3dBloomSEPARABLE_GAUSSIAN,
};

extern void  E3dGL_UpdateBloomKernel(E3dRenderer* PRenderer, E3dBloom* PBloom, const int PBloomRadius);

//----------------------------------------
// E3D/Panel/Renderer.c
//----------------------------------------
extern void  E3dRenderer_UpdateStats(E3dRenderer* PRenderer);
extern void  E3dRenderer_UpdateStatsAndSync(E3dRenderer* PRenderer);
extern void  E3dDrawContext_UpdateFromRenderer(E3dDrawContext* PDrawContext, const E3dRenderer* PRenderer);


//----------------------------------------
// SLCommon.ci
//----------------------------------------
extern int E3dSRenderer_ClassFree(E3dRendererClass* PClass);

typedef struct E3dPolyGroupComponentRenderCustomShape_S  E3dPolyGroupComponentRenderCustomShape;

#define E3dPolyGroupComponentRenderCustomShapeCore\
	E3dPolyGroupComponentCore;\
\
	E3dBBox  BBox;\
\
	EBool  (*TraceRayClosestHit)(E3dRay* PRay,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PComponentRenderCustomShape,  E3dRayHit* PRayHit);\
	EBool  (*TraceShadowRay)(E3dRay* PRay,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PComponentRenderCustomShape, const E3dCoordinate PMaxDistance);\
	EBool  (*RayMarchVolume)(const E3dIRay* PRay, const E3dRayHit* PRayHit,  const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PVolumePG,  E3dShaderResult* PResult);\
	void   (*SampleSurface)(const E3dRPolyGroup* PRPolyGroup, E3dPolyGroupComponentRenderCustomShape* PPolyGroupComponent,  E3dSurfacePoint* PSurfacePoint)


struct E3dPolyGroupComponentRenderCustomShape_S
{
	E3dPolyGroupComponentRenderCustomShapeCore;
};

// PolyGroupComponent baseclass for rendering custom shapes like implicit surfaces, SDFs etc.
//
extern E3dPolyGroupComponentClass  E3dPolyGroupComponentRenderCustomShape_class[];


//========================================================
// Sign-agnostic Fresnel term (PNdotV may be negative)
// Schlick's approximation
//========================================================
static inline float E3d_Fresnel(const float PCurrentRefractiveIndex, const float PRefractiveIndex, const float PNdotV)
{
/*
	const float  Leta = PCurrentRefractiveIndex / PRefractiveIndex;
	const float  LT0 = Leta - 1.0, LT1 = Leta + 1.0,
	             LR0 = LT0 * LT0 / (LT1 * LT1);
*/

	const float  LT0 = PCurrentRefractiveIndex - PRefractiveIndex, LT1 = PCurrentRefractiveIndex + PRefractiveIndex, LT2 = LT0 / LT1,
	             LR0 = LT2 * LT2;  // Reflectivity at normal incidence


//printf("%s() %s:%d  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, LT0, LT1, LR0);fflush(stdout);

// Simplified Fresnel term (Schlick's approximation)
//
	const float  c = 1.0 - fabsf(PNdotV);
	return(LR0 + (1.0 - LR0) * c * c * c * c * c);

// The full Fresnel term
//
/*
	const float  c = 1.0 - fabsf(PNdotV);
	float        g = E3d_Sqrt(PRefractiveIndex * PRefractiveIndex + c * c - 1.0), gPc = g + c, gMc = g - c;

	return(0.5 * gMc * gMc / (gPc * gPc) * (1.0 + (c * gPc - 1.0) * (c * gPc - 1.0) / ((c * gMc + 1.0) * (c * gMc + 1.0))));
*/
}


//========================================================
// Fresnel term for conductors (metal)
//========================================================
static inline void E3d_MetalFresnel(const float* PEtas,
 const float* Pks,
 const int PNSpectralSamples,
 const E3dCoordinate PCosI,
 float* PReflectivity)
{
	const E3dCoordinate  LCosI2 = PCosI * PCosI;

	int  LI = 0;for(;LI < PNSpectralSamples;LI++)
	{
		const E3dCoordinate  LEta = PEtas[LI], Lk = Pks[LI],
		                     LTmpF = LEta * LEta + Lk * Lk, LTmp  = LTmpF * LCosI2,
		                     L2EtaCosI = 2.0 * LEta * PCosI;

		const E3dCoordinate  LRparl2 = (LTmp  - (L2EtaCosI) + 1.0)    / (LTmp  + (L2EtaCosI) + 1.0);
		const E3dCoordinate  LRperp2 = (LTmpF - (L2EtaCosI) + LCosI2) / (LTmpF + (L2EtaCosI) + LCosI2);

		E3dCoordinate        LF = (LRparl2 + LRperp2) * 0.5;if(LF < 0.0) LF = 0.0;

		PReflectivity[LI] = LF;
	}
}


//========================================================
// Fresnel term for dielectrics
//========================================================
static inline void E3d_DielectricFresnel(const float* PEtas_i, const float* PEtas_t,  const int PNSpectralSamples,  const E3dCoordinate PCosI,  float* PReflectivity)
{
	const E3dCoordinate  L2 = E3d_Sqrt(E_Max(0.0, 1.0 - PCosI * PCosI));
	const E3dCoordinate  LCosI = E3d_Abs(PCosI);

	int  LI = 0;for(;LI < PNSpectralSamples;LI++)
	{
		const float          LEtaI = PEtas_i[LI], LEtaT = PEtas_t[LI];
		const E3dCoordinate  LSinT = LEtaI / LEtaT * L2;	// Compute sin t with Snell's law

		if(LSinT >= 1.0)
		{
// Handle total internal reflection
//
			PReflectivity[LI] = 1.0;
		}
		else
		{
			const E3dCoordinate  LCosT = E3d_Sqrt(E_Max(0.0, 1.0 - LSinT * LSinT));

			const E3dCoordinate  LEtaTxLCosI = LEtaT * LCosI,
			                     LEtaIxLCosI = LEtaI * LCosI,
			                     LEtaIxLCosT = LEtaI * LCosT,
			                     LEtaTxLCosT = LEtaT * LCosT;

			const E3dCoordinate  LRParl = (LEtaTxLCosI - LEtaIxLCosT) / (LEtaTxLCosI + LEtaIxLCosT);
			const E3dCoordinate  LRPerp = (LEtaIxLCosI - LEtaTxLCosT) / (LEtaIxLCosI + LEtaTxLCosT);

			PReflectivity[LI] = (LRParl * LRParl + LRPerp * LRPerp) * 0.5;
		}
	}
}


//========================================================
// Dielectric Fresnel term for shaders
//========================================================
static inline void E3d_ShaderFresnel(E3dSurfacePoint* PPoint, const E3dShader* PVolumeShader, const E3dShader* PShaderBeyondSurface, const int PSpectralIndex, const int PNSpectralSamples,  E3dShaderResult* PResult)
{
	const E3dCoord3      LNormal = PPoint->Normal;
	const E3dCoord4      LPointToViewer = PPoint->PointToViewer;
	const E3dCoordinate  LNdotV = E_Dot3_vv(LNormal, LPointToViewer);

	if(0)
	{
// Old Fresnel term (Schlick's approximation)
//
		const float  LCurrentRefractiveIndex = PVolumeShader ? PVolumeShader->RefractiveIndex.Samples[PSpectralIndex] : 1.0;
		const float  LNextRefractiveIndex    = PShaderBeyondSurface ? PShaderBeyondSurface->RefractiveIndex.Samples[PSpectralIndex] : 1.0;
		const float  LFresnel = E3d_Fresnel(LCurrentRefractiveIndex, LNextRefractiveIndex, LNdotV);
		const float  LNFresnel = 1.0 - LFresnel;

		{
			E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)
			{
				PResult->Reflective[LSI] *= LFresnel;PResult->Transparent[LSI] *= LNFresnel;
			}
		}
	}
	else
	{
// Physically accurate dielectric Fresnel term (i.e. glass)
//
		float  LFresnel[PNSpectralSamples];
		E3d_DielectricFresnel(PVolumeShader ? PVolumeShader->RefractiveIndex.Samples + PSpectralIndex : E3d_Ones,
		 PShaderBeyondSurface ? PShaderBeyondSurface->RefractiveIndex.Samples + PSpectralIndex : E3d_Ones,  PNSpectralSamples,  LNdotV,  LFresnel);

		{
			E3dM_SpectralLoop(PSpectralIndex, PNSpectralSamples)
			{
				const float  LFresnelV = LFresnel[LSC];
				PResult->Reflective[LSI] *= LFresnelV;PResult->Transparent[LSI] *= 1.0 - LFresnelV;
			}
		}
	}
}


//========================================================
// Fresnel term for dielectrics - enter/exit check
//========================================================
static inline void E3d_DielectricFresnelDirCheck(const float* PEtas_i, const float* PEtas_t, const int PNSpectralSamples, const E3dCoordinate PCosI, float* PReflectivity)
{
	const EBool   LEntering = PCosI > 0.0;
	const float*  LEtas_i;
	const float*  LEtas_t;

//assert(PCosi >= -1.0 && PCosi <= 1.0);
	if(LEntering) { LEtas_i = PEtas_i;LEtas_t = PEtas_t; }
	else { LEtas_i = PEtas_t;LEtas_t = PEtas_i; }

	{
		const E3dCoordinate  L2 = E3d_Sqrt(E_Max(0.0, 1.0 - PCosI * PCosI));
		const E3dCoordinate  LCosI = E3d_Abs(PCosI);

		int  LI = 0;for(;LI < PNSpectralSamples;LI++)
		{
			const float          LEtaI = LEtas_i[LI], LEtaT = LEtas_t[LI];
			const E3dCoordinate  LSinT = LEtaI / LEtaT * L2;	// Compute sin t with Snell's law

			if(LSinT >= 1.0)
			{
// Handle total internal reflection
//
				PReflectivity[LI] = 1.0;
			}
			else
			{
				const E3dCoordinate  LCosT = E3d_Sqrt(E_Max(0.0, 1.0 - LSinT * LSinT));

				const E3dCoordinate  LEtaTxLCosI = LEtaT * LCosI,
				                     LEtaIxLCosI = LEtaI * LCosI,
				                     LEtaIxLCosT = LEtaI * LCosT,
				                     LEtaTxLCosT = LEtaT * LCosT;

				const E3dCoordinate  LRParl = (LEtaTxLCosI - LEtaIxLCosT) / (LEtaTxLCosI + LEtaIxLCosT);
				const E3dCoordinate  LRPerp = (LEtaIxLCosI - LEtaTxLCosT) / (LEtaIxLCosI + LEtaTxLCosT);

				PReflectivity[LI] = (LRParl * LRParl + LRPerp * LRPerp) * 0.5;
			}
		}
	}
}


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}	// Close scope of 'extern "C"' declaration
#endif

#endif	// _E3D_Rendering_h
