//================================================================================================================================
// Plugin for EQUINOX-3D
//
// Add a new VertexAttribute to the selected Meshes
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Aug-02 19:53:56
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//================================================================================================================================
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>  // For qsort()

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

#include <E3D/Scene.h>

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

#include <Xe/Dialog.h>
#include <Xe/Matrix.h>
#include <Xe/PushButton.h>
#include <Xe/Scale.h>
#include <Xe/Shell.h>

#define E3dMULTI_TREADING  1

char*                E_PluginInfo = "Generate Vertex gradients.";

static EPlugin*      _Plugin = NULL;

static XeDWidget      _ToolBarButtons[4];static int  _ToolBarButtons_Count = 0;

//static const char    _Gradient0Str[] = "BDistance";

static const char  _Str_Generating_Vertex_gradients[] = "Generating Vertex gradients";


typedef struct E3dInfo_S  E3dInfo;

#if E3dMULTI_TREADING
typedef struct
{
	EThreadSet1DCore;

	EIndex                 MeshNTotalVertices;
	const E3dVertexLinkNew*   VertexLinksNew;EIndex  VertexLinksStride;
	const E3dCoordinate*   EdgeLengths;
	const EIndex*          VerticesLeftToCheck;
	EList(, EIndex, TargetVertexIndices);
	E3dInfo*               ObjectComponent;
} EThreadSet1DDijkstra;
#endif


struct E3dInfo_S
{
	E3dObjectComponentCore;

// E3dInfo part
//
	float  Scale, Max, FallOffPower;

	EList(, E3dCoordinate, PathLengths);
	EBool  GradientGenerationGo;

#if E3dMULTI_TREADING
	EThreadSet1DDijkstra  ThreadSet;
#endif
};

enum
{
	E3dPathLengthsModeFROM_SELECTED_VERTICES = 1,
	E3dPathLengthsModeFROM_SELECTED_EDGES,
};

enum
{
	E3dPathLengthsSINGLE_PASS = 1,
	E3dPathLengthsDIJKSTRA,
	E3dPathLengthsA_STAR
};


static const char  E3dpStr_Distances_from_Vertices[] = "Calculate path lengths from selected Vertices.";
static const char  E3dpStr_Distances_from_Edges[] = "Calculate path lengths from Vertices on selected Edges.";

#define E3dPRIORITY_QUEUE  1

//static const int  _Algorithm = E3dPathLengthsSINGLE_PASS;
static const int  _Algorithm = E3dPathLengthsDIJKSTRA;

#define ROffset(field)  (void*)(EPtrOffset(E3dInfo, field))
#define ROffsetI(field)  EPtrOffset(E3dInfo, field)
static EResource  _Resources[]=
{
	{ "Scale",         EResFLOAT32,         ROffset(Scale) },
	{ "Max",           EResFLOAT32,         ROffset(Max) },
	{ "FallOffPower",  EResFLOAT32,         ROffset(FallOffPower) },

	{ "PathLengths",   E3dCOORDINATE_TYPE|EResDYNAMIC,  ROffset(PathLengths), .Ext.Count= ROffsetI(PathLengths_Count) },
};
#undef ROffset
#undef ROffsetI

static E3dObjectComponentClass*  _ComponentClass = NULL;

typedef struct
{
	XeWidget  ScaleW, MaxW, FallOffPowerW, FromVerticesW, FromEdgesW;
} E3dResourcesGUI;

//========================================================
// Constructor of the ObjectComponent class
//========================================================
static int _Init(E3dObject* LObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	EList_Init(LThis->PathLengths);

	LThis->Scale = 1.0;LThis->Max = 1.0;
	LThis->FallOffPower = 2.0;

	LThis->GradientGenerationGo = FALSE;

#if E3dMULTI_TREADING
	EThreadSet1D_Init((EThreadSet1D*)&LThis->ThreadSet);
#endif

	return(E_SUCCESS);
}


static int _Free(E3dObject* LObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	EList_FreeChk(LThis->PathLengths);

#if E3dMULTI_TREADING
	EThreadSet1D_FreeContents((EThreadSet1D*)&LThis->ThreadSet);
#endif

	return(E_SUCCESS);
}


enum
{
	E3dNOT_SET = -2,
	E3dON_SELECTED_EDGE = -1
};


typedef struct
{
	EIndex         VertexIndex;
	E3dCoordinate  Distance;
} E3dIndexAndDistance;


typedef struct
{
	E3dCoordinate  Dist;
	EIndex         Prev;
	EIndex         Orig;  // Vertex index in the Mesh
	uint16_t       InQueue:1, WasAddedToPriorityQueue:1;
} E3dDijkstraVertex;


//========================================
// Abort callback for renderers
//========================================
static void _CB_Abort(EPointer PClientData)
{
	E3dInfo*  LComponent = (E3dInfo*)PClientData;

	LComponent->GradientGenerationGo = FALSE;
}


//========================================================
// Copy-constructor of the ObjectComponent class
//========================================================
static void _Copy(const E3dObject* PSObject, const E3dObjectComponent* PSInfo, E3dObject* PDObject, E3dObjectComponent* PDInfo)
{
	const E3dInfo*  LSInfo = (E3dInfo*)PSInfo;
	E3dInfo*        LDInfo = (E3dInfo*)PDInfo;

	_Init(PDObject, PDInfo);

#define _CopyField(pField)  LDInfo->pField = LSInfo->pField
	_CopyField(Scale);
	_CopyField(Max);
	_CopyField(FallOffPower);
#undef _CopyField
}


#define ATTRIBUTE_TYPE  EResFLOAT32


#ifndef E3dPRIORITY_QUEUE
static int _DVertexCmp(const void* PA, const void* PB)
{
	const E3dDijkstraVertex*  LA = *((E3dDijkstraVertex**)PA);
	const E3dDijkstraVertex*  LB = *((E3dDijkstraVertex**)PB);

	if(LA->Dist < LB->Dist) return(1);
	else if(LA->Dist > LB->Dist) return(-1);
	else return(0);
}
#endif


static void _ProcessSourceVerticesDijkstra(const EIndex PStart, const EIndex PN,  const EIndex PMeshNTotalVertices,  const E3dVertexLinkNew* PVertexLinks, const EIndex PVertexLinksStride,  const E3dCoordinate* PEdgeLengths,
 const EIndex* PVerticesLeftToCheck,
 E3dDijkstraVertex* PDVertices, E3dDijkstraVertex** PDVerticesQ, EIndex* PS,
 const EIndex* PTargetVertexIndices, const EIndex PNTargetVertexIndices,
 E3dInfo* PInfo)
{
	EIndex  LVC = 0, LVI = PStart;for(;LVC < PN;LVC++, LVI++)
	{
		const EIndex  LSrcVertexIndex = PVerticesLeftToCheck[LVI];
//printf("%s() %s:%d  %sSrcVertex%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LSrcVertexIndex);fflush(stdout);

		{
			EIndex  LVC1 = 0, LI = 0;E3dDijkstraVertex*  LDVertex = PDVertices;for(;LVC1 < PMeshNTotalVertices;LVC1++, LDVertex++)
			{
				LDVertex->Dist = E3dHUGE;
				LDVertex->Prev = -1;
				LDVertex->InQueue = TRUE;
				LDVertex->WasAddedToPriorityQueue = FALSE;
#ifndef E3dPRIORITY_QUEUE
				if(LVC1 != LSrcVertexIndex) LDVerticesQ[LI++] = LDVertex;
#endif
			}
// Put the source Vertex last in LDVerticesQ, as it will have the smallest Dist value (0.0) at the beginning
//
			{
				E3dDijkstraVertex*  LSrcDVertex = PDVertices + LSrcVertexIndex;
				LSrcDVertex->Dist = 0.0;
				LSrcDVertex->InQueue = TRUE;
				PDVerticesQ[LI++] = LSrcDVertex;LSrcDVertex->WasAddedToPriorityQueue = TRUE;
			}
		}

		EIndex  LDVerticesQ_Count;
#ifdef E3dPRIORITY_QUEUE
		LDVerticesQ_Count = 1;
#else
		LDVerticesQ_Count = LVN;
#endif

		while(LDVerticesQ_Count > 0)
		{
// Get the DijkstraVertex with the smallest Dist value. This will be the last element, because:
// - we keep this list sorted to eliminate the need for searching
// - and it's sorted in ascending order to eliminate copies when removing elements
//
			const EIndex  LDVertexIndex = LDVerticesQ_Count - 1;E3dDijkstraVertex*  LDVertex = PDVerticesQ[LDVertexIndex];

			const E3dCoordinate  LDistU = LDVertex->Dist;
			const EIndex         LOrigVertexIndexU = LDVertex->Orig;

			LDVerticesQ_Count--;  // Remove U from the list
			LDVertex->InQueue = FALSE;

			{
				const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(PVertexLinks, PVertexLinksStride,  LOrigVertexIndexU);

//if(LSrcVertexIndex == LDebugV) { printf("%s() %s:%d    %sGREEN%s  U %2lld Dist %f  Valence %d", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LOrigVertexIndexU, LDVertices[LOrigVertexIndexU].Dist, LVertexUsage->EdgeEnds_Count);fflush(stdout); }

#ifndef E3dPRIORITY_QUEUE
				EBool  LSort = FALSE;
#endif
				EIndex  LI = 0;const EIndex  LN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LI < LN;LI++, LEdgeEnd++)
				{
					const EIndex  LVertex1Index = LEdgeEnd->VertexIndex;E3dDijkstraVertex*  LDVertex1 = PDVertices + LVertex1Index;
					if(LDVertex1->InQueue && !LDVertex1->WasAddedToPriorityQueue)
					{
//if(LSrcVertexIndex == LDebugV) { printf("  %lld %f", (long long)LVertex1Index, LDistU);fflush(stdout); }
						const EIndex         LEdgeIndex = LEdgeEnd->EdgeIndex;
						const E3dCoordinate  LAlt = LDistU + PEdgeLengths[LEdgeIndex];
						if(LDVertex1->Dist > LAlt)
						{
//if(LSrcVertexIndex == LDebugV) { printf(" %sCloser%s", ECodeGreen, ECodeNormal);fflush(stdout); }
							LDVertex1->Dist = LAlt;
							LDVertex1->Prev = LOrigVertexIndexU;

#ifdef E3dPRIORITY_QUEUE
// Insert LDVertex1 in LDVerticesQ at the righr position, keeping LDVerticesQ sorted in decreasing order by Dist

// We need to do a binary search. Do a linear search for now...

#define _Cmp(pIndex)  (PDVerticesQ[pIndex]->Dist >= LAlt)
							EIndex  LInsertBefore;E_BinarySearch(LDVerticesQ_Count, _Cmp, LInsertBefore);
#undef _Cmp

							if(LInsertBefore < LDVerticesQ_Count) memmove(PDVerticesQ + LInsertBefore + 1, PDVerticesQ + LInsertBefore, sizeof(E3dDijkstraVertex*) * (LDVerticesQ_Count - LInsertBefore));
							PDVerticesQ[LInsertBefore] = LDVertex1;
							LDVertex1->WasAddedToPriorityQueue = TRUE;
							LDVerticesQ_Count++;
#else
							LSort = TRUE;
#endif
						}
					}
				}
//if(LSrcVertexIndex == LDebugV) { printf("\n");fflush(stdout); }

#ifndef E3dPRIORITY_QUEUE
				if(LSort)
				{
//printf("%s() %s:%d  %sIMPLEMENT ME%s re-sort LDVertices\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);

const ETime  LT_ms = E_GetMilliseconds();
					qsort(LDVerticesQ, LDVerticesQ_Count, sizeof(E3dDijkstraVertex*), _DVertexCmp);
{ const ETime  LNow_ms = E_GetMilliseconds();Lbottleneck_ms += LNow_ms - LT_ms; }
				}
#endif
			}
		}

/*
1  S = empty sequence
2  u = target
3  if prev[u] is defined or u = source:          // Do something only if the vertex is reachable
4      while u is defined:                       // Construct the shortest path with a stack S
5          insert u at the beginning of S        // Push the vertex onto the stack
6          u = prev[u]                           // Traverse from target to source
*/

		{
			E3dCoordinate  LPathLengthToClosestTargetVertex = E3dHUGE;

			E3dCoordinate*  LPathLengths = PInfo->PathLengths;

			EIndex  LTI = 0;for(;LTI < PNTargetVertexIndices;LTI++)
			{
				EIndex         LS_Count = 0;
				E3dCoordinate  LPathLength = 0.0;
				EIndex         LU = PTargetVertexIndices[LTI];

//if(LSrcVertexIndex == LDebugV) { printf("%s() %s:%d    %sDstVertex%s %lld ", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LU);fflush(stdout); }
				if(PDVertices[LU].Prev >= 0 || LU == LSrcVertexIndex)
				{
					while(LU >= 0)
					{
//if(LSrcVertexIndex == LDebugV) { printf(" %lld", (long long)LU);fflush(stdout); }
						PS[LS_Count++] = LU;

						const EIndex  LPrev = PDVertices[LU].Prev;if(LPrev >= 0)
						{
							const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(PVertexLinks, PVertexLinksStride,  LU);
							EIndex  LI = 0;const EIndex  LN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LI < LN;LI++, LEdgeEnd++)
							{
								if(LEdgeEnd->VertexIndex == LPrev)
								{
									const EIndex  LEdgeIndex = LEdgeEnd->EdgeIndex;
									LPathLength += PEdgeLengths[LEdgeIndex];
								}
							}
						}

						LU = LPrev;
					}
//if(LSrcVertexIndex == LDebugV) { printf(" %f", LPathLength);fflush(stdout); }

					if(LPathLengthToClosestTargetVertex > LPathLength) LPathLengthToClosestTargetVertex = LPathLength;
				}
//if(LSrcVertexIndex == LDebugV) { printf("\n");fflush(stdout); }
			}

//if(LSrcVertexIndex == LDebugV) { printf("%s() %s:%d    %sShortest%s  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LPathLengthToClosestTargetVertex);fflush(stdout); }

			LPathLengths[LSrcVertexIndex] = LPathLengthToClosestTargetVertex;
		}

	}
}


#if E3dMULTI_TREADING

//========================================================
// Thread main function for Dijkstra shortest path
//========================================================
static void* _Dijkstra_Thread(void* PThread)
{
	EThread*                 LThread = (EThread*)PThread;
	EThreadSet1DDijkstra*    LThreadSet = (EThreadSet1DDijkstra*)LThread->ThreadSet;
	const EIndex             LNWorkUnits = LThreadSet->NWorkUnits;
	const EIndex             LVN = LThreadSet->MeshNTotalVertices;
	const E3dVertexLinkNew*  LVertexLinks = LThreadSet->VertexLinksNew;const EIndex  LVertexLinksStride = LThreadSet->VertexLinksStride;
	const E3dCoordinate*     LEdgeLengths = LThreadSet->EdgeLengths;
	const EIndex*            LTargetVertexIndices = LThreadSet->TargetVertexIndices;const EIndex  LTargetVertexIndices_Count = LThreadSet->TargetVertexIndices_Count;
	E3dInfo*                 LComponent = LThreadSet->ObjectComponent;

	E3dDijkstraVertex*  LDVertices;ENewN(LVN, LDVertices);
	{
		EIndex  LVC1 = 0;E3dDijkstraVertex*  LDVertex = LDVertices;for(;LVC1 < LVN;LVC1++, LDVertex++)
		{
			LDVertex->Orig = LVC1;
		}
	}

	E3dDijkstraVertex**  LDVerticesQ;ENewN(LVN, LDVerticesQ);
	EIndex*              LS;ENewN(LVN, LS);

	while(1)
	{
E_THREAD_LOCK(LThreadSet->GetWorkload);
		EIndex  LVC = LThreadSet->NextWorkUnit;LThreadSet->NextWorkUnit += 1;if(LVC >= LNWorkUnits)
		{
E_THREAD_UNLOCK(LThreadSet->GetWorkload);
			break;
		}
E_THREAD_UNLOCK(LThreadSet->GetWorkload);

		_ProcessSourceVerticesDijkstra(LVC, 1, LVN,  LVertexLinks, LVertexLinksStride,  LEdgeLengths,  LThreadSet->VerticesLeftToCheck,  LDVertices, LDVerticesQ, LS,  LTargetVertexIndices, LTargetVertexIndices_Count,  LComponent);
	}

	EFreeClr(LS);
	EFreeClr(LDVerticesQ);
	EFreeClr(LDVertices);

	return(NULL);
}
#endif


static void _Progress(const float PCompleted,  EPointer PCustomData,  void (*PAbortCallback)(EPointer PCustomData), EPointer PAbortFuncCustomData)
{
	E3dProgressIndicator_Set(PCompleted, _CB_Abort, PAbortFuncCustomData);XeApp_Sync(Xe_App);
}


//================================================================
// Calculate path lengths to Vertices on selected Edges
//================================================================
static int _GeneratePathLengths(E3dMesh* PMesh, E3dInfo* PInfo, const int PMode)
{
	int  LReturnValue = E_SUCCESS;

	const EIndex  LVN = PMesh->Vertices.Count;

	EResizeN(LVN, PInfo->PathLengths);PInfo->PathLengths_Count = LVN;

	const E3dVertexLinkNew*  LVertexLinks = E3dMesh_GetVertexLinksNew(PMesh, E3dVertexLinksEDGES);
	const EIndex             LVertexLinksStride = PMesh->VertexLinksStride;

	EListI(, EIndex, LVerticesLeftToCheck);ENewN(LVN, LVerticesLeftToCheck);LVerticesLeftToCheck_Count = LVN;

// Calculate Edge lengths
//
	const EIndex    LEN = PMesh->Edges.Count;
	E3dCoordinate*  LEdgeLengths;ENewN(LEN, LEdgeLengths);

	const E3dVertexSize  LVertexSize = PMesh->VertexSize;

	{
		EIndex  LEI = 0;const E3dVertex*  LVertices = PMesh->Vertices.Items;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LEI < LEN;LEI++, LEdge++)
		{
			const E3dVertex*  LVertexS = EStruct_ByIndex(LVertices, LVertexSize, LEdge->Start);
			const E3dVertex*  LVertexE = EStruct_ByIndex(LVertices, LVertexSize, LEdge->End);
			E3dCoordinate  LDistanceSq;E_DistanceSq3_vv(LVertexS->Position, LVertexE->Position, LDistanceSq);
			LEdgeLengths[LEI] = E3d_Sqrt(LDistanceSq);
		}
	}

	{
		{
			EIndex  LVI = 0;for(;LVI < LVN;LVI++)
			{
				LVerticesLeftToCheck[LVI] = LVI;
			}
		}

//printf("%s() %s:%d  LVerticesLeftToCheck_Count at start: %s%lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, (long long)LVerticesLeftToCheck_Count, ECodeNormal);fflush(stdout);
	}

//printf("%s() %s:%d  LVerticesLeftToCheck_Count at scan start: %s%lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, (long long)LVerticesLeftToCheck_Count, ECodeNormal);fflush(stdout);

	PInfo->GradientGenerationGo = TRUE;

	switch(_Algorithm)
	{
		case E3dPathLengthsSINGLE_PASS:
			{
				E3dIndexAndDistance*  LVertexTowardNearestSelectedEdgesVertex;ENewN(LVN, LVertexTowardNearestSelectedEdgesVertex);

				{
					EIndex  LVI = 0;E3dIndexAndDistance*  LVInfo = LVertexTowardNearestSelectedEdgesVertex;for(;LVI < LVN;LVI++, LVInfo++)
					{
						LVInfo->VertexIndex = E3dNOT_SET;
						LVInfo->Distance = -1.0;
					}
				}

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

// Mark Vertices that lie on selected Edges
//
				{
					EIndex  LEI = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LEI < LEN;LEI++, LEdge++)
					{
						if(LEdge->Flags & E3dEdgeSELECTED)
						{
							{
								E3dIndexAndDistance*  LVInfo = LVertexTowardNearestSelectedEdgesVertex + LEdge->Start;LVInfo->VertexIndex = E3dON_SELECTED_EDGE;LVInfo->Distance = 0.0;
								EList_IndexRemoveNoRealloc(LVerticesLeftToCheck, LEdge->Start);
							}

							{
								E3dIndexAndDistance*  LVInfo = LVertexTowardNearestSelectedEdgesVertex + LEdge->End;LVInfo->VertexIndex = E3dON_SELECTED_EDGE;LVInfo->Distance = 0.0;
								EList_IndexRemoveNoRealloc(LVerticesLeftToCheck, LEdge->End);
							}
						}
					}
				}

// Do a pass to mark Vertices one Edge away from selected Edges and make the corresponding E3dIndexAndDistance to point toward the nearest Vertex on a selected Edge
//
				{
//const EIndex  LDebugFromVertex = 23;
					EIndex  LVI = 0, LFoundC = 0;const E3dVertex*  LVertices = PMesh->Vertices.Items;for(;LVI < LVerticesLeftToCheck_Count;)
					{
						(void)LFoundC;
						const EIndex          LVertexIndex = LVerticesLeftToCheck[LVI];
						const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(LVertexLinks, LVertexLinksStride,  LVertexIndex);
						EIndex                LVertexToShortestEdge = -1;E3dCoordinate  LShortestEdgeLengthSq = 0.0;

//printf("%s() %s:%d  %sGREEN%s  EdgeEnds %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVertexUsage->EdgeEnds_Count);fflush(stdout);
						EIndex  LI = 0;const EIndex  LN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LI < LN;LI++, LEdgeEnd++)
						{
//printf("%s() %s:%d  %sGREEN%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI);fflush(stdout);

							const EIndex          LVertex1Index = LEdgeEnd->VertexIndex;
							E3dIndexAndDistance*  LVertex1Info = LVertexTowardNearestSelectedEdgesVertex + LVertex1Index;

							const EIndex  LVertex1NextVertexIndex = LVertex1Info->VertexIndex;if(LVertex1NextVertexIndex != E3dNOT_SET)
							{
								const E3dVertex*  LVertex0 = EStruct_ByIndex(LVertices, LVertexSize, LVertexIndex);
								const E3dVertex*  LVertex1 = EStruct_ByIndex(LVertices, LVertexSize, LVertex1Index);
								E3dCoordinate  LDistanceSq;E_DistanceSq3_vv(LVertex0->Position, LVertex1->Position, LDistanceSq);

								if(LVertex1NextVertexIndex == E3dON_SELECTED_EDGE && LShortestEdgeLengthSq > LDistanceSq)
								{
//if(LVertexIndex == LDebugFromVertex) { printf("%s() %s:%d  %sLVertexToShortestEdge%s %lld  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVertex1Index, E3d_Sqrt(LDistanceSq));fflush(stdout); }

									LShortestEdgeLengthSq = LDistanceSq;
									LVertexToShortestEdge = LVertex1Index;
								}
							}
						}

						if(LVertexToShortestEdge >= 0)
						{
							E3dIndexAndDistance*  LVertexInfo = LVertexTowardNearestSelectedEdgesVertex + LVertexIndex;
							LVertexInfo->VertexIndex = LVertexToShortestEdge;
							LVertexInfo->Distance = E3d_Sqrt(LShortestEdgeLengthSq);
							EList_IndexRemoveByIndexNoRealloc(LVerticesLeftToCheck, LVI);

//if(LVertexIndex == LDebugFromVertex) { printf("%s() %s:%d  %s%p%s LVertexToShortestEdge %lld  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LVertexInfo, ECodeNormal, (long long)LVertexInfo->VertexIndex, LVertexInfo->Distance);fflush(stdout); }
							LFoundC++;
						}
						else LVI++;
					}

printf("%s() %s:%d  Iteration 0  LVerticesLeftToCheck_Count: %s%lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, (long long)LVerticesLeftToCheck_Count, ECodeNormal);fflush(stdout);
				}

				{
					do
					{
//const EIndex  LDebugFromVertex = 23;
						EIndex  LVI = 0, LFoundC = 0;for(;LVI < LVerticesLeftToCheck_Count;)
						{
							const EIndex          LVertexIndex = LVerticesLeftToCheck[LVI];
							const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(LVertexLinks, LVertexLinksStride,  LVertexIndex);
							EIndex                LVertexToShortestEdge = -1;E3dCoordinate  LShortestEdgeLength = 0.0;

//printf("%s() %s:%d  %sGREEN%s  EdgeEnds %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVertexUsage->EdgeEnds_Count);fflush(stdout);
							EIndex  LI = 0;const EIndex  LN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LI < LN;LI++, LEdgeEnd++)
							{
//printf("%s() %s:%d  %sGREEN%s %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LI);fflush(stdout);

								const EIndex          LVertex1Index = LEdgeEnd->VertexIndex;
								const EIndex          LEdgeIndex = LEdgeEnd->EdgeIndex;
								E3dIndexAndDistance*  LVertex1Info = LVertexTowardNearestSelectedEdgesVertex + LVertex1Index;

								const EIndex  LVertex1NextVertexIndex = LVertex1Info->VertexIndex;if(LVertex1NextVertexIndex != E3dNOT_SET)
								{
									const E3dCoordinate  LDistance = LEdgeLengths[LEdgeIndex];
									EBool  LUpdate = LVertexToShortestEdge == -1;
									if(LVertex1NextVertexIndex == E3dON_SELECTED_EDGE) LUpdate = TRUE;
									else if(LShortestEdgeLength > LDistance) LUpdate = TRUE;

									if(LUpdate)
									{
//if(LVertexIndex == LDebugFromVertex) { printf("%s() %s:%d  %sLVertexToShortestEdge%s %lld  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVertex1Index, E3d_Sqrt(LDistanceSq));fflush(stdout); }

										LShortestEdgeLength = LDistance;
										LVertexToShortestEdge = LVertex1Index;
									}
								}
							}

							if(LVertexToShortestEdge >= 0)
							{
								E3dIndexAndDistance*  LVertexInfo = LVertexTowardNearestSelectedEdgesVertex + LVertexIndex;
								LVertexInfo->VertexIndex = LVertexToShortestEdge;
								LVertexInfo->Distance = LShortestEdgeLength;
								EList_IndexRemoveByIndexNoRealloc(LVerticesLeftToCheck, LVI);

//if(LVertexIndex == LDebugFromVertex) { printf("%s() %s:%d  %s%p%s LVertexToShortestEdge %lld  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeCyan, LVertexInfo, ECodeNormal, (long long)LVertexInfo->VertexIndex, LVertexInfo->Distance);fflush(stdout); }
								LFoundC++;
							}
							else LVI++;
						}

//printf("%s() %s:%d  Iteration %lld  LVerticesLeftToCheck_Count: %s%lld%s\n", __FUNCTION__, __FILE__, __LINE__, (long long)LIC, ECodeGreen, (long long)LVerticesLeftToCheck_Count, ECodeNormal);fflush(stdout);

						if(LFoundC == 0) break;
					} while(1);
				}

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


// Now, we can walk the Vertex / Edge paths to add up the distances to the nearest selected Edge from each Vertex
//
				{
					E3dCoordinate*  LPathLengths = PInfo->PathLengths;

//const EIndex  LDebugFromVertex = 23;
					EIndex  LVI = 0;const E3dIndexAndDistance*  LVInfo = LVertexTowardNearestSelectedEdgesVertex;for(;LVI < LVN;LVI++, LVInfo++)
					{
//if(LVI == LDebugFromVertex) { printf("%s() %s:%d  %sGREEN%s  LVI %lld ->", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVI);fflush(stdout); }
						const E3dIndexAndDistance*  LVT = LVInfo;E3dCoordinate  LDistance = 0.0;do
						{
//printf("%s() %s:%d  %sFIX ME%s This is not enough. We must walk the full path to the nearest selected Edge to find the shortest distance\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
							if(LVT->VertexIndex < 0 || LVT->Distance == 0.0) break;

//if(LVI == LDebugFromVertex) { printf(" %s%p%s %lld %f", ECodeCyan, LVT, ECodeNormal, (long long)LVT->VertexIndex, LVT->Distance);fflush(stdout); }
							LDistance += LVT->Distance;
							LVT = LVertexTowardNearestSelectedEdgesVertex + LVT->VertexIndex;
						} while(1);

//if(LVI == LDebugFromVertex) { printf("\nTotal distance %f\n", LDistance);fflush(stdout); }

						LPathLengths[LVI] = LDistance;
					}
				}

// Select the Vertices from which we couldn't find a path to a selected Edge, to show them. These are either Vertices
// unused by Polygons, or Vertices in Polygon islands, separated from the island(s) that have selected Edges
//
				{
					EIndex  LVI = 0;E3dVertex*  LVertices = PMesh->Vertices.Items;for(;LVI < LVerticesLeftToCheck_Count;LVI++)
					{
						const EIndex  LVertexIndex = LVerticesLeftToCheck[LVI];
						E3dVertex*    LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexIndex);
						LVertex->Flags |= E3dVtxSELECTED;
					}
				}

				EFreeClr(LVertexTowardNearestSelectedEdgesVertex);
			}
		break;

		case E3dPathLengthsDIJKSTRA:
			{
printf("%s() %s:%d  %sDijkstra%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);

				EListAI(, EIndex, LTargetVertexIndices);

// Mark selected Vertices or Vertices that lie on selected Edges. These will be our target Vertices.
//
				switch(PMode)
				{
					case E3dPathLengthsModeFROM_SELECTED_VERTICES:
						{
							if(E3dMesh_NumOfSelectedVertices(PMesh, 0.0, NULL) > 0)
							{
								EIndex  LVI = 0;const E3dVertex*  LVertex = PMesh->Vertices.Items;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LVertexSize))
								{
									if(LVertex->Flags & E3dVtxSELECTED)
									{
										EList_IndexRemoveNoRealloc(LVerticesLeftToCheck, LVI);
										EList_IndexAddAChk(LTargetVertexIndices, 1024, LVI);
									}
								}
							}
							else
							{
								E3dp_PrintMessage(ELogWARNING, 5000, "No selected Vertices on Mesh %s", PMesh->Name);
								LReturnValue = E_NO_DATA;
								goto Done;
							}
						}
					break;

					case E3dPathLengthsModeFROM_SELECTED_EDGES:
						{
							if(E3dMesh_NumOfSelectedEdges(PMesh) > 0)
							{
								EIndex  LEI = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LEI < LEN;LEI++, LEdge++)
								{
									if(LEdge->Flags & E3dEdgeSELECTED)
									{
										{
											EList_IndexRemoveNoRealloc(LVerticesLeftToCheck, LEdge->Start);
											EList_IndexAddAChk(LTargetVertexIndices, 1024, LEdge->Start);
										}

										{
											EList_IndexRemoveNoRealloc(LVerticesLeftToCheck, LEdge->End);
											EList_IndexAddAChk(LTargetVertexIndices, 1024, LEdge->End);
										}
									}
								}
							}
							else
							{
								E3dp_PrintMessage(ELogWARNING, 5000, "No selected Edges on Mesh %s", PMesh->Name);
								LReturnValue = E_NO_DATA;
								goto Done;
							}
						}
					break;
				}

				const ETime  LPrev_ms = E_GetMilliseconds();

				{
					const EBool  LShowProgress = LVerticesLeftToCheck_Count > 500;

					if(LShowProgress) { E3dProgressIndicator_SetMessage(_Str_Generating_Vertex_gradients);E3dProgressIndicator_Set(0.0, _CB_Abort, PInfo);XeApp_Sync(Xe_App); }

#if E3dMULTI_TREADING
					EThreadSet1DDijkstra*  LThreadSet = &PInfo->ThreadSet;
					LThreadSet->ProgressMessage = _Str_Generating_Vertex_gradients;
					LThreadSet->ProgressFunc = _Progress;
					LThreadSet->AbortFuncCustomData = PInfo;

					LThreadSet->MeshNTotalVertices = LVN;
					LThreadSet->VertexLinksNew = LVertexLinks;
					LThreadSet->VertexLinksStride = LVertexLinksStride;
					LThreadSet->EdgeLengths = LEdgeLengths;
					LThreadSet->VerticesLeftToCheck = LVerticesLeftToCheck;
					LThreadSet->TargetVertexIndices = LTargetVertexIndices;LThreadSet->TargetVertexIndices_Count = LTargetVertexIndices_Count;
					LThreadSet->ObjectComponent = PInfo;

					EThreadSet1D_Run((EThreadSet1D*)LThreadSet, LVerticesLeftToCheck_Count,  sizeof(EThread), NULL, NULL,  _Dijkstra_Thread);
#else

					ETime  LLastUIUpdate_ms = -1;
					E3dDijkstraVertex*  LDVertices;ENewN(LVN, LDVertices);
					{
						EIndex  LVC1 = 0;E3dDijkstraVertex*  LDVertex = LDVertices;for(;LVC1 < LVN;LVC1++, LDVertex++)
						{
							LDVertex->Orig = LVC1;
						}
					}

					E3dDijkstraVertex**  LDVerticesQ;ENewN(LVN, LDVerticesQ);
					EIndex*              LS;ENewN(LVN, LS);

//const EIndex  LDebugV = -1;
					EIndex  LVI = 0;for(;LVI < LVerticesLeftToCheck_Count && PInfo->GradientGenerationGo;LVI++)
					{
						_ProcessSourceVerticesDijkstra(LVI, 1,  LVN, LVertexUsageList, LEdgeLengths, LVerticesLeftToCheck,  LDVertices, LDVerticesQ, LS,  LTargetVertexIndices, LTargetVertexIndices_Count,  PInfo);

						if(LShowProgress)
						{
							const ETime  LNow_ms = E_GetMilliseconds();
							if(LNow_ms - LLastUIUpdate_ms > 200)
							{
								E3dProgressIndicator_Set((double)LVI / (double)LVerticesLeftToCheck_Count, _CB_Abort, PInfo);XeApp_Sync(Xe_App);
								LLastUIUpdate_ms = LNow_ms;
							}
						}
					}

					EFreeClr(LS);
					EFreeClr(LDVerticesQ);
					EFreeClr(LDVertices);
#endif

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

{ const ETime  LNow_ms = E_GetMilliseconds();char  LTimeStr[256];printf("%s() %s:%d  Shortest paths: %lld  Took %s%s%s\n", __FUNCTION__, __FILE__, __LINE__, (long long)(LVerticesLeftToCheck_Count * LTargetVertexIndices_Count), ECodeGreen, EStr_MillisecondsToString(LNow_ms - LPrev_ms, LTimeStr, FALSE), ECodeNormal);fflush(stdout); }

				{
					EIndex  LTI = 0;for(;LTI < LTargetVertexIndices_Count;LTI++)
					{
						PInfo->PathLengths[LTargetVertexIndices[LTI]] = 0.0;
					}
				}

				EList_FreeA(LTargetVertexIndices);
			}
		break;

		case E3dPathLengthsA_STAR:
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
		break;
	}

Done:
	EFreeClr(LEdgeLengths);

	EList_Free(LVerticesLeftToCheck);

	return(LReturnValue);
}


//========================================================================
// Update Vertex gradients. Re-generate PathLengths, if necessary
//========================================================================
static EBool _UpdateGradients(E3dMesh* PMesh, E3dInfo* PInfo, const int PMode)
{
	if(PInfo->PathLengths == NULL)
	{
		if(_GeneratePathLengths(PMesh, PInfo, PMode) != E_SUCCESS) return(FALSE);
	}

	EBool  LChanged = FALSE;

//	const char*  LSemantic = _Gradient0Str;
	const char*  LSemantic = E3dVtxAttribute_SoftSelection;
	EIndex  LOffset = EResource_Offset(&PMesh->VertexAttributes,  LSemantic, NULL, 0,  NULL);if(LOffset < 0)
	{
		EResource*  LAttr;if(E3dVertexGeometry_VertexAttributeAdd((E3dVertexGeometry*)PMesh,  LSemantic, "Gradient", ATTRIBUTE_TYPE, 1,  &LAttr) == E_SUCCESS)
		{
			LOffset = EResource_Offset(&PMesh->VertexAttributes,  LSemantic, NULL, 0,  NULL);
			LAttr->UI = (char*)E3dUI_Show0;
		}
		LChanged = TRUE;
	}

	const E3dVertexSize  LVertexSize = PMesh->VertexSize;

	E3dVertex*  LVertex = PMesh->Vertices.Items;
	float*      LPtr = (float*)EOffsetToPtr(LVertex, LOffset);

	const float  LScale = PInfo->Scale, LMax = PInfo->Max, LPower = PInfo->FallOffPower;

	EIndex  LVI = 0;const EIndex  LVN = PMesh->Vertices.Count;for(;LVI < LVN;LVI++, EStruct_Inc(LPtr, LVertexSize))
	{
		const E3dCoordinate  LPathLength = PInfo->PathLengths[LVI];
		if(LScale < 0.0)
		{
			float  LV = 1.0 - pow(LPathLength * -LScale, LPower);
			if(LV < 0.0) LV = 0.0;
			if(LV > LMax) LV = LMax;
			*LPtr = LV;
		}
		else
		{
			float  LV = pow(LPathLength * LScale, LPower);
			if(LV > LMax) LV = LMax;
			*LPtr = LV;
		}
	}

	{
		E3dVertexGeometryGPUData*  LGPUData = PMesh->VertexGeometryGPUData;if(LGPUData)
		{
			E3dVertexAttributeVisualization_Free(&LGPUData->VertexAttributeVisualization, FALSE);
		}
	}

	return(LChanged);
}


//========================================
// Update() (create/change) method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PComponentTemplate,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dMesh*  LMesh = E_DynamicCast(PObject, E3dMesh);if(LMesh)
	{
		E3dInfo*  LComponent = (E3dInfo*)PComponentTemplate;if(LComponent)
		{
			if(PCheckOnly)
			{
//				if(LComponent->PathLengths) _UpdateGradients(LMesh, LComponent, 0);
			}
			else
			{
				if(!PCheckOnly && LComponent->AttributeChangeCount == 0 && LComponent->InputChangeCount == 0) return(NULL);    // If no attributes or inputs changed, bail
/*
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
				EList_Free(LComponent->PathLengths);
				_UpdateGradients(LMesh, LComponent, 0);
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
*/
			}
		}
	}

	return(PObject);
}


//========================================
// Dialog box callback handlers
//========================================
static void _DCB_Scale(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dMesh*         LMesh = (E3dMesh*)LObject;
	E3dInfo*         LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);
	EChangeFlags     LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	LComponent->Scale = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

	if(_UpdateGradients(LMesh, LComponent, 0))
	{
		if(E3dObject_IsEdited(LObject)) E3dObjectPanel_Update(NULL, LObject,  NULL, NULL);
	}

	LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateVERTEX_ATTRIBUTE, E3dCallALL,  LScene);
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}

static void _DCB_Max(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dMesh*         LMesh = (E3dMesh*)LObject;
	E3dInfo*         LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);
	EChangeFlags     LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	LComponent->Max = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

	if(_UpdateGradients(LMesh, LComponent, 0))
	{
		if(E3dObject_IsEdited(LObject)) E3dObjectPanel_Update(NULL, LObject,  NULL, NULL);
	}

	LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateVERTEX_ATTRIBUTE, E3dCallALL,  LScene);
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}

static void _DCB_FallOffPower(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dMesh*         LMesh = (E3dMesh*)LObject;
	E3dInfo*         LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);
	EChangeFlags     LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	LComponent->FallOffPower = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

	if(_UpdateGradients(LMesh, LComponent, 0))
	{
		if(E3dObject_IsEdited(LObject)) E3dObjectPanel_Update(NULL, LObject,  NULL, NULL);
	}

	LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateVERTEX_ATTRIBUTE, E3dCallALL,  LScene);
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}

//========================================
// Dialog box callback handlers
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dMesh*         LMesh = (E3dMesh*)LObject;
	E3dInfo*         LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);
	EChangeFlags     LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, LComponent->Class);if(LDrawer)
	{
		E3dResourcesGUI*  LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;
		if(PW == LGUI->FromVerticesW)
		{
			EList_Free(LComponent->PathLengths);
			_UpdateGradients(LMesh, LComponent, E3dPathLengthsModeFROM_SELECTED_VERTICES);
		}
		else if(PW == LGUI->FromEdgesW)
		{
			EList_Free(LComponent->PathLengths);
			_UpdateGradients(LMesh, LComponent, E3dPathLengthsModeFROM_SELECTED_EDGES);
		}

		LChanged |= E3dObject_UpdateForDisplay(LObject,  E3dUpdateVERTEX_ATTRIBUTE, E3dCallALL,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


//========================================
// GUI
//========================================
static void _GUI(E3dObject* PObject, E3dObjectComponent* PInfo, void* PPanel, void* PDrawer, const int PAction)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PPanel;
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dResourcesGUI*         LGUI;
	E3dInfo*                 LComponent = (E3dInfo*)PInfo;

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

				XeScale_SetValue_f32(LGUI->ScaleW, LComponent->Scale, FALSE);
				XeScale_SetValue_f32(LGUI->MaxW, LComponent->Max, FALSE);
				XeScale_SetValue_f32(LGUI->FallOffPowerW, LComponent->FallOffPower, FALSE);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
				XeWidget  LIndentWs[7];int  LIndentN;      // For indenting the ScaleWidgets
				XeWidget  LTraverseWs[7];int  LTraverseN;  // For TextField traversal
				EArg      Args[256];int  ArgCnt;

				ENew(LGUI);LDrawer->ResourcesGUI = LGUI;

				LTraverseN = 0;
				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNScaleLengthInPixels, 160);
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->ScaleW = LW = XeScale_New_Coord(LTopMatrixW, "Scale",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LComponent->Scale,  -100.0, 100.0,  -2.0, 2.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_Scale, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNScaleLengthInPixels, 160);
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->MaxW = LW = XeScale_New_Coord(LTopMatrixW, "Max",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LComponent->Max,  0.01, 100000.0,  0.01, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_Max, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNScaleLengthInPixels, 160);
				EXeSetArg(XeNTextFieldDigits, 8);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->FallOffPowerW = LW = XeScale_New_Coord(LTopMatrixW, "Fall-off power",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  LComponent->FallOffPower,  0.01, 10.0,  0.01, 10.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_FallOffPower, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);

				{
					EXeStart;
					EXeSetArg(XeNSpacingTop, 6);EXeSetArg(XeNSpacingBottom, 4);
					EXeSetArg(XeNSpacingX, 6);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("OpMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 2);
					EXeSetArg(XeNMarginSizeX, 8);EXeSetArg(XeNMarginSizeY, -1);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8(E3dpStr_Distances_from_Vertices);
					LGUI->FromVerticesW = LW = XeWidget_ManagedNew("From Vertices", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 2);
					EXeSetArg(XeNMarginSizeX, 8);EXeSetArg(XeNMarginSizeY, -1);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8(E3dpStr_Distances_from_Edges);
					LGUI->FromEdgesW = LW = XeWidget_ManagedNew("From Edges", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB, (EPointer)LPanel);
				}
				
// Set up TextField traversal
//
				XeTraversal_SetRing(LTraverseWs, LTraverseN);
			}
		break;
	}
}


//================================================
// ToolBar icon callback handler
//================================================
static void _CB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(E3d_Scene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LI = 0;for(;LI < LNObjects;LI++)
		{
			E3dObject*  LObject = LObjects.Items[LI];
			E3dMesh*    LMesh = (E3dMesh*)LObject;

			E3dInfo*  LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ComponentClass, FALSE);if(LComponent == NULL)
			{
				LComponent = (E3dInfo*)E3dObjectComponent_Add(LObject, _ComponentClass);
				if(E3dObject_IsEdited(LObject)) E3dObjectPanel_Update(NULL, LObject,  NULL, _ComponentClass);
			}

			if(LNObjects == 1) E3dObject_Edit(LObject, _ComponentClass);
			else
			{
				if(_UpdateGradients(LMesh, LComponent, E3dPathLengthsModeFROM_SELECTED_EDGES))
				{
					if(E3dObject_IsEdited(LObject)) E3dObjectPanel_Update(NULL, LObject,  NULL, NULL);
				}
			}
		}
		EList_FreeA_s(LObjects);

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


static const char*  _ToolTip = "This plugin generates gradients between selected Edges on the selected Meshes, with a new VertexAttribute.";

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

	{
		E3dObjectComponentClass  LClass=
		{
			.Name=        "VertexGradients",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_Resources),

			.Init=        _Init,
			.Copy=        _Copy,
			.Free=        _Free,
			.Update1=      _Update1,
			.GUI=         _GUI
		};
		_ComponentClass = E3dObjectComponent_ClassRegister(&LClass);
	}

// Use default plugin icon
//
	if(_ComponentClass)
	{
		XePushButton_AddImg3(_ToolBarButtons[_ToolBarButtons_Count++], Plugin, "Tool->Create", "Vertex gradients", '\0', NULL, NULL, FALSE, _ToolTip, _CB, (EPointer)0);
		return(E_SUCCESS);
	}
	else return(E_COULD_NOT_INITIALIZE);
}


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

	if(_ComponentClass) { E3dObjectComponent_ClassDeactivate(_ComponentClass);_ComponentClass = NULL; }

	return(E_SUCCESS);
}
