//================================================================
// EQUINOX-3D plugin
//
// - Clean up Meshes
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jul-29 16:44:46
//
// 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 <Xe/Matrix.h>
#include <Xe/Scale.h>
#include <Xe/Shell.h>
#include <Xe/PushButton.h>
#include <Xe/ToggleButton.h>
#include <Xe/Shell.h>
#include <Xe/Dialog.h>

#include <E3D/Polygon.h>
#include <E3D/Mesh.h>

#include <E3D/Editing.h>
#include <E3D/Deleting.h>

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

#include <E3D/MergePolyGroups.h>

char*  E_PluginInfo =
"Mesh cleanup tools, including:\n- Remove empty Geometries from Models\n\
- Remove unused Vertices and unused Edges\n\
- Merge PolyGroups\n\
- Merge Vertices\n- Fix or remove bad and duplicate Polygons\nAnd more.";

static EBool  _Initialized = FALSE;

static E3dMergePolyGroups  _MergePolyGroups =
{
	.Active = FALSE,
	.LayoutST0.Mode = 0, .LayoutST0.Spacing = 0.01, .LayoutST0.Scaling = 1.0,
	.LayoutST1.Mode = 0, .LayoutST1.Spacing = 0.01, .LayoutST1.Scaling = 1.0,
	.LayoutUV.Mode  = 0, .LayoutUV.Spacing  = 0.01, .LayoutUV.Scaling  = 1.0
};

static EPlugin*       _Plugin = NULL;
static EPluginLock*   _DialogLock = NULL;

static XeDWidget       _MenuButtons[16];static int  _MenuButtons_Count = 0;


#define E3dMERGE_VERTEX_TO_CLOSEST  1

// Cleanup
//
static XeWidget       _DialogW = NULL;
static E3dCoordinate  _MinDistance = 0.01;

static EBool          _RemoveEmptyGeometries = FALSE,
                      _RemoveEmptyPolyGroups = FALSE,
                      _RemoveFloatingEdges = FALSE,
                      _RemoveUnusedPoints = FALSE,
                      _WeldPoints = FALSE,
                      _UnweldPoints = FALSE,
                      _RemoveDuplicatePolygons = FALSE,
                      _WeldOnlySelectedPoints = FALSE,
                      _FixPolygons = FALSE,
                      _RemoveBadPolygons = FALSE;


static XeWidget  _UnweldPointsBW = NULL, _WeldOnlySelectedPointsW, _FixPolygonsBW = NULL, _RemoveBadPolygonsBW = NULL,
                 _WeldPointsW = NULL;


enum
{
	CR_REMOVE_EMPTY_GEOMETRIES = XeCR_CUSTOM0,
	CR_REMOVE_EMPTY_POLYGROUPS,
	CR_REMOVE_FLOATING_EDGES,
	CR_REMOVE_UNUSED_POINTS,
	CR_WELD_POINTS,
	CR_WELD_ONLY_SELECTED_POINTS_TO_UNSELECTED_POINTS,
	CR_UNWELD_VERTICES,
	CR_FIX_POLYGONS,
	CR_REMOVE_BAD_POLYGONS,
	CR_REMOVE_DUPLICATE_POLYGONS,
	CR_MINDISTANCE
};

static EBool  _GoOn = FALSE;


//========================================================================================
// Plugin lock delete callback:
// called when EQX is exiting and is forcing all plugins to clean up after themselves
//========================================================================================
static int _DialogLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	XeWidget_Unmap(_DialogW);

	return(E_SUCCESS);
}


//========================================
// Abort callback
//========================================
static void _CB_Abort(EPointer PClientData)
{
	_GoOn = FALSE;
}


//================================================================================================================
// Fix Polygon with repeating VertexNodes, empty contours (e.g. a hole with less than 3 VertexNodes) etc.
//================================================================================================================
static EBool _FixPolygon(E3dPolyGroup* PPolyGroup, E3dPolygon* PPolygon)
{
	if(PPolygon->NumOfVertices >= 3)
	{
		E3dVertexNode*           LVertexNode = PPolygon->VertexNodes;
		EIndex                   LNVNodesRemoved = 0;
		const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;


#define DEBUG 1

// Do the exterior contouor
//
#ifdef DEBUG
printf("Polygon % 3lld :", (long long)(PPolygon - PPolyGroup->Polygons.Items));fflush(stdout);
#endif

		if(PPolygon->NumOfVertices > PPolygon->NumOfExteriorVertices)
		{
printf("%s() %s:%d  %sIMPLEMENT ME%s Polygon has hole(s)\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
		}


		EIndex  LVI = 0;for(;LVI < PPolygon->NumOfExteriorVertices;)
		{
			E3dVertexNode*  LNextVertexNode = E3d_PolygonContourNextVertexNodeI(PPolygon, LVertexNodeSize, LVI);

			const E3dVertexIndex  LVID = LVertexNode->VertexID;

#ifdef DEBUG
printf(" [%d - %d]", LVID, LNextVertexNode->VertexID);fflush(stdout);
#endif

			if(LNextVertexNode->VertexID == LVID)
			{
#ifdef DEBUG
//printf("Polygon %d Removing Vtx repeat: %d,%d\n", (int)(PPolygon - PPolyGroup->Polygons), LVID, LNextVertexNode->VertexID);fflush(stdout);
printf(" Removing Vtx repeat: %d,%d", LVID, LNextVertexNode->VertexID);fflush(stdout);
#endif
				if(LVI < (PPolygon->NumOfVertexNodes - 1)) memmove(LVertexNode, LNextVertexNode, LVertexNodeSize * (PPolygon->NumOfVertexNodes - LVI - 1));
				LNVNodesRemoved++;

				PPolygon->NumOfVertexNodes -= 1;
				PPolygon->NumOfExteriorVertices -= 1;
				PPolygon->NumOfVertices -= 1;
			}
			else { LVI++;LVertexNode = LNextVertexNode; }
		}
#ifdef DEBUG
printf("\n");fflush(stdout);
#endif


// E3dFIXME
// Now check for non-consecutive vertex repeats
//



//assert(PPolygon - PPolyGroup->Polygons != 8);

		if(LNVNodesRemoved)
		{
			if(PPolygon->NumOfExteriorVertices < 3)
			{
printf("%s() %s:%d Polygon %lld  Resulting Polygon has < 3 ExteriorVertices\n",  __FUNCTION__, __FILE__, __LINE__,  (long long)(PPolygon - PPolyGroup->Polygons.Items));fflush(stdout);
				return(TRUE);
			}
		}

		if(PPolygon->NumOfVertices > PPolygon->NumOfExteriorVertices)
		{
// Handle holes (check for repetition of contour separators too! There may be like a -3, then a -2!)
//
printf("%s() %s:%d  %sIMPLEMENT ME%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
		}


		if(LNVNodesRemoved)
		{
			if(PPolygon->NumOfVertexNodes < 3)
			{
				if(PPolygon->VertexNodes)
				{
					EFree(PPolygon->VertexNodes);
					PPolygon->VertexNodes = NULL;
					PPolygon->NumOfVertexNodes = PPolygon->NumOfExteriorVertices = PPolygon->NumOfVertices = 0;
				}

printf("%s() %s:%d Polygon %lld  Resulting Polygon has < 3 VertexNodes\n",  __FUNCTION__, __FILE__, __LINE__,  (long long)(PPolygon - PPolyGroup->Polygons.Items));fflush(stdout);
				return(TRUE);
			}
			else PPolygon->VertexNodes = (E3dVertexNode*)ERealloc(PPolygon->VertexNodes, LVertexNodeSize * PPolygon->NumOfVertexNodes);
		}
		return(FALSE);
	}
	return(FALSE);
}


//================================================
// Remove unused Vertices from a Mesh
//================================================
static EIndex _MeshRemoveFloatingEdges(EOps* POps, E3dMesh* PMesh)
{
	E3dVertex*    LVertices = PMesh->Vertices.Items;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;
	const EIndex  LVN = PMesh->Vertices.Count;

// Clear Vertex E3dVtxMARK0 flags
//
	{
		EIndex  LVI = 0;E3dVertex*  LVertex = LVertices;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LMeshVertexSize))
		{
			LVertex->Flags &= ~E3dVtxMARK0;
		}
	}

// Mark and count Vertices that are used by the Polygons of the Mesh
//
	EIndex  LNewNumOfVertices = 0;(void)LNewNumOfVertices;

	{
		EIndex  LPGC = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGC < LPGN;LPGC++)
		{
			E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[LPGC];
			const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

			EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++)
			{
				EIndex  LVNI = 0;const EIndex  LVNN = LPolygon->NumOfVertexNodes;const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
				for(;LVNI < LVNN;LVNI++, EStruct_Inc(LVertexNode, LVertexNodeSize))
				{
					if(LVertexNode->VertexID > -1)
					{
						E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID);
						if((LVertex->Flags & E3dVtxMARK0) == 0) { LVertex->Flags |= E3dVtxMARK0;LNewNumOfVertices++; }
					}
				}
			}
		}
	}

// Check for floating Edges (Mesh Edges that are not Polygon Edges)
//
	EListAI(, EIndex, LFloatingEdges);

	{
		EIndex  LEI = 0;const EIndex  LEN = PMesh->Edges.Count;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LEI < LEN;LEI++, LEdge++)
		{
			EBool  LEdgeIsFloating = FALSE;

			{
				E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LMeshVertexSize, LEdge->Start);
				if((LVertex->Flags & E3dVtxMARK0) == 0) { LVertex->Flags |= E3dVtxMARK0;LNewNumOfVertices++;LEdgeIsFloating = TRUE; }
			}

			{
				E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LMeshVertexSize, LEdge->End);
				if((LVertex->Flags & E3dVtxMARK0) == 0) { LVertex->Flags |= E3dVtxMARK0;LNewNumOfVertices++;LEdgeIsFloating = TRUE; }
			}

			if(LEdgeIsFloating)
			{
				EList_IndexAddA(LFloatingEdges, 4, LEI);
			}
		}
	}

	if(LFloatingEdges_Count)
	{
//printf("%s() %s:%d  Floating Edges found: %s%lld%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeBlue, (long long)LFloatingEdges_Count, ECodeNormal);fflush(stdout);

		E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)EOperator_New(E3dOperatorDeleteVertices_class);

		E_AssignToMember(PMesh, LOperator,MeshNewT);
		EResourceList_Copy(&PMesh->VertexAttributes, &LOperator->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);
		LOperator->VertexSize = PMesh->VertexSize;

		LOperator->EdgesToDelete_Count   = LFloatingEdges_Count;
		LOperator->EdgesToDelete_Indices = LFloatingEdges;

// Add Operator to POps
//
		EList_PointerAddA_s(POps->Operators, 1, LOperator);

// Call Operator
//
		E3dOperatorDeleteVertices_class->Func(POps, (EOperator*)LOperator, EOpDO);
	}

	return(LFloatingEdges_Count);
}


//========================================================
// Weld Vertices that are closer than a given distance
//========================================================
static EBool _MeshWeldPoints(E3dMesh* PMesh, EIndex* PNumOfVerticesPtr, EIndex* POldToNewVertexTable, const E3dCoordinate PMinDSquare)
{
	E3dVertex*            LVertices = PMesh->Vertices.Items;
	const E3dVertexIndex  LVertices_Count = PMesh->Vertices.Count;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;
	const EResourceList*  LVertexAttributes = &PMesh->VertexAttributes;
	EIndex*               LNewToOldVertexTable;ENewN(LVertices_Count, LNewToOldVertexTable);
	EIndex                LNewNumOfVertices = 0;
	const EIndex          LNVerticesAllocated = LVertices_Count + 1;
	E3dVertex*            LNewVertices = (E3dVertex*)EMalloc(LMeshVertexSize * LNVerticesAllocated);
	ETime                 LLastGUIUpdateMilliseconds = E_GetMilliseconds();

	if(_WeldOnlySelectedPoints)
	{
		E3dVertexIndex*  LSelectedVertexIndices;ENewN(LVertices_Count, LSelectedVertexIndices);
		E3dVertexIndex   LNumOfSelectedVertices = 0;

		E3dVertex*  LNewVertex = LNewVertices;

// Copy unselected Vertices first
//
		{
			EIndex  LVI = 0;const E3dVertex*  LVertex = LVertices;for(;LVI < LVertices_Count;LVI++, EStruct_Inc(LVertex, LMeshVertexSize))
			{
				if(LVertex->Flags & E3dVtxSELECTED)
				{
					LSelectedVertexIndices[LNumOfSelectedVertices] = LVI;LNumOfSelectedVertices++;
				}
				else
				{
					E3dVertex_Copy(LVertex, LMeshVertexSize, LVertexAttributes,  LNewVertex, LMeshVertexSize, LVertexAttributes);
					POldToNewVertexTable[LVI] = LNewNumOfVertices;
					LNewToOldVertexTable[LNewNumOfVertices] = LVI;
					LNewNumOfVertices++;
					EStruct_Inc(LVertex, LMeshVertexSize);
				}
			}
		}

		const EIndex  LNumOfUnselectedVertices = LNewNumOfVertices;
		if(LNewNumOfVertices < LVertices_Count)
		{
			EIndex  LVI = 0;for(;LVI < LNumOfSelectedVertices && _GoOn;LVI++)
			{
				const E3dVertexIndex  LSelectedVertexIndex = LSelectedVertexIndices[LVI];
				const E3dVertex*      LOldVertex = EStruct_ByIndex(LVertices, LMeshVertexSize, LSelectedVertexIndex);
				E3dVertexIndex        LClosestVertexIndex = -1;
				E3dCoordinate         LClosestDSquare = 0.0;

				EIndex  LC1 = 0;E3dVertex*  LTVertex = LNewVertices;for(;LC1 < LNumOfUnselectedVertices;LC1++, EStruct_Inc(LTVertex, LMeshVertexSize))
				{
					const E3dCoordinate  LDX = LOldVertex->Position.X - LTVertex->Position.X;
					const E3dCoordinate  LDY = LOldVertex->Position.Y - LTVertex->Position.Y;
					const E3dCoordinate  LDZ = LOldVertex->Position.Z - LTVertex->Position.Z;
					const E3dCoordinate  LDSquare = LDX * LDX + LDY * LDY + LDZ * LDZ;

					if(LDSquare < PMinDSquare)
					{
						if(E3dMERGE_VERTEX_TO_CLOSEST)
						{
							if(LClosestVertexIndex == -1 || LClosestDSquare > LDSquare)
							{
								LClosestVertexIndex = LC1;
								LClosestDSquare = LDSquare;
							}
						}
						else { POldToNewVertexTable[LSelectedVertexIndex] = LC1;goto MergeVertex1; }
					}
				}

				if(E3dMERGE_VERTEX_TO_CLOSEST && LClosestVertexIndex != -1)
				{
					POldToNewVertexTable[LSelectedVertexIndex] = LClosestVertexIndex;
					goto MergeVertex1;
				}


// Vertex is not close to any other ones : add it to the new array
//
				*LNewVertex = *LOldVertex;
				POldToNewVertexTable[LSelectedVertexIndex] = LNewNumOfVertices;
				LNewToOldVertexTable[LNewNumOfVertices] = LSelectedVertexIndex;
				LNewNumOfVertices++;LNewVertex++;

MergeVertex1:
				if((LVI & 0xFF) == 0)
				{
					const ETime  LNow = E_GetMicroseconds();
					if(LLastGUIUpdateMilliseconds - LNow > 10)
					{
						E3dProgressIndicator_Set((float)LVI / (float)LNumOfSelectedVertices, _CB_Abort, NULL);XeApp_Sync(Xe_App);
						LLastGUIUpdateMilliseconds = LNow;
					}
				}
			}
		}

{
EIndex  LI = 0;for(;LI < LVertices_Count;LI++)
{
assert(POldToNewVertexTable[LI] >= 0);
//	POldToNewVertexTable[LI];
}
}

		EFree(LSelectedVertexIndices);
	}
	else
	{
		LNewNumOfVertices = 1;

// Copy 0th Vertex (we won't compare it with itself)
//
		E3dVertex_Copy(LVertices, LMeshVertexSize, LVertexAttributes,  LNewVertices, LMeshVertexSize, LVertexAttributes);
		POldToNewVertexTable[0] = 0;
		LNewToOldVertexTable[0] = 0;

		E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LMeshVertexSize, 1);
		E3dVertex*  LNewVertex = EStruct_ByIndex(LNewVertices, LMeshVertexSize, 1);
		EIndex  LVI = 1;for(;(LVI < LVertices_Count) && _GoOn;LVI++, EStruct_Inc(LVertex, LMeshVertexSize))
		{
			E3dVertexIndex  LClosestVertexIndex = -1;
			E3dCoordinate   LClosestDSquare = 0.0;

			EIndex  LC1 = 0;E3dVertex*  LTVertex = LNewVertices;for(;LC1 < LNewNumOfVertices;LC1++, EStruct_Inc(LTVertex, LMeshVertexSize))
			{
				const E3dCoordinate  LDX = LVertex->Position.X - LTVertex->Position.X;
				const E3dCoordinate  LDY = LVertex->Position.Y - LTVertex->Position.Y;
				const E3dCoordinate  LDZ = LVertex->Position.Z - LTVertex->Position.Z;
				const E3dCoordinate  LDSquare = LDX * LDX + LDY * LDY + LDZ * LDZ;

				if(LDSquare < PMinDSquare)
				{
					if(E3dMERGE_VERTEX_TO_CLOSEST)
					{
						if(LClosestVertexIndex == -1 || LClosestDSquare > LDSquare)
						{
							LClosestVertexIndex = LC1;
							LClosestDSquare = LDSquare;
						}
					}
					else { POldToNewVertexTable[LVI] = LC1;goto MergeVertex2; }
				}
			}

			if(E3dMERGE_VERTEX_TO_CLOSEST && LClosestVertexIndex != -1)
			{
				POldToNewVertexTable[LVI] = LClosestVertexIndex;
				goto MergeVertex2;
			}

// Vertex is not close to any other ones : add it to the new array
//
			E3dVertex_Copy(LVertex, LMeshVertexSize, LVertexAttributes,  LNewVertex, LMeshVertexSize, LVertexAttributes);
			POldToNewVertexTable[LVI] = LNewNumOfVertices;
			LNewToOldVertexTable[LNewNumOfVertices] = LVI;
			LNewNumOfVertices++;
			EStruct_Inc(LNewVertex, LMeshVertexSize);

MergeVertex2:
			if((LVI & 0xFF) == 0)
			{
				const ETime  LNow = E_GetMicroseconds();
				if(LLastGUIUpdateMilliseconds - LNow > 10)
				{
					E3dProgressIndicator_Set((float)LVI / (float)LVertices_Count, _CB_Abort, NULL);XeApp_Sync(Xe_App);
					LLastGUIUpdateMilliseconds = LNow;
				}
			}
		}
	}

	if(_GoOn)
	{
// Free unnecessary Vertices
//
		if(LNVerticesAllocated > LNewNumOfVertices)
		{
			E3dVertices_FreeContents(EStruct_ByIndex(LNewVertices, LMeshVertexSize, LNewNumOfVertices), LMeshVertexSize, LNVerticesAllocated - LNewNumOfVertices, LVertexAttributes);
			LNewVertices = (E3dVertex*)ERealloc(LNewVertices, LMeshVertexSize * LNewNumOfVertices);
		}

		*PNumOfVerticesPtr = LNewNumOfVertices;

		E3dMesh_VerticesRemap(PMesh, LNewToOldVertexTable, LNewNumOfVertices);

		EFreeChk(LNewToOldVertexTable);
		EFreeChk(LNewVertices);

		return(TRUE);
	}
	else
	{
		E3dVertices_Free(LNewVertices, LNewNumOfVertices, LMeshVertexSize,  LVertexAttributes);
		EFreeChk(LNewToOldVertexTable);
		return(FALSE);
	}
}


static EBool _VertexNodesEqual(const EResourceList* PAttributes0, const E3dVertexNode* PVertexNode0,  const EResourceList* PAttributes1, const E3dVertexNode* PVertexNode1,
 const E3dCoordinate PEpsilon)
{
	int  LOffset0 = sizeof(E3dVertexNodeCore), LOffset1 = sizeof(E3dVertexNodeCore);

	int  LI = 0;const int  LN0 = PAttributes0->Count;const int  LN1 = PAttributes1->Count;const EResource*  LVA0 = PAttributes0->Items;const EResource*  LVA1 = PAttributes1->Items;
	for(;LI < LN0 && LI < LN1;LI++, LVA0++, LVA1++)
	{
		if(LVA0->Semantic == LVA1->Semantic)
		{
			if(LVA0->Semantic == E3dVtxAttribute_Normal)
			{
				const E3dCoord3*  L0  = (E3dCoord3*)((uint8_t*)PVertexNode0 + LOffset0);
				const E3dCoord3*  L1  = (E3dCoord3*)((uint8_t*)PVertexNode1 + LOffset1);

				if((E3d_Abs(L0->X - L1->X) > PEpsilon) ||
				   (E3d_Abs(L0->Y - L1->Y) > PEpsilon) ||
				   (E3d_Abs(L0->Z - L1->Z) > PEpsilon)) return(FALSE);
			}
			else if(LVA0->Semantic == E3dVtxAttribute_ST)
			{
				const E3dST*  LST0  = (E3dST*)((uint8_t*)PVertexNode0 + LOffset0);
				const E3dST*  LST1  = (E3dST*)((uint8_t*)PVertexNode1 + LOffset1);

				if(E3d_Abs(LST0->S - LST1->S) > PEpsilon || E3d_Abs(LST0->T - LST1->T) > PEpsilon) return(FALSE);
			}

// E3dFINISHME
// Check other vertex attributes as well (color, tangent etc.)
//
		}
		else return(FALSE);

		{ const E3dVertexNodeSize  LTSize = EResource_Size(LVA0);if(LTSize >= 0) LOffset0 += LTSize; }
		{ const E3dVertexNodeSize  LTSize = EResource_Size(LVA0);if(LTSize >= 0) LOffset1 += LTSize; }
	}

	return(TRUE);
}


//========================================================
// Split Mesh Vertices if their corresponding Polygon
// vertex-normals, texture coordinates etc. are different
//========================================================
static EIndex _MeshUnweldPoints(E3dMesh* PMesh)
{
	EIndex	LNumOfVerticesAdded = 0;

	if(PMesh->Vertices.Count > 0)
	{
		EIndex                     LNumOfVertices = PMesh->Vertices.Count, LNumOfVerticesAllocated = PMesh->Vertices.Count;
		const E3dVertexLinkPolys*  LVertexLinks = (E3dVertexLinkPolys*)E3dMesh_GetVertexLinksNew(PMesh, E3dVertexLinksPOLYGONS|E3dVertexLinksPOLYGROUPS);
		const EIndex               LVertexLinksStride = PMesh->VertexLinksStride;
		EIndex*                    LNewToOldTable;ENewN(LNumOfVertices, LNewToOldTable);

// Fill out first PMesh->Vertices.Count elements of LNewToOldTable
//
		{
			EIndex  LVI = 0;const EIndex  LVN = LNumOfVertices;for(;LVI < LVN;LVI++) LNewToOldTable[LVI] = LVI;
		}

		EIndex  LVI = 0;const EIndex  LVN = LNumOfVertices;const E3dVertexLinkPolys*  LVertexLinksRec = LVertexLinks;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
		{
			if(LVertexLinksRec->VertexNodes)
			{
				E3dVertexNode*  LVertexNode0 = LVertexLinksRec->VertexNodes[0];
				E3dPolyGroup*   LPolyGroup0 = LVertexLinksRec->PolyGroups[0];

#define EPSILON	0.0001
				EIndex  LVNI = 1;const EIndex  LVNN = LVertexLinksRec->VertexNodes_Count;for(;LVNI < LVNN;LVNI++)
				{
					E3dVertexNode*  LVertexNode = LVertexLinksRec->VertexNodes[LVNI];
					E3dPolyGroup*   LPolyGroup = LVertexLinksRec->PolyGroups[LVNI];

					if(_VertexNodesEqual(&LPolyGroup->VertexAttributes, LVertexNode,  &LPolyGroup0->VertexAttributes, LVertexNode0,  EPSILON)) continue;

// Unweld Vertex and put new one at the end of PMesh->Vertices
//
					if(LNumOfVerticesAllocated < LNumOfVertices + 1)
					{
						EResizeNC(EIndex, LNumOfVerticesAllocated + 8,  LNewToOldTable, LNumOfVerticesAllocated);
					}

					LNewToOldTable[LNumOfVertices] = LVI;

					LVertexNode->VertexID = LNumOfVertices;

					LNumOfVertices++;LNumOfVerticesAdded++;

//printf("%s() unwelded vertex %d\n", __FUNCTION__, LVI);fflush(stdout);
				}
			}
			else
			{
				if(LVertexLinksRec->VertexNodes_Count)
				{
printf("%s() %s:%d %sERROR%s Vertex %d indicates, but LVertexUsageEntry->VertexNodes is NULL\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeRed, ECodeNormal, LVertexLinksRec->VertexNodes_Count);
fflush(stdout);
				}
			}
		}

		E3dMesh_FreeCaches(PMesh);

		E3dMesh_VerticesRemap(PMesh, LNewToOldTable, LNumOfVertices);

		EFree(LNewToOldTable);
	}

	return(LNumOfVerticesAdded);
}


//========================================================
// Remove (hoppelessly) bad Polygons from a Mesh
//========================================================
static EIndex _MeshRemoveBadPolygons(E3dMesh* PMesh)
{
	E3dVertex*  LVertices = PMesh->Vertices.Items;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;
	EIndex      LNumOfBadPolysRemoved = 0;

	EIndex  LPGC = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGC < LPGN;LPGC++)
	{
		E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGC];
		E3dPolygon*    LPolygon = LPolyGroup->Polygons.Items;
		EIndex         LNumOfPolysRemoved = 0;

		if(LPolygon)
		{
			const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

			E3dProgressIndicator_SetMessage("Looking for bad Polygons: PolyGroup #%d", LPGC);XeApp_Sync(Xe_App);

			EIndex  LCOrig = 0;
			EIndex  LI = 0, LN = LPolyGroup->Polygons.Count, LNOrig = LN;for(;LI < LN;)
			{
				E3dVertexNode*  LVertexNode;
				E3dVertexNode*  LVertexNode1;
				E3dVertexNode*  LVertexNode2;

				EBool  LRemovePoly = FALSE;
				if(LPolygon->NumOfVertices < 3) LRemovePoly = TRUE;
				else
				{
					if(LPolygon->NumOfVertices == 3)
					{
						LVertexNode = LPolygon->VertexNodes;
						LVertexNode1 = EStruct_Next(LVertexNode, LVertexNodeSize);
						LVertexNode2 = EStruct_Next(LVertexNode1, LVertexNodeSize);
						if((LVertexNode->VertexID == LVertexNode1->VertexID)||
						   (LVertexNode->VertexID == LVertexNode2->VertexID)||
						   (LVertexNode1->VertexID == LVertexNode2->VertexID)) LRemovePoly = TRUE;
					}
					else
					{
// If it's not a triangle, try triangulating it. If that fails, it's a degenerate Polygon
//
						if(E3dPolygon_TestTriangulate(LPolygon, LVertexNodeSize,  LVertices, LMeshVertexSize,  TRUE) == 0)
						{
printf("%s() %s:%d DEGENERATE poly %d\n",  __FUNCTION__, __FILE__, __LINE__,  (int)LI);fflush(stdout);
							LRemovePoly = TRUE;
						}
					}
				}

				if(LRemovePoly)
				{
					E3dM_PolygonFreeContents(LPolygon);

					if(LI < LN - 1) memmove(LPolygon, LPolygon + 1, sizeof(E3dPolygon) * (LN - LI - 1));
					LPolyGroup->Polygons.Count -= 1;
					LN--;

					LNumOfPolysRemoved++;
					if((LNumOfPolysRemoved & 0xFF) == 0)
					{
						E3dProgressIndicator_SetMessage("PolyGroup %d of %d (%d Polys), removed %d bad Polygons", LPGC + 1, LPGN, LNOrig, LNumOfPolysRemoved);XeApp_Sync(Xe_App);
					}
				}
				else { LPolygon++;LI++; }

				LCOrig++;
				if((LCOrig & 0xFF) == 0) E3dProgressIndicator_Set((float)LCOrig / (float)LNOrig, _CB_Abort, NULL);
			}
		}
		LNumOfBadPolysRemoved += LNumOfPolysRemoved;
	}

	return(LNumOfBadPolysRemoved);
}


//========================================================
// Remove Polygons that use the same Vertices
//========================================================
static EIndex _MeshRemoveDuplicatePolygons(E3dMesh* PMesh)
{
	EIndex  LNumOfDuplicatePolysRemoved = 0;


	E3dProgressIndicator_SetMessage("Looking for duplicate Polygons");XeApp_Sync(Xe_App);

	EIndex  LPGC = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;(LPGC < LPGN) && _GoOn;LPGC++)
	{
		E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[LPGC];
		E3dPolygon*              LPolygons = LPolyGroup->Polygons.Items;
		const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
		EIndex                   LNumOfPolysRemoved = 0;

		if(LPolyGroup->Polygons.Count > 1)
		{
			EIndex       LNewNumOfPolygons = 1;
			EIndex       LNOrig = LPolyGroup->Polygons.Count;
			E3dPolygon*  LNewPolygons;ENewN((LNOrig + 1), LNewPolygons);

			memcpy(LNewPolygons, LPolygons, (sizeof(E3dPolygon) * LNOrig));

			E3dPolygon*  LPolygon = LPolygons + 1;
			E3dPolygon*  LCopy = LNewPolygons + 1;
			EIndex  LI = 1;for(;LI < LNOrig;LI++, LPolygon++)
			{
				EIndex  j = 0;for(;j < LNewNumOfPolygons;j++)
				{
					E3dPolygon*  LTPolygon = LNewPolygons+j;

					if(LTPolygon->NumOfVertexNodes == LPolygon->NumOfVertexNodes)
					{
						const EIndex          LVN = LPolygon->NumOfVertexNodes;
						const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
						E3dVertexNode*        LTVertexNode = LTPolygon->VertexNodes;

// E3dFIXME
// Two Polygons might not start on the same Vertex, but they might still use the same exact Vertices.
// Add a check for this situation.
//
						EIndex  LVI = 0;for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNode, LVertexNodeSize), EStruct_Inc(LTVertexNode, LVertexNodeSize))
						{
							if(LVertexNode->VertexID != LTVertexNode->VertexID) break;
						}
						if(LVI == LVN)
						{
							E3dM_PolygonFreeContents(LPolygon);

							LNumOfPolysRemoved++;

//printf("Prnt %d\n", LNumOfPolysRemoved);fflush(stdout);
							if((LNumOfPolysRemoved & 0x3F) == 0) { E3dProgressIndicator_SetMessage("PolyGroup %d of %d, removed %d redundant Polygons", LPGC+1, LPGN, LNumOfPolysRemoved);XeApp_Sync(Xe_App); }

							goto duplicatePoly;
						}
					}
				}

// Add to the new array
//
				*LCopy = *LPolygon;
				LNewNumOfPolygons++;LCopy++;

duplicatePoly:

				if((LI & 0x1FF) == 0) E3dProgressIndicator_Set((float)LI / (float)LNOrig, _CB_Abort, NULL);
			}

// Free unnecessary Polygons
//
			if(LNewNumOfPolygons < LNOrig) EResizeNT(E3dPolygon, LNewNumOfPolygons,  LNewPolygons);


			EFree(LPolyGroup->Polygons.Items);
			LPolyGroup->Polygons.Items = LNewPolygons;
			LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LNewNumOfPolygons;
		}
		E3dProgressIndicator_SetMessage("PolyGroup %d of %d, removed %d redundant Polygons", LPGC + 1, LPGN, LNumOfPolysRemoved);XeApp_Sync(Xe_App);
	}

	return(LNumOfDuplicatePolysRemoved);
}


//================================================================================================
// Clean up selected objects
//
// E3dFINISHME LGeoUpdateFlags is set, but we still call E3dObject_UpdateForDisplay in both
// here and in E3dOperatorDeleteVertices_class->Func (called by E3d_MeshRemoveUnusedVertices).
// Solution? A: try to combine all operations here into an undoable Operator
// Solution? B: remove calls to E3dObject_UpdateForDisplay from Operators, collect changes and call E3dObject_UpdateForDisplay after all Operators were called in an EOps (undo/redo step).
//
//================================================================================================
static EChangeFlags _Cleanup(E3dScene* PScene)
{
	EIndex  LClCnt = 0,
	        LNumOfGeometriesRemoved = 0,
	        LNumOfPolyGroupsRemoved = 0,
	        LNumOfBadPolysRemoved = 0,
	        LNumOfDuplicatePolysRemoved = 0,
	        LNumOfPointsRemoved = 0, LNumOfPointsAdded = 0,
	        LNumOfEdgesRemoved = 0;

	const EIndex  LNSelectedGeometries = E3dScene_GetObjects(PScene, E3dItemMESH, E3dSELECTED, FALSE,  NULL);

	_GoOn = TRUE;

	EChangeFlags  LChanged = 0;

	if(LNSelectedGeometries > 0)
	{
		EIndex  LRI = 0;const EIndex  LRN = PScene->RootNodes.Count;E3dNode**  LRootNodes = PScene->RootNodes.Items;for(;LRI < LRN && _GoOn;LRI++)
		{
			E3dNode*  LNode = LRootNodes[LRI];for(;LNode && _GoOn;LNode = LNode->Next)
			{
				E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
				{
					const EBool  LNodeSelected = E3dNode_IsSelected(LNode);

// We need to collect the Objects from LModel into a temporary array, to make sure the array doesn't change as we call E3dObject_UpdateForDisplay, etc.
//
					EListAI(, E3dObject*, LObjects);

					{ EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;for(;LObjI < LObjN;LObjI++) EList_ObjectAddA(LObjects, 16, LModel->Objects.Items[LObjI]); }

					if(LObjects)
					{
						EIndex  LObjI = 0;for(;LObjI < LObjects_Count;LObjI++)
						{
							E3dObject*  LObject = LObjects[LObjI];

							if(LNodeSelected || (LModel->Selection == E3dNodeSelOBJECT && LObject->Selection == E3dObjectSelOBJECT))
							{
								if(EObject_IsSubClass(LObject, E3dMesh))
								{
									E3dMesh*      LMesh = (E3dMesh*)LObject;
									EIndex        LMeshVN = LMesh->Vertices.Count;
									EBool         LFixPolygons = FALSE, LRemoveBadPolygons = FALSE;
									EUpdateFlags  LGeoUpdateFlags = 0;

// Remove empty Geometries
//
									if(_RemoveEmptyGeometries)
									{
										if(LMesh->PolyGroups.Count == 0)
										{
											LNumOfGeometriesRemoved++;
											E3dModel_ObjectRemove(LModel, LObject);break;
										}
									}

									E3dProgressIndicator_SetMessage("Checking Mesh [%s]: %d Vertices, %d PolyGroups", LMesh->Name, LMesh->Vertices.Count, LMesh->PolyGroups.Count);

									if(_RemoveEmptyPolyGroups)
									{
										EIndex	LPGC = 0;for(;LPGC < LMesh->PolyGroups.Count;)
										{
											E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGC];

											if(LPolyGroup->Polygons.Count == 0 && LPolyGroup->TriangleStrips.Count == 0)
											{
												E3dMesh_PolyGroupDel(LMesh, LPolyGroup);
												LNumOfPolyGroupsRemoved++;
												LGeoUpdateFlags |= E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateTRIANGULATE|E3dUpdateVERTEX_NORMAL);
											}
											else LPGC++;
										}
									}

									if(_MergePolyGroups.Active && LMesh->PolyGroups.Count > 1)
									{
										const EIndex  LNumOfPolyGroupsRemovedT = E3dMesh_MergePolyGroups(LMesh, &_MergePolyGroups);if(LNumOfPolyGroupsRemovedT)
										{
											E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, TRUE, TRUE);
											
											LGeoUpdateFlags |= E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateTRIANGULATE|E3dUpdateVERTEX_NORMAL);
											LNumOfPolyGroupsRemoved += LNumOfPolyGroupsRemovedT;
										}
									}

									E3dProgressIndicator_SetMessage("MergePG test done");

									E3dProgressIndicator_Set((float)LClCnt / (float)LNSelectedGeometries, _CB_Abort, NULL);

									if(!_GoOn) break;

									EOps  LOps;EOps_Init(&LOps);

									if(_RemoveFloatingEdges)
									{
										E3dProgressIndicator_SetMessage("Looking for 'floating' Edges");XeApp_Sync(Xe_App);

										const EIndex  LER = _MeshRemoveFloatingEdges(&LOps, LMesh);if(LER)
										{
											LNumOfEdgesRemoved += LER;

											E3dProgressIndicator_SetMessage("Removed floating Edges: %lld", (long long)LER);
										}
									}

									E3dProgressIndicator_SetMessage("Looking for unused points");XeApp_Sync(Xe_App);

									if(_RemoveUnusedPoints)
									{
										const EIndex  LVR = E3dMesh_RemoveUnusedVertices(&LOps, LMesh);if(LVR > 0)
										{
											LNumOfPointsRemoved += LVR;
											LChanged |= E3dChgMeshSHAPE;

											LGeoUpdateFlags |= E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES);

											E3dProgressIndicator_SetMessage("Removed unused Vertices: %lld", (long long)LVR);
										}
									}

// The Operators were called already. Do the post processing.
//
									if(LOps.OpNodes.Items) E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);

									if(LOps.Operators.Count)
									{
										E3dpUndoStack_AddOps(&LOps, E3dLng_Mesh_cleanup);  // Add OperatorList to undo stack
									}

									EOps_FreeContents(&LOps);


									if(_WeldPoints && LMeshVN > 1)
									{
										EIndex*  LRemappingTable;ENewN(LMeshVN, LRemappingTable);  // Tells which new Vertex maps to an given old Vertex

										const E3dCoordinate  LMinDSquare = _MinDistance * _MinDistance;

										E3dProgressIndicator_SetMessage("Welding Vertices");XeApp_Sync(Xe_App);

// Initialize LRemappingTable. -1 means that this Vertex was not evaluated yet
//
										EIndex  LVI = 0;const EIndex  LVN = LMesh->Vertices.Count;for(;LVI < LVN;LVI++) LRemappingTable[LVI] = -1;

										const EIndex  LNumOfOldVertices = LMesh->Vertices.Count;
										EIndex  LNumOfNewVertices = LMesh->Vertices.Count;

										if(_MeshWeldPoints(LMesh, &LNumOfNewVertices, LRemappingTable, LMinDSquare))
										{
											LNumOfPointsRemoved += (LNumOfOldVertices - LNumOfNewVertices);

											E3dMesh_EdgesAndPolyGroupsRemapVertexIndices(LMesh, LRemappingTable);

											LGeoUpdateFlags |= E3dUpdateALL & ~E3dUpdateVERTEX_NORMAL;

// If we welded points, we must fix/remove bad Polygons
//
											LFixPolygons = LRemoveBadPolygons = TRUE;
										}

										EFreeChk(LRemappingTable);
									}


									if(_UnweldPoints)
									{
										const EIndex  LV = _MeshUnweldPoints(LMesh);if(LV)
										{
											LNumOfPointsAdded += LV;
											LGeoUpdateFlags |= E3dUpdateALL & ~E3dUpdateVERTEX_NORMAL;
										}
									}


									if(LFixPolygons || _FixPolygons)
									{
										EIndex  LPGC = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGC < LPGN;LPGC++)
										{
											E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGC];
											{
												EIndex	LI = 0;const EIndex  LN = LPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LI < LN;LI++, LPolygon++)
												{
													if(_FixPolygon(LPolyGroup, LPolygon)) LGeoUpdateFlags |= E3dUpdateALL & ~E3dUpdateVERTEX_NORMAL;
												}
											}
										}
									}

									if(LRemoveBadPolygons || _RemoveBadPolygons) LNumOfBadPolysRemoved += _MeshRemoveBadPolygons(LMesh);

									if(!_GoOn) break;

									if(_RemoveDuplicatePolygons) LNumOfDuplicatePolysRemoved += _MeshRemoveDuplicatePolygons(LMesh);

									if(LGeoUpdateFlags)
									{
//{ char  s[4096];printf("%s() %s:%d  [%s]\n",  __FUNCTION__, __FILE__, __LINE__,  E3dUpdateFlags_ToString(LGeoUpdateFlags, s));fflush(stdout); }
										E3dProgressIndicator_SetMessage("Updating Mesh for drawing");XeApp_Sync(Xe_App);

										E3dObject_UpdateForDisplay(LObject,  LGeoUpdateFlags, E3dCallALL,  E3d_Scene);
									}
								}
							}

							LClCnt++;

//printf("Cleanup %d\n", LClCnt);fflush(stdout);
							E3dProgressIndicator_Set((float)LClCnt / (float)LNSelectedGeometries, _CB_Abort, NULL);
						}

						EList_ObjectsFreeA(LObjects);
					}
				}
			}
		}

		E3dProgressIndicator_Set(-1.0, NULL, NULL);


		{
			char   LMessage[4096] = "";
			char   LSpacing[128] = "";
			EBool  LAddedStuff = FALSE, LRemovedStuff = FALSE;

			if(LNumOfPointsAdded)
			{
				const char*  LNoun = LNumOfPointsAdded > 1 ? "Points" : "Point";
				char   LTmpStr[128] = "";
				if(LAddedStuff) sprintf(LTmpStr, ", %lld %s", (long long)LNumOfPointsAdded, LNoun);
				else { sprintf(LTmpStr, "Added %lld %s", (long long)LNumOfPointsAdded, LNoun);strcpy(LSpacing, "   ");LAddedStuff = TRUE; }
				strcat(LMessage, LTmpStr);
			}

			if(LNumOfGeometriesRemoved)
			{
				const char*  LNoun = LNumOfGeometriesRemoved > 1 ? "Geometries" : "Geometry";
				char         LTmpStr[128 + 128] = "";sprintf(LTmpStr, "%sRemoved %lld %s", LSpacing, (long long)LNumOfGeometriesRemoved, LNoun);strcpy(LSpacing, "   ");LRemovedStuff = TRUE;

				strcat(LMessage, LTmpStr);
				LChanged |= E3dChgSCHEMATICS;
			}

			if(LNumOfPolyGroupsRemoved)
			{
				const char*  LNoun = LNumOfPolyGroupsRemoved > 1 ? "PolyGroups" : "PolyGroup";
				char         LTmpStr[128 + 128] = "";

				if(LRemovedStuff) sprintf(LTmpStr, ", %lld %s", (long long)LNumOfPolyGroupsRemoved, LNoun);
				else { sprintf(LTmpStr, "%sRemoved %lld %s", LSpacing, (long long)LNumOfPolyGroupsRemoved, LNoun);strcpy(LSpacing, "   ");LRemovedStuff = TRUE; }
				strcat(LMessage, LTmpStr);
				LChanged |= E3dChgSCHEMATICS;
			}

			if((LNumOfBadPolysRemoved + LNumOfDuplicatePolysRemoved))
			{
				const char*  LNoun = (LNumOfBadPolysRemoved + LNumOfDuplicatePolysRemoved) > 1 ? "Polygons" : "Polygon";

				if(LRemovedStuff) strcat(LMessage, ", ");
				else LRemovedStuff = TRUE;

				char   LTmpStr[128 + 128] = "";sprintf(LTmpStr, "%sRemoved %lld %s (", LSpacing, (long long)(LNumOfBadPolysRemoved + LNumOfDuplicatePolysRemoved), LNoun);strcat(LMessage, LTmpStr);

				if(LNumOfBadPolysRemoved) { sprintf(LTmpStr, "%lld bad)", (long long)LNumOfBadPolysRemoved);strcat(LMessage, LTmpStr); }
				if(LNumOfDuplicatePolysRemoved)
				{
					if(LNumOfBadPolysRemoved) strcat(LMessage, ", ");
					sprintf(LTmpStr, "%lld duplicate(s)", (long long)LNumOfDuplicatePolysRemoved);strcat(LMessage, LTmpStr);
				}
			}

			if(LNumOfPointsRemoved)
			{
				const char*  LNoun = LNumOfPointsRemoved > 1 ? "Points" : "Point";
				char         LTmpStr[128 + 128] = "";

				if(LRemovedStuff) sprintf(LTmpStr, ", %lld %s", (long long)LNumOfPointsRemoved, LNoun);
				else { sprintf(LTmpStr, "%sRemoved %lld %s", LSpacing, (long long)LNumOfPointsRemoved, LNoun);strcpy(LSpacing, "   ");LRemovedStuff = TRUE; }
				strcat(LMessage, LTmpStr);
			}

			if(LNumOfEdgesRemoved)
			{
				const char*  LNoun = LNumOfEdgesRemoved > 1 ? "Edges" : "Edge";
				char         LTmpStr[128 + 128] = "";

				if(LRemovedStuff) sprintf(LTmpStr, ", %lld %s", (long long)LNumOfEdgesRemoved, LNoun);
				else { sprintf(LTmpStr, "%sRemoved %lld %s", LSpacing, (long long)LNumOfEdgesRemoved, LNoun);strcpy(LSpacing, "   ");LRemovedStuff = TRUE; }
				strcat(LMessage, LTmpStr);
			}

			if(LAddedStuff || LRemovedStuff)
			{
				E3dp_PrintMessage(ELogMESSAGE, 15000, LMessage);
			}
			else E3dp_PrintMessage(ELogMESSAGE, 15000, "Nothing was added or removed");
		}
	}

	if(LChanged & E3dChgSCHEMATICS) E3dScene_LayoutOnSchematics(PScene, TRUE);

	return(LChanged);
}


//========================================
// Cleanup dialog callback
//========================================
static void _DCB_OK(XeWidget PW, EPointer XeClientData, EPointer PCallData)
{
	const EChangeFlags  LChanged = _Cleanup(E3d_Scene);
	XeWidget_Unmap(_DialogW);
	E3dWindows_RedrawByChangeFlags(E3d_Scene, LChanged);
	EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
}


//========================================
// Cleanup dialog callback
//========================================
static void _DCB_CleanupClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeWidget_Unmap(_DialogW);
		EPluginLock_Del(_Plugin, &_DialogLock, FALSE);
	}
}


//========================================
// Cleanup dialog callback
//========================================
static void _DCB_Cleanup(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_REMOVE_EMPTY_GEOMETRIES:
			_RemoveEmptyGeometries = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_REMOVE_EMPTY_POLYGROUPS:
			_RemoveEmptyPolyGroups = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_REMOVE_FLOATING_EDGES:
			_RemoveFloatingEdges = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_REMOVE_UNUSED_POINTS:
			_RemoveUnusedPoints = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_WELD_POINTS:
			_WeldPoints = ((XeToggleButtonCallbackStruct*)PCallData)->Set;if(_WeldPoints)
			{
				XeChangeManage  LChildren[2];XeChangeManage*  LChild = LChildren;
//				LChild->W = _FixPolygonsBW;LChild->Manage = FALSE;LChild++;
				Xe_SetValuesVa(_FixPolygonsBW, XeNSensitive, FALSE, NULL);
				XeToggleButton_SetState(_FixPolygonsBW, TRUE, FALSE);

//				LChild->W = _RemoveBadPolygonsBW;LChild->Manage = FALSE;LChild++;
				Xe_SetValuesVa(_RemoveBadPolygonsBW, XeNSensitive, FALSE, NULL);
				XeToggleButton_SetState(_RemoveBadPolygonsBW, TRUE, FALSE);

				LChild->W = _WeldOnlySelectedPointsW;LChild->Manage = TRUE;LChild++;
				if(LChild - LChildren) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}
			else
			{
				XeChangeManage  LChildren[2];XeChangeManage*  LChild = LChildren;

//				LChild->W = _FixPolygonsBW;LChild->Manage = TRUE;LChild++;
				Xe_SetValuesVa(_FixPolygonsBW, XeNSensitive, TRUE, NULL);
				XeToggleButton_SetState(_FixPolygonsBW, _FixPolygons, FALSE);

//				LChild->W = _RemoveBadPolygonsBW;LChild->Manage = FALSE;LChild++;
				Xe_SetValuesVa(_RemoveBadPolygonsBW, XeNSensitive, TRUE, NULL);
				XeToggleButton_SetState(_RemoveBadPolygonsBW, _RemoveBadPolygons, FALSE);

				LChild->W = _WeldOnlySelectedPointsW;LChild->Manage = FALSE;LChild++;
				if(LChild - LChildren) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}
		break;

		case CR_UNWELD_VERTICES:
			_UnweldPoints = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_WELD_ONLY_SELECTED_POINTS_TO_UNSELECTED_POINTS:
			_WeldOnlySelectedPoints = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_FIX_POLYGONS:
			_FixPolygons = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_REMOVE_BAD_POLYGONS:
			_RemoveBadPolygons = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_REMOVE_DUPLICATE_POLYGONS:
			_RemoveDuplicatePolygons = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		break;

		case CR_MINDISTANCE:
			_MinDistance = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			XeToggleButton_SetState(_WeldPointsW, TRUE, TRUE);
		break;
	}
}


static E3dMergePolyGroupsGUI  _MergePolyGroupsGUI;

static void _DCB_MergePolyGroups(EPointer PClientData, E3dMergePolyGroupsCallbackStruct* PCBS)
{
	E3dMergePolyGroups*     LMPG = &_MergePolyGroups;
	E3dMergePolyGroupsGUI*  LMPGGUI = &_MergePolyGroupsGUI;

	switch(PCBS->Reason)
	{
		case E3dMpgON:
		case E3dMpgOFF:
			{
				const EBool  LActive = PCBS->Reason == E3dMpgON ? TRUE : FALSE;
				LMPG->Active = LActive;

				{
					XeChangeManage  LChildren[32];XeChangeManage*  LChild = LChildren;
					E3dMergePolyGroups_GUIOnOff(LMPG, LMPGGUI);
/*
					LChild->W = LGUI->LayoutST0RadioMatrixW;LChild->Manage = LActive;LChild++;
					LChild->W = LGUI->LayoutST1RadioMatrixW;LChild->Manage = LActive;LChild++;
					LChild->W = LGUI->LayoutUVRadioMatrixW; LChild->Manage = LActive;LChild++;
*/
					if(LChild - LChildren) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
				}
			}
		break;

		case E3dMpgLAYOUT:
			{
				E3dMergePolyGroupsLayoutComponent*  LComponent = (E3dMergePolyGroupsLayoutComponent*)((uint8_t*)LMPG + PCBS->FieldOffset);
				LComponent->Mode = PCBS->Value.i32;
				E3dMergePolyGroups_GUIUpdate(LMPGGUI, LMPG, TRUE);
			}
		break;

		case E3dMpgSPACING:
			{
				E3dMergePolyGroupsLayoutComponent*  LComponent = (E3dMergePolyGroupsLayoutComponent*)((uint8_t*)LMPG + PCBS->FieldOffset);
				LComponent->Spacing = PCBS->Value.f32;
				E3dMergePolyGroups_GUIUpdate(LMPGGUI, LMPG, TRUE);
			}
		break;
	}
}


//========================================
// Pop up the dialog box
//========================================
static void _CleanupPopupDialog(void)
{
	if(_DialogLock == NULL) _DialogLock = EPluginLock_Add(_Plugin, "GeometryCleanup", _DialogLockDel);

	if(_DialogW == NULL)
	{
		XeWidget  LIndentWs[7];int  LIndentN;  // For indenting the ScaleWidgets
		EArg      Args[256];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNMinSizeX, 240);EXeSetArg(XeNMinSizeY, 300);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Geometry clean-up v0.6.2");EXeSetArg(XeNIconName, "Cleanup");

		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Cleanup", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;
		XeAddCallback(LDialogW, XeNWMCallback, _DCB_CleanupClose, (EPointer)0);

		_DialogW = LDialogW;

		XeWidget  LMessageBoxW = XeDialog_GetChild(LDialogW, XeDIALOG_MAIN);
		EXeStart;
		EXeSetArg(XeNWorkAreaMarginSizeX, 6);
		EXeSetArg(XeNWorkAreaMarginSizeY, 6);
		Xe_SetValues(LMessageBoxW,  Args, ArgCnt);

		{ XeWidget  LW = XeDialog_AddButton("Do it", XePushButton_class, LMessageBoxW, TRUE);XeAddCallback(LW, XeNActivateCallback, _DCB_OK, (EPointer)XeCR_OK); }

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);
		EXeStart;
		EXeSetArg(XeNSpacingY, 0);
		EXeSetArg(XeNOrientation, XeVERTICAL);
		Xe_SetValues(LTopMatrixW,  Args, ArgCnt);


		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _RemoveEmptyGeometries);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Remove Meshes with no PolyGroups, Splines with no CVs etc.");
		XeWidget  LW = XeWidget_ManagedNew("Remove empty Geometries", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_EMPTY_GEOMETRIES);

		EXeStart;
		EXeSetArg(XeNMarginTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _RemoveEmptyPolyGroups);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Remove PolyGroups with no Polygons or Triangle-Strips");
		LW = XeWidget_ManagedNew("Remove empty PolyGroups", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_EMPTY_POLYGROUPS);

		EXeStart;
		EXeSetArg(XeNSpacingTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
//  EXeSetArg(XeNSpacingLeft, 3);EXeSetArg(XeNSpacingRight, 3);
		EXeSetArg(XeNSet, _RemoveFloatingEdges);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Remove Mesh Edges that are not formed by Polygons.");
		LW = XeWidget_ManagedNew("Remove floating Edges", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_FLOATING_EDGES);

		EXeStart;
		EXeSetArg(XeNSpacingTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
//  EXeSetArg(XeNSpacingLeft, 3);EXeSetArg(XeNSpacingRight, 3);
		EXeSetArg(XeNSet, _RemoveUnusedPoints);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Remove unused Vertices, Spline CVs etc.");
		LW = XeWidget_ManagedNew("Remove unused points", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_UNUSED_POINTS);

		E3dMergePolyGroups_GUICreate(&_MergePolyGroupsGUI, LTopMatrixW, 6,  &_MergePolyGroups,  _DCB_MergePolyGroups, NULL);

		EXeStart;
		EXeSetArg(XeNMarginTop, 5);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _WeldPoints);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Merge (\"weld\") Vertices that are closer than a given distance");
		_WeldPointsW = LW = XeWidget_ManagedNew("Merge Vertices closer than", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_WELD_POINTS);

		LIndentN = 0;
		EXeStart;
		EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 3);
		EXeSetArg(XeNScaleMaximum, 1000);
		EXeSetArg(XeNResizableY, FALSE);
		LIndentWs[LIndentN++] = LW = XeScale_New_Coord(LTopMatrixW, "",  Args, ArgCnt);
		XeScale_SetValueAndRanges_Coord(LW,  _MinDistance,  0.0, 100.0,  0.0, 1.0,  FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_MINDISTANCE);

		EXeStart;
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _FixPolygons);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Only merge selected points to unselected ones\n(don't move unselected points and don't merge selected ones with each-other).");

		if(_WeldPoints)
		{
			LW = _WeldOnlySelectedPointsW = XeWidget_ManagedNew("Only selected to unselected", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		}
		else LW = _WeldOnlySelectedPointsW = XeWidget_New("Only selected to unselected", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_WELD_ONLY_SELECTED_POINTS_TO_UNSELECTED_POINTS);




		EXeStart;
//	EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
		EXeSetArg(XeNMarginTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _FixPolygons);
		if(_WeldPoints) { EXeSetArg(XeNSensitive, FALSE); }
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Merge Vertex-Nodes (of Polygons) with the same Vertex index");
		LW = _FixPolygonsBW = XeWidget_ManagedNew("Fix Polygons", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_FIX_POLYGONS);


		EXeStart;
		EXeSetArg(XeNMarginTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _RemoveBadPolygons);
		if(_WeldPoints) { EXeSetArg(XeNSensitive, FALSE); }
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Remove Polygons that have 0 surface area, intersecting edges etc.");
		_RemoveBadPolygonsBW = LW = XeWidget_ManagedNew("Remove bad Polygons", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_BAD_POLYGONS);


		EXeStart;
		EXeSetArg(XeNMarginTop, 3);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _RemoveBadPolygons);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8(
"Remove Polygons within Polygroups that use the same Vertices.\nFor example:\n\
 if, in a PolyGroup, Polygon #5 and #12 are both defined by Vertices 5,6,7,1 and 12,\n\
 Polygon #12 is redundant and will get removed by this tool.");
		LW = XeWidget_ManagedNew("Remove duplicate Polygons", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_REMOVE_DUPLICATE_POLYGONS);

		EXeStart;
		EXeSetArg(XeNMarginTop, 5);
		EXeSetArg(XeNShadowThickness, 0);
		EXeSetArg(XeNHighlightThickness, 0);
		EXeSetArg(XeNSet, _UnweldPoints);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8("Split Mesh Vertices if their corresponding Polygon\nVertex-normals, texture coordinates etc. are different");
		LW = _UnweldPointsBW = XeWidget_ManagedNew("Split Vertices", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_Cleanup, (EPointer)CR_UNWELD_VERTICES);

		XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 0);

		XeShell_Raise(_DialogW);
		XeShell_CenterToWidget(_DialogW, E3dp_TopShellW);
	}
	else XeShell_Raise(_DialogW);
}


//========================================
// Menu callback
//========================================
static void _MCB_Cleanup(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(!_Initialized)
	{
		E3dMergePolyGroups_Init(&_MergePolyGroups);
		_Initialized = TRUE;
	}

	_CleanupPopupDialog();
/*
	if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) _CleanupPopupDialog();
	else _Cleanup(E3d_Scene);
*/
}


static EScriptFunction*  _SelectIntermediatePoints = NULL;
static EScriptFunction*  _DisconnectSelectedPoints = NULL;
static EScriptFunction*  _HideSelectedPoints = NULL;
static EScriptFunction*  _Unselect_Points = NULL;

static void _MCB_DecimateEdges(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(_SelectIntermediatePoints == NULL) _SelectIntermediatePoints = E3dScriptFunction_ByName("SelectIntermediatePoints");
	if(_DisconnectSelectedPoints == NULL) _DisconnectSelectedPoints = E3dScriptFunction_ByName("DisconnectSelectedPoints");
	if(_HideSelectedPoints == NULL) _HideSelectedPoints = E3dScriptFunction_ByName("HideSelectedPoints");
	if(_Unselect_Points == NULL) _Unselect_Points = E3dScriptFunction_ByName("Unselect_Points");

//	if(_SelectIntermediatePoints)
//	if(_SelectIntermediatePoints && _DisconnectSelectedPoints)
//	if(_SelectIntermediatePoints && _DisconnectSelectedPoints && _HideSelectedPoints)
	if(_SelectIntermediatePoints && _DisconnectSelectedPoints && _HideSelectedPoints && _Unselect_Points)
	{
printf("%s() %s:%d  %sGREEN%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);
		EChangeFlags  LSceneChangeFlags = 0;

		E3dScene*  LScene = E3d_Scene;

		LSceneChangeFlags |= _SelectIntermediatePoints->Func(NULL);
		LSceneChangeFlags |= _DisconnectSelectedPoints->Func(NULL);
		LSceneChangeFlags |= _HideSelectedPoints->Func(NULL);
		LSceneChangeFlags |= _Unselect_Points->Func(NULL);

		if(LSceneChangeFlags & E3dChgHIERARCHY)
		{
			E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(LScene, TRUE);
		}

		if(LSceneChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LSceneChangeFlags);

		if(LSceneChangeFlags & E3dChgHIERARCHY) E3dTransformPanel_Update(NULL, E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
		E3dp_PrintMessage(ELogMESSAGE, 5000, "DecimateEdges succeeded");
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "DecimateEdges failed. Script function(s) not registered.");
}


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

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Effect", "Cleanup", '\0', NULL, NULL, FALSE, NULL, _MCB_Cleanup, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Effect", "Del Edges + remove splitter verts", '\0', NULL, NULL, FALSE, NULL, _MCB_DecimateEdges, (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 < _MenuButtons_Count;LI++) { XeItemFree(_MenuButtons[LI]); } _MenuButtons_Count = 0;
	}

	return(E_SUCCESS);
}
