//================================================================================================
// Plugin for EQUINOX-3D
//
// Example to show how to create custom Node.
// This plugin visualizes the CIE  RGB <-> color spectrum curves.
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-16 01:38:51
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================================================
#include <assert.h>
#include <stdio.h>

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

#include <E3D/Matrix.h>
#include <E3D/Scene.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>


#include <Xe/Matrix.h>
#include <Xe/PushButton.h>
#include <Xe/Scale.h>
#include <Xe/ToggleButton.h>

#include <Color/Panel.h>

#include <E3D/UI/Panel.h>
#include <E3D/UI/Panels.h>
#include <E3D/UI/StatusPanel.h>
#define XeVosfActivate				(XeVirtualKeys+0)
#define XeVosfAddMode				(XeVirtualKeys+12)
#define XeVosfBackSpace				(XeVirtualKeys+23)
#define XeVosfBeginLine				(XeVirtualKeys+36)
#define XeVosfCancel				(XeVirtualKeys+49)
#define XeVosfClear				(XeVirtualKeys+59)
#define XeVosfCopy				(XeVirtualKeys+68)
#define XeVosfCut				(XeVirtualKeys+76)
#define XeVosfDelete				(XeVirtualKeys+83)
#define XeVosfDown				(XeVirtualKeys+93)
#define XeVosfEndLine				(XeVirtualKeys+101)
#define XeVosfHelp				(XeVirtualKeys+112)
#define XeVosfInsert				(XeVirtualKeys+120)
#define XeVosfLeft				(XeVirtualKeys+130)
#define XeVosfMenu				(XeVirtualKeys+138)
#define XeVosfMenuBar				(XeVirtualKeys+146)
#define XeVosfPageDown				(XeVirtualKeys+157)
#define XeVosfPageLeft				(XeVirtualKeys+169)
#define XeVosfPageRight				(XeVirtualKeys+181)
#define XeVosfPageUp				(XeVirtualKeys+194)
#define XeVosfPaste				(XeVirtualKeys+204)
#define XeVosfPrimaryPaste			(XeVirtualKeys+213)
#define XeVosfQuickPaste			(XeVirtualKeys+229)
#define XeVosfRight				(XeVirtualKeys+243)
#define XeVosfSelect				(XeVirtualKeys+252)
#define XeVosfUndo				(XeVirtualKeys+262)
#define XeVosfUp				(XeVirtualKeys+270)
#define XeVosfBackTab				(XeVirtualKeys+276)
#define XeVosfBeginData				(XeVirtualKeys+287)
#define XeVosfDeselectAll			(XeVirtualKeys+300)
#define XeVosfEndData				(XeVirtualKeys+315)
#define XeVosfEscape				(XeVirtualKeys+326)
#define XeVosfExtend				(XeVirtualKeys+336)
#define XeVosfLeftLine				(XeVirtualKeys+346)
#define XeVosfNext				(XeVirtualKeys+358)
#define XeVosfNextField				(XeVirtualKeys+366)
#define XeVosfNextMenu				(XeVirtualKeys+379)
#define XeVosfNextMinor				(XeVirtualKeys+391)
#define XeVosfPrevField				(XeVirtualKeys+404)
#define XeVosfPrevMenu				(XeVirtualKeys+417)
#define XeVosfPrior				(XeVirtualKeys+429)
#define XeVosfPriorMinor			(XeVirtualKeys+438)
#define XeVosfReselect				(XeVirtualKeys+452)
#define XeVosfRestore				(XeVirtualKeys+464)
#define XeVosfRightLine				(XeVirtualKeys+475)
#define XeVosfSelectAll				(XeVirtualKeys+488)
#define XeVosfSwitchDirection			(XeVirtualKeys+501)


char*  E_PluginInfo = "Visualize BRDFs - Compare ShadeSurface, SampleDirection and SampleDirections functions of a Shader,\nfor debugging.";

static EPlugin*  _Plugin = NULL;

static XeDWidget  _MenuButtons[16];static int  _NumOfMenuButtons = 0;
static XeDWidget  _ToolbarButtons[16];static int  _NumOfToolbarButtons = 0;

#define E3dMAX_NEAREST_POINTS  65536

EpIconDef3I(static, BRDFVis);

typedef struct
{
	E3dKDTreePointCore;
	float  Intensity;
} E3dBRDFSamplePoint;


typedef struct
{
	E3dMesh*        Mesh;
	E3dCoordinate*  UnitSpherePoints;EIndex  NumOfUnitSpherePoints;
	E3dCoordinate*  RotatedUnitSpherePoints;  // The sphere Mesh is oriented, so its "north/south axis" aligns with the mirror reflection vector, to get the best resolution
	EBool           GrabUnitSphere;           // Sphere Mesh topology updated, cache Vertex positions as "unit sphere"
} E3dVisGeo;


// Direction of sampling. BRDFs are reciprocal by definition (B meaning bi-directional), but this allows us to visualize either way
enum
{
	E3dSAMPLE_LIGHTDIRS = 1,    // Fixed viewing direction, sample incoming light - this is how shader importance sampling works (SampleDirection() and SampleDirections() functions)
	E3dSAMPLE_VIEWDIRS          // Fixed light direction, sample viewing directions
};


// How to reconstruct the BRDF surface from importance-sampling.
enum
{
	E3dGAUSSIAN_SPLATS = 1,  // Density estimation, using gaussian splats on the hemisphere and kd-trees for look-up (a-la photon mapping)
	E3dSPHERICAL_HARMONICS,
	E3dWAVELET
};


typedef struct
{
	E3dNodeComponentCore;

	E3dCoordinate      BlackRadius, Scale;

	int                Dimensions;

	E3dMaterial*       Material;

	EBool              OrientSphere;

	E3dVisGeo          ShadeSurface;
	E3dVisGeo          SampleDirection;
	E3dVisGeo          SampleDirections;

	int                SamplesSqrt;
	float              SplatRadius;        // For E3dGAUSSIAN_SPLATS integration
	uint8_t            SamplingDirection;  // E3dSAMPLE_LIGHTDIRS or E3dSAMPLE_VIEWDIRS
	uint8_t            IntegrationMethod;  // E3dGAUSSIAN_SPLATS etc.

	E3dKDTreePointMap           PointMap;       // Sample points are stored in a KD-tree, so we can do fast nearest neighbour queries for density estimation (to integrate importance-sampled directions)
	E3dNearestKDTreePointList*  NearestPoints;  // For collecting nearest neighbours
} E3dInfo;


#define ROffset(field)  (void*)(EPtrOffset(E3dInfo, field))
static EResource  _Resources[]=
{
	{ .Name= "Black radius",      .Type= E3dCOORDINATE_TYPE,  .Address= ROffset(BlackRadius) },
	{ .Name= "Scale",             .Type= E3dCOORDINATE_TYPE,  .Address= ROffset(Scale) },

	{ .Name= "Material",          .Type= EResOBJECT,          .Address= ROffset(Material) },

	{ .Name= "ShadeSurface",      .Type= EResOBJECT,          .Address= ROffset(ShadeSurface.Mesh) },
	{ .Name= "SampleDirection",   .Type= EResOBJECT,          .Address= ROffset(SampleDirection.Mesh) },
	{ .Name= "SampleDirections",  .Type= EResOBJECT,          .Address= ROffset(SampleDirections.Mesh) },

	{ .Name= "SamplesSqrt",       .Type= EResUINT32,          .Address= ROffset(SamplesSqrt) },
	{ .Name= "SamplingDirection", .Type= EResUINT8,           .Address= ROffset(SamplingDirection) },
	{ .Name= "IntegrationMethod", .Type= EResUINT8,           .Address= ROffset(IntegrationMethod) },
	{ .Name= "SplatRadius",       .Type= EResFLOAT32,         .Address= ROffset(SplatRadius) },
};
#undef ROffset


typedef struct
{
	XeWidget  SampleViewDirsW, SampleLightDirsW,  BlackRadiusW, ScaleW, SamplesSqrtW, SplatRadiusW, OrientSphereW;
} E3dResourcesGUI;


static E3dNodeComponentClass*  _NodeComponentClass = NULL;


static void _CheckMesh(E3dInfo* PInfo, E3dVisGeo* PVisGeo)
{
	const EIndex  LDimensions = PInfo->Dimensions;

	if(PVisGeo->GrabUnitSphere)
	{
		E3dMesh*  LMesh = PVisGeo->Mesh;

		{
			const E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;const EIndex  LN = LMesh->Vertices.Count;
			EResizeNT(E3dCoordinate, LN * LDimensions,  PVisGeo->UnitSpherePoints);PVisGeo->NumOfUnitSpherePoints = LN;
			EResizeNT(E3dCoordinate, LN * LDimensions,  PVisGeo->RotatedUnitSpherePoints);

			{
				EIndex	LI = 0;E3dCoordinate*  LUnitSpherePoint = PVisGeo->UnitSpherePoints;for(;LI < LN;LI++, EStruct_Inc(LVertex, LVertexSize), LUnitSpherePoint += LDimensions)
				{
					LUnitSpherePoint[0] = LVertex->Position_3[0];
					LUnitSpherePoint[1] = LVertex->Position_3[1];
					LUnitSpherePoint[2] = LVertex->Position_3[2];

					E3d_Normalize3_a(LUnitSpherePoint);
				}
			}
		}

		PVisGeo->GrabUnitSphere = FALSE;
	}

	if(PInfo->OrientSphere)
	{
		E3dMatrix  LMatrix;E3dMatrix_LoadIdentity(LMatrix);

// Orient the sphere, so its "north pole" is along the mirror reflection vector
//
		{
			E3dWindow*  L3DWindow = E3dp_Main3DWindow;E3dpWindow*  L3DpWindow = E_DynamicCast(L3DWindow, E3dpWindow);if(L3DpWindow)
			{
				E3dRenderer*  LRenderer = L3DpWindow->RendererSet->Renderer;
				if(LRenderer->VisibleLights.Items)
				{
					const E3dCoord3  LPosition = E3dCoord3_s(0.0, 0.0, 0.0);
					E3dCoord3        LNormal = E3dCoord3_s(0.0, 1.0, 0.0);
					const E3dCoord3  L1 = E3dCoord3_s(0.0, 1.0, 0.0);

					E3dRLightInstance*  LLight = LRenderer->VisibleLights.Items;

					{
						E3dCoord3  E_Sub3i_vv(LMirrorReflectionDir,  LPosition, LLight->Position);E3dCoordinate  LDist;E3d_NormalizeGetLength3_v(LMirrorReflectionDir, LDist);

						E3d_Reflect(&LMirrorReflectionDir, LNormal);

//printf("%s() %s:%d  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LMirrorReflectionDir));fflush(stdout);

						E3d_VectorsGetRotMatrix(&L1, &LMirrorReflectionDir, LMatrix);
					}
				}
			}
		}

		if(PVisGeo->RotatedUnitSpherePoints)
		{
			EIndex	LI = 0;const EIndex  LN = PVisGeo->NumOfUnitSpherePoints;const E3dCoordinate*  LUnitSpherePoint = PVisGeo->UnitSpherePoints;E3dCoordinate*  LRotatedUnitSpherePoint = PVisGeo->RotatedUnitSpherePoints;for(;LI < LN;LI++, LUnitSpherePoint += LDimensions, LRotatedUnitSpherePoint += LDimensions)
			{
				LRotatedUnitSpherePoint[0] = LUnitSpherePoint[0];
				LRotatedUnitSpherePoint[1] = LUnitSpherePoint[1];
				LRotatedUnitSpherePoint[2] = LUnitSpherePoint[2];

				EMatrix_Transform3x3_a(LMatrix, LRotatedUnitSpherePoint);
				E3d_Normalize3_a(LRotatedUnitSpherePoint);
			}
		}
	}
}


//================================================
// Guassian filter with precomputed constants
//================================================
static inline E3dCoordinate _GaussianFilterPrep(const E3dCoordinate PDistanceSq, const E3dCoordinate POneOverTwoRadiusSq)
{
	const E3dCoordinate  LAlpha = 0.918, LBeta = 1.953, LK = PDistanceSq * POneOverTwoRadiusSq,
	                     LDenom = 0.8581521110346772563,  //  = 1.0 - exp(-LBeta)
	                     LNumerator = 1.0 - exp(-LBeta * LK);

//	return(1.0 - LAlpha * (1.0 - LNumerator / LDenom));  // Inverted gaussian : it distributes better, with less "sparkling" around corners
	return(LAlpha * (1.0 - LNumerator / LDenom));
}


static E3dModel* _SceneGetFirstVisibleLightModel(E3dScene* PScene)
{
	EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = PScene->RootNodes.Items[LRI];for(;LNode;LNode = LNode->Next)
		{
			if(LNode->Visible)
			{
				E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
				{
					EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LModel->Objects.Items[LObjI];if(LObject->Visible && EObject_IsSubClass(LObject, E3dLight)) return(LModel);
					}
				}
			}
		}
	}
	return(NULL);
}


static EChangeFlags _Update(E3dNode* PChangedBranchRootNode, E3dNode* PNode, E3dNodeComponent* PInfo, const EChangeFlags PChanged, const EBool PCheckOnly)
{
	if(PCheckOnly) return(0);

	E3dInfo*      LInfo = (E3dInfo*)PInfo;
	E3dModel*     LModel = (E3dModel*)PNode;
	E3dMesh*      LShadeSurfaceMesh     = NULL;
	E3dMesh*      LSampleDirectionMesh  = NULL;
	E3dMesh*      LSampleDirectionsMesh = NULL;
	EChangeFlags  LTriggeredChanges = 0;

	const EBool   LDebug = FALSE;

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

	if(PChanged == 0)
	{
// Just check consistency
//
		if(LModel->Objects.Count == 1)
		{
			LShadeSurfaceMesh = LInfo->ShadeSurface.Mesh;
			LSampleDirectionMesh = LInfo->SampleDirection.Mesh;
			LSampleDirectionsMesh = LInfo->SampleDirections.Mesh;

			LTriggeredChanges |= E3dChgMeshSHAPE;

			E3dObjectComponentClass*	LClass = E3dObjectComponent_ClassByName("Sphere");if(LClass)
			{
				{
					E3dObjectComponent*  LSphereInfo = E3dObjectComponent_New(LClass);if(LSphereInfo)
					{
						EObject_NamedResourceSet1f(LInfo, "Radius", 1.0);
						LSphereInfo->AttributeChangeCount++;E3dObject_CustomUpdate((E3dObject*)LShadeSurfaceMesh, LSphereInfo,  0,  &LTriggeredChanges);
					}
					LInfo->ShadeSurface.GrabUnitSphere = TRUE;
				}

				{
					E3dObjectComponent*  LSphereInfo = E3dObjectComponent_New(LClass);if(LSphereInfo)
					{
						EObject_NamedResourceSet1f(LInfo, "Radius", 1.0);
						LSphereInfo->AttributeChangeCount++;E3dObject_CustomUpdate((E3dObject*)LSampleDirectionMesh, LSphereInfo,  0,  &LTriggeredChanges);
					}
					LInfo->SampleDirection.GrabUnitSphere = TRUE;
				}

				{
					E3dObjectComponent*  LSphereInfo = E3dObjectComponent_New(LClass);if(LSphereInfo)
					{
						EObject_NamedResourceSet1f(LInfo, "Radius", 1.0);
						LSphereInfo->AttributeChangeCount++;E3dObject_CustomUpdate((E3dObject*)LSampleDirectionsMesh, LSphereInfo,  0,  &LTriggeredChanges);
					}
					LInfo->SampleDirections.GrabUnitSphere = TRUE;
				}
			}
		}

		return(0);
	}
	else
	{
		LShadeSurfaceMesh = LInfo->ShadeSurface.Mesh;
		LSampleDirectionMesh = LInfo->SampleDirection.Mesh;
		LSampleDirectionsMesh = LInfo->SampleDirections.Mesh;
	}

	if(LShadeSurfaceMesh && LShadeSurfaceMesh->Vertices.Count != LInfo->ShadeSurface.NumOfUnitSpherePoints) LInfo->ShadeSurface.GrabUnitSphere = TRUE;
	if(LSampleDirectionMesh && LSampleDirectionMesh->Vertices.Count != LInfo->SampleDirection.NumOfUnitSpherePoints) LInfo->SampleDirection.GrabUnitSphere = TRUE;
	if(LSampleDirectionsMesh && LSampleDirectionsMesh->Vertices.Count != LInfo->SampleDirections.NumOfUnitSpherePoints) LInfo->SampleDirections.GrabUnitSphere = TRUE;

	if(PChanged & (E3dChgLIGHT|E3dChgATTRIBUTES) && LShadeSurfaceMesh && LSampleDirectionMesh && LSampleDirectionsMesh)
	{
		_CheckMesh(LInfo, &LInfo->ShadeSurface);
		_CheckMesh(LInfo, &LInfo->SampleDirection);
		_CheckMesh(LInfo, &LInfo->SampleDirections);

		{
			E3dWindow*    L3DWindow = E3dp_Main3DWindow;E3dpWindow*  L3DpWindow = E_DynamicCast(L3DWindow, E3dpWindow);if(L3DpWindow)
			{
				E3dRenderer*  LRenderer = L3DpWindow->RendererSet->Renderer;

				E3dSurfacePoint  LPoint;E3dSurfacePoint_Init(&LPoint);

				if(LRenderer->VisibleLights.Count > 0)
				{
					E3dScene*  LScene = E3d_Scene;
					E3dModel*  LSceneGetFirstVisibleLightModel = _SceneGetFirstVisibleLightModel(LScene);
					E3dCoord3  LLightPosition;LLightPosition = E3dCoord3_s(LSceneGetFirstVisibleLightModel->LocalToWorldMatrix[M30], LSceneGetFirstVisibleLightModel->LocalToWorldMatrix[M31], LSceneGetFirstVisibleLightModel->LocalToWorldMatrix[M32]);

					E3dMaterial*  LSurfaceMaterial = LInfo->Material;if(LSurfaceMaterial)
					{
						E3dShader*  LShader = LSurfaceMaterial->Shader;
						int         LNumOfSpectralSamples = LRenderer->NumOfSpectralSamples;
						E3dCoord3   E_Sub3i_vv(LPointToLight,  LLightPosition, LPoint.Position);E3dCoordinate  LLightDistance;E3d_NormalizeGetLength3_v(LPointToLight, LLightDistance);

						const E3dShaderClass*  LShaderClass = LShader->Class;

//----------------------------------------------------------------
// Visualize the Shader's ShadeSurface() function
//----------------------------------------------------------------
						if(LShader->Class && LShader->Class->ShadeSurface)
						{
							E3dShaderSurfaceFunc  LShadeSurface = LShader->Class->ShadeSurface;
							E3dVisGeo*            LVisGeo = &LInfo->ShadeSurface;
							E3dMesh*              LMesh = LVisGeo->Mesh;
							if(LMesh->Vertices.Count == LVisGeo->NumOfUnitSpherePoints)
							{
								E3dVertex*    LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;const EIndex  LDimensions = LInfo->Dimensions;
								ERandomState  LRandomState[5];E_RandomInit(42, LRandomState);

								E3dShaderLight  LShaderLight;
								E3dCoordinate   LNdotL = 1.0;

								if(LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS)
								{
if(LDebug) { printf("%s() %s:%d  %sShadeSurface()%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout); }
									LNdotL = E_Dot3_vv(LPoint.Normal, LPointToLight);
									LShaderLight.PointToLightXYZ = LPointToLight;
									LShaderLight.PointToLightNew.W = LLightDistance;

									LShaderLight.NdotL = LNdotL;
									LShaderLight.SolidAngle = 0.0;

									LShaderLight.Shadowed = FALSE;
									LShaderLight.ImportanceSampled = FALSE;
								}

								ESetN(LShaderLight.Power, 1.0, LNumOfSpectralSamples);
								LShaderLight.Mult = 1.0;  // We are visualizing the BRDF. Mult is 1.0, so a Lambert shader will produce a hemisphere shape.

if(LDebug) { printf("%s() %s:%d  %sShadeSurface()%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout); }
								EIndex  LI = 0;const EIndex  LN = LMesh->Vertices.Count;const E3dCoordinate*  LUnitSpherePoint = LVisGeo->RotatedUnitSpherePoints;for(;LI < LN;LI++, LUnitSpherePoint += LDimensions, EStruct_Inc(LVertex, LVertexSize))
								{
/*
									{
										E3dCamera*       LCamera = LRenderer->Camera;
										const E3dCoord3  LCameraPosition = E3dCoord3_s(LCameraModel->LocalToWorldMatrix[M30], LCameraModel->LocalToWorldMatrix[M31], LCameraModel->LocalToWorldMatrix[M32]);
										E3dCoord3        E_Sub3i_vv(LPointToViewer,  LCameraPosition, LPoint.Position);E3dCoordinate  LDist;E3d_NormalizeGetLength3_v(LPointToViewer, LDist);
										LPoint.PointToViewerXYZ = LPointToViewer;
										LPoint.PointToViewer.W = LDist;
									}
*/

									if(LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS)
									{
if(LDebug) { printf("%s() %s:%d  %sShadeSurface()%s %lld  Sample ViewDirs\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI);fflush(stdout); }
										LPoint.PointToViewer = E3dCoord4_s(LUnitSpherePoint[0], LUnitSpherePoint[1], LUnitSpherePoint[2], 1.0);
									}
									else
									{
if(LDebug) { printf("%s() %s:%d    %sShadeSurface()%s %4lld  Saample Lights\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI);fflush(stdout); }
										LPoint.PointToViewer = E3dCoord4_s(LPointToLight.X, LPointToLight.Y, LPointToLight.Z, LLightDistance);

										LShaderLight.PointToLightXYZ = E3dCoord3_s(LUnitSpherePoint[0], LUnitSpherePoint[1], LUnitSpherePoint[2]);
										LNdotL = E_Dot3_vv(LPoint.Normal, LShaderLight.PointToLightXYZ);

//{ printf("%s() %s:%d    %sShadeSurface()%s LUnitSpherePoint[1] %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LUnitSpherePoint[1]);fflush(stdout); }

										LShaderLight.NdotL = LNdotL;LShaderLight.Mult = 1.0;  // We are visualizing the BRDF. Mult is 1.0, so a Lambert shader will produce a hemisphere shape.
										LShaderLight.SolidAngle = 0.0;
										LShaderLight.PointToLightNew.W = LLightDistance;

										LShaderLight.Shadowed = FALSE;
										LShaderLight.ImportanceSampled = FALSE;
									}

									{
										E3dShaderResult  LShaderResult;E3dShaderResult_Init(&LShaderResult, LSurfaceMaterial,  0, LNumOfSpectralSamples,  NULL, NULL, LRandomState,  NULL, TRUE, FALSE);

										LShaderResult.ApplyNdotL = FALSE;

										LShadeSurface(NULL,  &LPoint, LSurfaceMaterial, LSurfaceMaterial->Shader, NULL, LShader,  &LShaderLight, 1,  &LShaderResult);

										float  LV = LShaderResult.Color[0];
										if(LV < 0.0) LV = 0.0;
										const E3dCoordinate  LR = LInfo->BlackRadius + LV * LInfo->Scale;

//if(LNdotL < 0.0) { printf("%s() %s:%d    %sShadeSurface()%s %4lld  LShaderResult.Color[0] % 7.3f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI,  LShaderResult.Color[0]);fflush(stdout); }

										LVertex->Position = E3dCoord3_s(LUnitSpherePoint[0] * LR, LUnitSpherePoint[1] * LR, LUnitSpherePoint[2] * LR);
//										LVertex->Position = E3dCoord3_s(LUnitSpherePoint[0], LUnitSpherePoint[1], LUnitSpherePoint[2]);
//if(LDebug) { printf("%s() %s:%d    %sShadeSurface()%s %4lld  LR %f  % 7.3f,% 7.3f,% 7.3f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI,  LR,  XYZ_s(LVertex->Position));fflush(stdout); }
									}
								}
							}
						}

						{
//const ETime  LSDStart_ms = E_GetMilliseconds();printf("%s() %s:%d  SampleDirections START\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);

//----------------------------------------------------------------
// Visualize the Shader's SampleDirections() function
//----------------------------------------------------------------
							E3dVisGeo*  LVisGeo = &LInfo->SampleDirections;
							E3dMesh*    LMesh   = LVisGeo->Mesh;

//E3dMesh_PolyGroupsFree(LMesh);  // We use point visualization for now...

							E3dMesh_RenderDataFree(LMesh);

							{
								ERandomState       LRandomState[5];E_RandomInit(42, LRandomState);
								const EIndex       LN = LInfo->SamplesSqrt, LNN = LN * LN;
								const EIndex       LNSamplesMult = 2;  // Allocate this many times more samples, because some shaders have multiple lobes that they need to sample independently (e.g. diffuse + specular)

								E3dShaderLight*    LSamples;

								if(LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS)
								{
									ENewN(LNN * LNSamplesMult, LSamples);
								}
								else
								{
									ENewN(LNN * LNSamplesMult, LSamples);
								}

//{ const ETime  LNow_ms = E_GetMilliseconds();printf("%s() %s:%d  SampleDirections 1 %lld ms\n", __FUNCTION__, __FILE__, __LINE__, (long long)(LNow_ms - LSDStart_ms));fflush(stdout); }

//printf("%s() %s:%d  sizeof(E3dShaderLight) %d\n", __FUNCTION__, __FILE__, __LINE__, sizeof(E3dShaderLight));fflush(stdout);

// SampleDirections() generates point-to-light vectors, based on the surface normal etc., and the view direction...
// BRDFs are reciprocal, so we can just plug in the light direction here.
								LPoint.PointToViewerXYZ = LPointToLight;

								if(LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS)
								{
	printf("%s() %s:%d  %sE3dSAMPLE_VIEWDIRS%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
// Fixed light direction, sample viewing directions
//
									if(LMesh->Vertices.Count != LNN)
									{
										E3dMesh_VerticesFree(LMesh);
										E3dMesh_VerticesAdd(LMesh, LNN);
									}

									const E3dVertexSize  LVertexSize = LMesh->VertexSize;

printf("%s() %s:%d  %sGREEN%s  LN %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LN);fflush(stdout);

									EIndex  LI = 0;E3dShaderLight*  LSample = LSamples;E3dVertex*  LVertex = LMesh->Vertices.Items;for(;LI < LNN;LI++)
									{
										const EIndex  LCount = LShaderClass->SampleDirections(&LPoint, LSurfaceMaterial, LSurfaceMaterial->Shader, NULL,  LN, 0.0, LRandomState,  0, LNumOfSpectralSamples,  LSample);if(LCount > 0)
										{
//										const float  LNdotL = E_Dot3_vv(LSample->PointToLight, LPoint.Normal);

											const E3dCoordinate  LW = LSample->Weight * LSample->Power[0];
//										const E3dCoordinate  LW = LSample->Power[0];
//										const E3dCoordinate  LW = 1.0;
											LVertex->Position = E3dCoord3_s(LSample->PointToLightNew.X * LW, LSample->PointToLightNew.Y * LW, LSample->PointToLightNew.Z * LW);
											LVertex->Flags = E3dVtxDEFAULT;
											EStruct_Inc(LVertex, LVertexSize);
										}
									}
									LMesh->Vertices.Count = EStruct_GetIndex(LMesh->Vertices.Items, LMesh->VertexSize, LVertex);
								}
								else
								{
// Fixed viewing direction, sample incoming light directions
//
									LPoint.PointToViewerXYZ = LPointToLight;
									LPoint.PointToViewer.W = LLightDistance;

									const EIndex  LCount = LShaderClass->SampleDirections(&LPoint, LSurfaceMaterial, LSurfaceMaterial->Shader, NULL,  LN, 0.0, LRandomState,  0, LNumOfSpectralSamples,  LSamples);

printf("%s() %s:%d  %sSample light dirs%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
if(LCount == 0) { printf("%s() %s:%d  %sLCount = 0%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout); }

//printf("%s() %s:%d  %lld  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LN, (long long)LCount);fflush(stdout);

// To integrate the importance-sampled BRDF response, we build a KD-tree and do density estimation.
// We do the density estimation via. gaussian splat monte carlo integration (similar to photon mapping).
//
									if(LInfo->IntegrationMethod == E3dGAUSSIAN_SPLATS)
									{
										E3dKDTreePointMap*  LPointMap = &LInfo->PointMap;

printf("%s() %s:%d    %sGaussian splats%s  %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LPointMap->Points_Count);fflush(stdout);
										if(PChanged & (E3dChgMaterialTYPE|E3dChgMaterialPARAM|E3dChgLIGHT|E3dChgCAMERA)) LPointMap->Points_Count = 0;

										if(LPointMap->Points_Count == 0)
										{
if(LDebug) { printf("%s() %s:%d  %sRealloc%s  %p %p\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal,  LPointMap, LPointMap->TmpPoints);fflush(stdout); }
//										if(LCount > LPointMap->Points_CountAllocated) 
											LPointMap->TmpPoints = (E3dKDTreePoint*)ERealloc(LPointMap->TmpPoints, LPointMap->BytesPerPoint * LCount);LPointMap->Points_CountAllocated = LCount;
											LPointMap->Balanced = FALSE;

printf("%s() %s:%d    %sRealloc%s  PointMap %p  TmpPoints %p  Points %p\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal,  LPointMap, LPointMap->TmpPoints, LPointMap->Points);fflush(stdout);
											LPointMap->Points_Count = LCount;

											{
												EIndex  LI = 0;const E3dShaderLight*  LSample = LSamples;E3dBRDFSamplePoint*  LSamplePoint = (E3dBRDFSamplePoint*)LPointMap->TmpPoints;for(;LI < LCount;LI++, LSample++, LSamplePoint++)
												{
													LSamplePoint->Position = LSample->PointToLightXYZ;

													const float  LMult = E3dLIGHTS_MULTIPLIED_BY_NDOTL ? LSample->NdotL : 1.0;
													LSamplePoint->Intensity = LSample->Weight * LSample->Power[0] * LMult;

//												LSamplePoint->Intensity = LSample->Power[0];
//												LSamplePoint->Intensity = 1.0;
													LSamplePoint->Plane = 0;
												}

//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
												{
													const EBool  LShowProgress = LN > 256;

													if(LShowProgress)
													{
														E3dProgressIndicator_SetMessage("Building KD-tree for BRDF sample points");
														E3dProgressIndicator_Set(0.5, NULL, NULL);
														XeApp_Sync(Xe_App);XeApp_Sync(Xe_App);
													}
													E3dKDTreePointMap_BuildTree(LPointMap,  LShowProgress ? &E3dp_ProgressIndicator : NULL, NULL, NULL);

													if(LShowProgress)
													{
														E3dProgressIndicator_Set(-1.0, NULL, NULL);
														XeApp_Sync(Xe_App);
													}
												}
											}
										}


// Now, sample the BRDF at the given points (Sphere Mesh Vertices)
//
										{
											if(LInfo->NearestPoints) { E3dNearestKDTreePoints_Free(LInfo->NearestPoints);LInfo->NearestPoints = NULL; }
											LInfo->NearestPoints = E3dNearestKDTreePoints_New(E3dMAX_NEAREST_POINTS);

											E3dNearestKDTreePointList*  LNearestPoints = LInfo->NearestPoints;
											const float                 LSplatRadius = LInfo->SplatRadius;
											const float                 LSplatRadiusSq = LSplatRadius * LSplatRadius;
											float*                      LDistanceSq = LNearestPoints->DistanceSq;(void)LDistanceSq;
											E3dBRDFSamplePoint**        LPoints = (E3dBRDFSamplePoint**)LNearestPoints->Points;
											const ETime                 LStart_ms = E_GetMilliseconds();
											ETime                       LLastProgressUpdate_ms = 0;
											EBool                       LShowProgress = FALSE;
											const double                LNormalizer = 5.0 * E3dPI * E3dPI / (double)LNN;

											if(LMesh->Vertices.Count == LVisGeo->NumOfUnitSpherePoints)
											{
												const E3dCoordinate*  LUnitSpherePoint = LVisGeo->RotatedUnitSpherePoints;
												E3dVertex*            LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;const EIndex  LDimensions = LInfo->Dimensions;

// See SphereSurfaceSamplingWithGaussianSplat00.odp
//
												const E3dCoordinate  LIntersectedArcX = (-LSplatRadiusSq + 2.0) / 2.0;                                 // X of Pi
												const E3dCoordinate  LIntersectedArcLength = E3d_ACos(LIntersectedArcX);                               // Length of arc on the unit circle that is enclosed in the splat
//printf("%s() %s:%d  %f %f\n", __FUNCTION__, __FILE__, __LINE__, LIntersectedArcX, LIntersectedArcLength);fflush(stdout);
												const E3dCoordinate  LIntersectedArcLengthSq = LIntersectedArcLength * LIntersectedArcLength;
												const E3dCoordinate  LIntersectedArea = 2.0 * E3dPI * LIntersectedArcLengthSq;   // Area on the surface of the unit circle that is enclosed in the splat
//											const E3dCoordinate  LOneOverTwoRadiusSq = 1.0 / (2.0 * LSplatRadiusSq);
												const E3dCoordinate  LOneOverTwoRadiusSq = 1.0 / (2.0 * LIntersectedArcLengthSq);

//printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LMesh->Vertices.Count);fflush(stdout);

												EIndex  LI = 0;const EIndex  LVN = LMesh->Vertices.Count;const double  LVNF = (double)LVN;for(;LI < LVN;LI++, LUnitSpherePoint += LDimensions, EStruct_Inc(LVertex, LVertexSize))
												{
//printf("%s() %s:%d  %lld of %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LI, (long long)LMesh->Vertices.Count);fflush(stdout);
													LNearestPoints->QueryPosition[0] = LUnitSpherePoint[0];
													LNearestPoints->QueryPosition[1] = LUnitSpherePoint[1];
													LNearestPoints->QueryPosition[2] = LUnitSpherePoint[2];
													LNearestPoints->Points_Count = 0;
													LNearestPoints->HaveHeap = 0;

													E3dNearestPoints_Jensen_iterative(LPointMap, LNearestPoints, LSplatRadiusSq);const EIndex  LPN = LNearestPoints->Points_Count;if(LPN > 0)
													{
//if(LPN < 10) { printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPN);fflush(stdout); }
														EIndex  LPI = 1;float  LNWTotal = 0.0;for(;LPI <= LPN;LPI++)
														{
															const E3dBRDFSamplePoint*  LNearestPoint = LPoints[LPI];

															{
//															const E3dCoordinate  LNW = _GaussianFilterPrep(LDistanceSq[LPI], LOneOverTwoRadiusSq);
																const float  LGreatCircleDistance = E3d_ArcLength((E3dCoord3*)LUnitSpherePoint, &LNearestPoint->Position);const E3dCoordinate  LNW = _GaussianFilterPrep(LGreatCircleDistance * LGreatCircleDistance, LOneOverTwoRadiusSq);

																LNWTotal += LNearestPoint->Intensity * LNW;
															}
														}

//													const float  LWW = LNWTotal * LNormalizer / LSplatRadiusSq;
														const float  LWW = LNWTotal * LNormalizer / LIntersectedArea;
														LVertex->Position = E3dCoord3_s(LUnitSpherePoint[0] * LWW, LUnitSpherePoint[1] * LWW, LUnitSpherePoint[2] * LWW);
														LVertex->Flags = E3dVtxDEFAULT;
													}
													else
													{
														LVertex->Position = E3dCoord3_s(0.0, 0.0, 0.0);
														LVertex->Flags = E3dVtxDEFAULT;
													}

													if((LI & 0xFF) == 0)
													{
														const ETime  LNow_ms = E_GetMilliseconds();
														if(!LShowProgress)
														{
															const ETime  LElapsed_ms = LNow_ms - LStart_ms;if(LElapsed_ms > 500)
															{
																E3dProgressIndicator_SetMessage("Integrating samples");
																XeApp_Sync(Xe_App);
																LShowProgress = TRUE;
															}
														}

														if(LShowProgress && (LNow_ms - LLastProgressUpdate_ms) >= 100)
														{
															E3dProgressIndicator_Set((double)LI / LVNF, NULL, NULL);XeApp_Sync(Xe_App);
															LLastProgressUpdate_ms = LNow_ms;
														}
													}
												}
											}



/*
											{
												const double  LCountF = (double)LCount;
												EIndex  LI = 0;const E3dShaderLight*  LSample = LSamples;E3dVertex*  LVertex = LMesh->Vertices;for(;LI < LCount;LI++, LSample++, EStruct_Inc(LVertex, LVertexSize))
												{
													const E3dCoordinate  LW = LSample->PointToLight.W * LSample->Power[0] / 4.0;

													LNearestPoints->QueryPosition[0] = LSample->PointToLightXYZ.X;
													LNearestPoints->QueryPosition[1] = LSample->PointToLightXYZ.Y;
													LNearestPoints->QueryPosition[2] = LSample->PointToLightXYZ.Z;
													LNearestPoints->Points_Count = 0;
													LNearestPoints->HaveHeap = 0;

													float  LNWTotal = 0.0;

													E3dNearestPoints_Jensen_iterative(LPointMap, LNearestPoints, LSplatRadiusSq);if(LNearestPoints->Points_Count)
													{
//assert(LNearestPoints->Points_Count <= E3dMAX_NEAREST_POINTS);
// See SphereSurfaceSamplingWithGaussianSplat00.odp
//
														const E3dCoordinate  LIntersectedArcX = (-LSplatRadiusSq + 2.0) / 2.0;                                 // X of Pi
														const E3dCoordinate  LIntersectedArcLength = E3d_ASin(LIntersectedArcX);                               // Length of arc on the unit circle that is enclosed in the splat
														const E3dCoordinate  LIntersectedArea = 2.0 * E3dPI * LIntersectedArcLength * LIntersectedArcLength;   // Area on the surface of the unit circle that is enclosed in the splat
														const E3dCoordinate  LInvA = 10000.0 / LIntersectedArea,
																								 LOneOverTwoRadiusSq = 1.0 / (2.0 * LSplatRadiusSq);

														EIndex  LPI = 1;const EIndex  LPN = LNearestPoints->Points_Count;for(;LPI <= LPN;LPI++)
														{
															const E3dBRDFSamplePoint*  LNearestPoint = LPoints[LPI];

															{
																const E3dCoordinate  LNW = _GaussianFilterPrep(LDistanceSq[LPI], LOneOverTwoRadiusSq) * LInvA;
																LNWTotal += LNearestPoint->Intensity * LNW;
															}
														}
													}

													const float  LWW = LW * LNWTotal * LNormalizer;
													LVertex->Position = E3dCoord3_s(LSample->PointToLight.X * LWW, LSample->PointToLight.Y * LWW, LSample->PointToLight.Z * LWW);
													LVertex->Flags = E3dVtxDEFAULT;

													if((LI & 0xFF) == 0)
													{
														const ETime  LNow_ms = E_GetMilliseconds();
														if(!LShowProgress)
														{
															const ETime  LElapsed_ms = LNow_ms - LStart_ms;if(LElapsed_ms > 500) { E3dProgressIndicator_SetMessage("Integrating samples");XeApp_Sync(Xe_App);LShowProgress = TRUE; }
														}

														if(LShowProgress && (LNow_ms - LLastProgressUpdate_ms) >= 100) { E3dProgressIndicator_Set((double)LI / LCountF, NULL, NULL);XeApp_Sync(Xe_App);LLastProgressUpdate_ms = LNow_ms; }
													}
												}
											}
*/


											if(LShowProgress)
											{
												E3dProgressIndicator_Set(-1.0, NULL, NULL);
												XeApp_Sync(Xe_App);
											}
										}
									}
									else
									{
										EIndex  LI = 0;const E3dShaderLight*  LSample = LSamples;E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;for(;LI < LCount;LI++, LSample++, EStruct_Inc(LVertex, LVertexSize))
										{
											const float  LNdotL = E_Dot3_vv(LSample->PointToLightNew, LPoint.Normal);

//									const E3dCoordinate  LW = LSample->PointToLight.W * LSample->Power[0] / 4.0;
											const E3dCoordinate  LW = LNdotL * LSample->Power[0];
//									const E3dCoordinate  LW = 1.0;
											LVertex->Position = E3dCoord3_s(LSample->PointToLightNew.X * LW, LSample->PointToLightNew.Y * LW, LSample->PointToLightNew.Z * LW);
											LVertex->Flags = E3dVtxDEFAULT;
										}
									}
								}

								EFree(LSamples);
							}
						}
					}
				}
			}
		}

		E3dNode_UpdateForDisplay(PNode, E3dUpdateALL);
	}

	return(LTriggeredChanges);
}


static void _VisGeoInit(E3dVisGeo* PVisGeo)
{
	PVisGeo->Mesh           = NULL;
	PVisGeo->GrabUnitSphere = FALSE;
	PVisGeo->UnitSpherePoints = NULL;PVisGeo->NumOfUnitSpherePoints = 0;
	PVisGeo->RotatedUnitSpherePoints = NULL;
}


static void _VisGeoFree(E3dVisGeo* PVisGeo)
{
	E_DeleteChk(PVisGeo->Mesh);
	EFreeChkClr(PVisGeo->UnitSpherePoints);PVisGeo->NumOfUnitSpherePoints = 0;
	EFreeChkClr(PVisGeo->RotatedUnitSpherePoints);
	PVisGeo->GrabUnitSphere = TRUE;
}

#define E3dPOINT_ALLOC_INCREMENT  16

//========================================
// Constructor
//========================================
static int _Init(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	LThis->BlackRadius = 0.0;
	LThis->Scale = 1.0;

	LThis->SamplingDirection = E3dSAMPLE_LIGHTDIRS;
//	LThis->IntegrationMethod = 0;
	LThis->IntegrationMethod = E3dGAUSSIAN_SPLATS;
	LThis->SamplesSqrt       = 256;

	LThis->SplatRadius = 0.05;

	LThis->Material = NULL;

	_VisGeoInit(&LThis->ShadeSurface);
	_VisGeoInit(&LThis->SampleDirection);
	_VisGeoInit(&LThis->SampleDirections);

	LThis->OrientSphere = TRUE;

	LThis->Dimensions = 3;

	E3dKDTreePointMap_Init(&LThis->PointMap, sizeof(E3dBRDFSamplePoint));
	LThis->PointMap.Points_Count = 0;  // E3dKDTreePointMap_Init() sets this to 1 because of an issue with the KD-tree code.

	LThis->NearestPoints = NULL;

	return(E_SUCCESS);
}


//========================================
// Destructor
//========================================
static int _Free(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	E_DeleteChk(LThis->Material);

	_VisGeoFree(&LThis->ShadeSurface);
	_VisGeoFree(&LThis->SampleDirection);
	_VisGeoFree(&LThis->SampleDirections);

//printf("%s() %s:%d  %sFreeing%s  PointMap %p  TmpPoints %p  Points %p\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal,  &LThis->PointMap, LThis->PointMap.TmpPoints, LThis->PointMap.Points);fflush(stdout);
	E3dKDTreePointMap_FreeContents(&LThis->PointMap);
	if(LThis->NearestPoints) { E3dNearestKDTreePoints_Free(LThis->NearestPoints);LThis->NearestPoints = NULL; }

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static int _Copy(const E3dNode* PSrcNode, const E3dNodeComponent* PSrcComponent,  E3dNode* PDstNode, E3dNodeComponent* PDstComponent)
{
	E3dInfo*  LSInfo = (E3dInfo*)PSrcComponent;
	E3dInfo*  LDInfo = (E3dInfo*)PDstComponent;

	_Init(PDstNode, PDstComponent);

#define _CopyField(mField) LDInfo->mField = LSInfo->mField
	_CopyField(BlackRadius);
	_CopyField(Scale);
	_CopyField(OrientSphere);

	_CopyField(SamplingDirection);
	_CopyField(IntegrationMethod);
	_CopyField(SamplesSqrt);
	_CopyField(SplatRadius);
#undef _CopyField
	if(LSInfo->Material) E_AssignToMember(LSInfo->Material, LDInfo, Material);

	return(E_SUCCESS);
}


static void _DCB_SampleLightDirs(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
	{
		LInfo->SamplingDirection = E3dSAMPLE_LIGHTDIRS;

		_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);

		E3dScene*  LScene = E3d_Scene;
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}
}


static void _DCB_SampleViewDirs(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	if(((XeToggleButtonCallbackStruct*)PCallData)->Set)
	{
		LInfo->SamplingDirection = E3dSAMPLE_VIEWDIRS;

		_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);

		E3dScene*  LScene = E3d_Scene;
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}
}


static void _DCB_BlackRadius(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LInfo->BlackRadius = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


static void _DCB_Scale(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LInfo->Scale = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


static void _DCB_SamplesSqrt(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LInfo->SamplesSqrt = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

	E3dKDTreePointMap*  LPointMap = &LInfo->PointMap;LPointMap->Points_Count = 0;

	_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


static void _DCB_SplatRadius(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LInfo->SplatRadius = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

	_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


//========================================
// Dialog box callback handler
//========================================
static void _DCB_OrientSphere(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*  LPanel = (E3dNodePanel*)PClientData;
	E3dNode*       LNode = LPanel->Node;
	E3dInfo*       LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);

	LInfo->OrientSphere = (EBool)(((XeToggleButtonCallbackStruct*)PCallData)->Set);

	_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


static void _GUI(E3dNode* PNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dInfo*                 LInfo = (E3dInfo*)PInfo;

	switch(PAction)
	{
		case E_UPDATE:
			if(LInfo)
			{
				if(LDrawer->ResourcesGUI)
				{
					E3dResourcesGUI*  LResourcesGUI = (E3dResourcesGUI*)(LDrawer->ResourcesGUI);

					XeScale_SetValue_Coord(LResourcesGUI->BlackRadiusW,      LInfo->BlackRadius,  FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->ScaleW,            LInfo->Scale,        FALSE);
					XeScale_SetValue(LResourcesGUI->SamplesSqrtW,            LInfo->SamplesSqrt,  FALSE);
					XeScale_SetValue_Coord(LResourcesGUI->SplatRadiusW,      LInfo->SplatRadius,  FALSE);
					XeToggleButton_SetState(LResourcesGUI->OrientSphereW,    LInfo->OrientSphere, FALSE);
					XeToggleButton_SetState(LResourcesGUI->SampleViewDirsW,  LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS,  FALSE);
					XeToggleButton_SetState(LResourcesGUI->SampleLightDirsW, LInfo->SamplingDirection == E3dSAMPLE_LIGHTDIRS, FALSE);
				}
				else
				{
					XeWidget          LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
					XeWidget          LIndentWs[7];int	LIndentN;      // For indenting the Scale Widgets
					XeWidget          LTraverseWs[16];int	LTraverseN;  // For TextField traversal
					EArg              Args[256];int  ArgCnt;
					E3dResourcesGUI*  LResourcesGUI;ENew(LResourcesGUI);

					LDrawer->ResourcesGUI = LResourcesGUI;

					LTraverseN = 0;
					LIndentN = 0;

					{
						EXeStart;
						EXeSetArg(XeNMarginTop, 5);
						EXeSetArg(XeNRadioBehavior, TRUE);
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNSpacingX, 6);
						EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						XeWidget_ManagedNew("Sample", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->SamplingDirection == E3dSAMPLE_VIEWDIRS);
						LResourcesGUI->SampleViewDirsW = LW = XeWidget_ManagedNew("View dirs", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_SampleViewDirs, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->SamplingDirection == E3dSAMPLE_LIGHTDIRS);
						LResourcesGUI->SampleLightDirsW = LW = XeWidget_ManagedNew("Light dirs", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_SampleLightDirs, (EPointer)PPanel);
					}


					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 2);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->BlackRadiusW = LW = XeScale_New_Coord(LTopMatrixW, "Black radius",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LInfo->BlackRadius,  0.0, 10.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_BlackRadius, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 2);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->ScaleW = LW = XeScale_New_Coord(LTopMatrixW, "Scale",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LInfo->Scale,  0.0, 100.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Scale, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNMinimum, 16);EXeSetArg(XeNMaximum, 4096);EXeSetArg(XeNValue, LInfo->SamplesSqrt);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->SamplesSqrtW = LW = XeScale_New_i32(LTopMatrixW, "Samples sqrt",  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_SamplesSqrt, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 2);
					EXeSetArg(XeNResizableY, FALSE);
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->SplatRadiusW = LW = XeScale_ManagedNew_f32(LTopMatrixW, "Splat radius",  Args, ArgCnt);
					XeScale_SetValueAndRange_Coord(LW,  LInfo->SplatRadius,  0.005, 0.5,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_SplatRadius, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginTop, 0);EXeSetArg(XeNMarginBottom, 4);
					EXeSetArg(XeNSet, LInfo->OrientSphere);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					LResourcesGUI->OrientSphereW = LW = XeWidget_ManagedNew("Orient", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_OrientSphere, (EPointer)PPanel);

					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);

// Set up TextField traversal
//
					XeTraversal_SetRing(LTraverseWs, LTraverseN);
				}
			}
		break;

		case E_FREE:
		break;
	}
}


//========================================
// Scene changed callback
//========================================
static void _CB_Scene(ECoreObject* PScn, EPointer PClientData, ECallbackStruct* PCallData)
{
	E3dScene*  LScene = (E3dScene*)PScn;

	switch(PCallData->Reason)
	{
		case E3dCR_NODE_TRANSLATE:
			{
				E3dSceneNodesCallbackStruct*  LCBS = (E3dSceneNodesCallbackStruct*)PCallData;
				E3dModel*  LFirstVisibleLightModel = _SceneGetFirstVisibleLightModel(LScene);if(LFirstVisibleLightModel == NULL) return;

				EIndex  LI = 0;const EIndex  LNumOfNodes = LCBS->NumOfNodes;E3dNode**  LNodes = LCBS->Nodes;for(;LI < LNumOfNodes;LI++)
				{
					if(LNodes[LI] == (E3dNode*)LFirstVisibleLightModel)
					{
						EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;for(;LRI < LRN;LRI++)
						{
							E3dNode*  LNode = LScene->RootNodes.Items[LRI];for(;LNode;LNode = LNode->Next)
							{
								E3dInfo*  LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);if(LInfo)
								{
									_Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES|E3dChgLIGHT, FALSE);
									
								}
							}
						}
						break;
					}
				}
			}
		break;

		case E3dCR_MATERIAL_CHANGED:
			{
				E3dSceneMaterialsCallbackStruct*  LCBS = (E3dSceneMaterialsCallbackStruct*)PCallData;
				EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;for(;LRI < LRN;LRI++)
				{
					E3dNode*  LNode = LScene->RootNodes.Items[LRI];for(;LNode;LNode = LNode->Next)
					{
						E3dInfo*  LInfo = (E3dInfo*)E3dNodeComponent_ByClass(LNode, _NodeComponentClass, FALSE);if(LInfo)
						{
							E3dMaterial*  LMaterial = LInfo->Material;if(LMaterial)
							{
								EIndex  LI = 0;const EIndex  LN = LCBS->NumOfMaterials;for(;LI < LN;LI++)
								{
									if(LCBS->Materials[LI] == LMaterial) _Update(NULL, LNode, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES|E3dChgMaterialTYPE, FALSE);
								}
							}
						}
					}
				}
			}
		break;
	}
}


static E3dMesh* _VisGeoNew(E3dVisGeo* PVisGeo, const char* PName, E3dObjectComponent* PSphereInfo, E3dMaterial* PMaterial, E3dModel* PModel)
{
	E3dMesh*  LMesh = (E3dMesh*)E3dObject_CustomNewInit(NULL, PSphereInfo, FALSE);EObject_SetName(LMesh, EStrDup(PName), FALSE);
	E3dModel_ObjectAppend(PModel, (E3dObject*)LMesh, FALSE);
	E_AssignToMember(PMaterial, LMesh->PolyGroups.Items[0],Material);
	return(LMesh);
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	E3dItem       LItem;
	EChangeFlags  LChanged = 0;

	if(E3dScene_GetSingleSelection(E3d_Scene, &LItem, E3dItemMESH|E3dItemFACE|E3dItemPOLYGROUP, 0) != E3dItemNONE)
	{
		E3dMaterial*  LMaterial = NULL;
		E3dObjItem*   LObjItem = &LItem.ObjItem;

		switch(LItem.Item.Type)
		{
			case E3dItemMESH:
				{
//printf("%s() %s:%d Mesh\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					E3dMesh*  LMesh = (E3dMesh*)LObjItem->Object;
					if(LMesh->PolyGroups.Count > 0)
					{
						LMaterial = LMesh->PolyGroups.Items[0]->Material;
					}
				}
			break;

			case E3dItemFACE:
				{
//printf("%s() %s:%d Face\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					E3dFace*  LFace = (E3dFace*)LObjItem->Object;
					LMaterial = LFace->PolyGroup ? LFace->PolyGroup->Material : NULL;
				}
			break;

			case E3dItemPOLYGROUP:
				{
//printf("%s() %s:%d PolyGroup\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					E3dPolyItem*  LPolyItem = &LItem.PolyItem;
					LMaterial = LPolyItem->PolyGroup->Material;
				}
			break;

			case E3dItemMODEL:
				{
//printf("%s() %s:%d Model\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					E3dModel*  LModel = (E3dModel*)LObjItem->Node;
					if(LModel->Objects.Count > 0)
					{
						E3dObject*  LObject = LModel->Objects.Items[0];
						E3dFace*    LFace = E_DynamicCast(LObject, E3dFace);if(LFace)
						{
							LMaterial = LFace->PolyGroup ? LFace->PolyGroup->Material : NULL;
							break;
						}
						E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
						{
							if(LMesh->PolyGroups.Count > 0)
							{
								LMaterial = LMesh->PolyGroups.Items[0]->Material;
							}
							break;
						}
					}
				}
			break;

			default:
				{
					char  s[256];printf("%s() %s:%d  |%s|\n", __FUNCTION__, __FILE__, __LINE__, E3d_ItemFlagsToString(LItem.Item.Type, s));fflush(stdout);
				}
			break;
		}

		if(LMaterial == NULL) goto NoMaterial;

		E3dModel*     LModel = NULL;
		E3dMaterial*  LSMaterial = NULL;

		E3dObjectComponentClass*  LSphereObjectComponentClass = E3dObjectComponent_ClassByName("Sphere");if(LSphereObjectComponentClass)
		{
			E_New(E3dModel, LModel);EObject_SetName(LModel, EStrDup("BRDFVis"), FALSE);

			LSMaterial = E3dMaterial_New(E3dLambertShader_class);if(LSMaterial)
			{
				E3dLambertShader*  LShader = (E3dLambertShader*)(LSMaterial->Shader);

				EObject_SetName(LSMaterial, EStrDup("BRDF"), FALSE);
				EcColor_InitRGB(&LShader->Ambient,  0.1, 0.1, 0.1);
				EcColor_InitRGB(&LShader->Diffuse,  0.8, 0.8, 0.8);
				E3dMaterial_UpdateForDisplay(LSMaterial, NULL, E3dChgMaterialTYPE, E3dTxtUF_ALL);

			}

			if(LModel)
			{
				E3dInfo*  LInfo = (E3dInfo*)E3dNodeComponent_Add((E3dNode*)LModel, _NodeComponentClass);

				E_AssignToMember(LMaterial, LInfo,Material);

				{
					E3dObjectComponent*  LSphereInfo = E3dObjectComponent_New(LSphereObjectComponentClass);if(LSphereInfo)
					{
//printf("%s() %s:%d  %sLSphereInfo%s %p\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LSphereInfo);fflush(stdout);
//						EObject_NamedResourceSet1i(LSphereInfo, "Type",        2);EObject_NamedResourceSet1i(LSphereInfo, "Iterations",  4);    // Geodesic sphere. It has a much more uniform distribution of Vertices
						EObject_NamedResourceSet1f(LSphereInfo, "Radius",    1.0);
						EObject_NamedResourceSet1i(LSphereInfo, "USections", 120);
						EObject_NamedResourceSet1i(LSphereInfo, "VSections",  90);
//						EObject_NamedResourceSet1f(LSphereInfo, "SouthMostLatitude", 0.0);

						{ E3dMesh*  LMesh = _VisGeoNew(&LInfo->ShadeSurface, "ShadeSurface()", LSphereInfo, LSMaterial, LModel);E_AssignToMember(LMesh, LInfo,ShadeSurface.Mesh); }
						{ E3dMesh*  LMesh = _VisGeoNew(&LInfo->SampleDirection, "SampleDirection()", LSphereInfo, LSMaterial, LModel);E_AssignToMember(LMesh, LInfo,SampleDirection.Mesh); LMesh->Visible = FALSE; }
						{ E3dMesh*  LMesh = _VisGeoNew(&LInfo->SampleDirections, "SampleDirections()", LSphereInfo, LSMaterial, LModel);E_AssignToMember(LMesh, LInfo,SampleDirections.Mesh); }

//printf("%s() %s:%d  %sLSphereInfo%s %p\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LSphereInfo);fflush(stdout);
						E3dObjectComponent_Free(LSphereInfo);  // Free this, as we were just using it as a template for E3dObject_CustomNewInit
					}
				}

				E3dScene_NodeHrcAdd(E3d_Scene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
				E3dScene_ObjectsUpdateModelList(LScene);
				E3dScene_LayoutOnSchematics(LScene, TRUE);
				LChanged |= E3dModel_GetChangeFlagsByObjects(LModel)|E3dChgHIERARCHY;
				_Update(NULL, (E3dNode*)LModel, (E3dNodeComponent*)LInfo, E3dChgATTRIBUTES, FALSE);
				
			}
		}

		if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dNode_Edit((E3dNode*)LModel, _NodeComponentClass);

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else
	{
NoMaterial:
		E3dp_PrintMessage(ELogMESSAGE, 10000, "Select something with a Material");
	}
}


static const char  _Tooltip[] = "Visualizes the BRDF on the selected object.\n\
You can Compare a Shader's ShadeSurface, SampleDirection and SampleDirection functions.\n\
It's great for slide presentations, or for debugging your shader.\n\
The light direction will be specified by the Scene's first point light's direction from the origin. Try moving the light around!";

//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	_Plugin = PPlugin;

	{
		E3dNodeComponentClass  LClass=
		{
			.Name=        "BRDF visualizer",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_Resources),
			.Init=        _Init,
			.Free=        _Free,
			.Copy=        _Copy,
			.Update=      _Update,
			.GUI=         _GUI
		};
		_NodeComponentClass = E3dNodeComponent_ClassRegister(&LClass);
	}

	if(_NodeComponentClass)
	{
		E3dScene_CallbackAdd(E3d_Scene, E3dChgNodeTRANSFORM|E3dChgMaterialTYPE|E3dChgMaterialPARAM, _CB_Scene, (EPointer)0);
// Use default plugin icon
//
		XePushButton_AddImg3(_MenuButtons[_NumOfMenuButtons++], Plugin, "Menu->Material", "BRDFVis", '\0', NULL, NULL, FALSE, _Tooltip, _CB, (EPointer)0);
		EpM_CreateIconStates3_sp(BRDFVis);XePushButton_AddImg3(_ToolbarButtons[_NumOfToolbarButtons++], BRDFVis, "Tool->Light", "BRDFVis", '\0', NULL, NULL, FALSE, _Tooltip, _CB, (EPointer)0);
	}

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		{ int  LI = 0;for(;LI < _NumOfMenuButtons;LI++) { XeItemFree(_MenuButtons[LI]); } _NumOfMenuButtons = 0; }
		{ int  LI = 0;for(;LI < _NumOfToolbarButtons;LI++) { XeItemFree(_ToolbarButtons[LI]); } _NumOfToolbarButtons = 0; }
	}

	E3dScene_CallbackRemove(E3d_Scene, _CB_Scene, (EPointer)0);

	if(_NodeComponentClass)
	{
		E3dNodeComponent_ClassDeactivate(_NodeComponentClass);_NodeComponentClass = NULL;
	}

	EpIconFree3(BRDFVis);

	return(E_SUCCESS);
}
