//========================================================================================================================
// PolyGroups Plugin for EQUINOX-3D
//
// - Create / edit Polygons
// - Separate Polygons to Mesh:   move selected Polygons into new PolyGroups and move those PolyGroups into a new Mesh
// - Separate Polygons:           move selected Polygons into a new PolyGroup
// - Merge selected Polygons:     collect selected Polygons into a PolyGroup
// - Separate PolyGroups:         move selected PolyGroups into a new Mesh
// - Fix Polygon directions       make Polygon directions (winding orders) consistent within a Mesh
// - Fill selected holes:         create Polygons for selected Edge loops
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 20:18:07
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//========================================================================================================================
// ELNG
//
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>    // For qsort()

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

#include <E3D/E3D.h>

#ifndef _E3D_Matrix_h
#include <E3D/Matrix.h>
#endif

#ifndef _E3D_PolygonEditing_h
#include <E3D/PolygonEditing.h>
#endif

#include <E3D/Mesh.h>

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

#include <E3D/UI/ModeStack.h>  // E3dFIXME : For E3dp_ResetWorkModes, temporarily
#include <E3D/UI/Panel.h>
#include <E3D/UI/StatusPanel.h>

#include <Xe/PushButton.h>

char*  E_PluginInfo = "Various PolyGroup tools, including:\n- Creating and editing Polygons\n- Automatinc fixing of inconsistent Polygon directions\n- Separate Polygons to a new Mesh\n- Separate Polygons into a new PolyGroup\n- Separate PolyGroups into a new Mesh\n- Merge Selected Polygons\nAnd more.";


static EPlugin*       _Plugin = NULL;



#define E3dAddEdgeUNDOABLE               1
#define E3dPolygonAddVertexNodeUNDOABLE  1
#define E3dADDVERTEX_UNDOABLE            1

static const EBool  _CheckForCreatingInvertedPolygon = FALSE;

static EBool        _SeparatePolygons_CopyMaterials = FALSE;



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

static E3dMesh*       _AddPolygon_Mesh = NULL;
static E3dPolyGroup*  _AddPolygon_PolyGroup = NULL;
static EIndex         _AddPolygon_PolygonIndex = -1;
static E3dPolygon     _AddPolygon_TmpPolygon;		// Until a newly created Polygon has at least 3 Vertices, we don't add it to a PolyGroup, but instead use this temporary struct

static E3dMesh*       _AddEdge_Mesh = NULL;
static EIndex         _AddEdge_StartVertexIndex = -1;

const EString  Lng_NewEdgeInvPoly   = "This would create an inverted Polygon",
               Lng_NewEdge0AreaPoly = "This would create an 0-area Polygon",
               Lng_NewEdgeIntersect = "New Edge would intersect other Edge(s) in the Polygon",
               Lng_SeparatePolygons_OnlyOnePolygon = "PolyGroup %s has only one Polygon",
               Lng_SeparatePolygons_MovedPolygons  = "Moved %d Polygon(s) to a new PolyGroup",
               Lng_SeparatePolygons_SelectPolygons = "Select Polygons to move to a new PolyGroup",
			         Lng_Adding_Edge                     = "Adding Edge   ",
               Lng_pick_Edge_end_Vertex            = ": pick Edge end Vertex    ",
               Lng_cancel_Edge                     = ": cancel Edge    ";


static XeString  _CreatePolygonMessage = NULL, _EditPolygonMessage = NULL;


enum
{
	CR_CREATE_POLYGONS = XeCR_CUSTOM0,
	CR_EDIT_POLYGONS
};


enum
{
	EditModeADD_EDGE = 1,
	EditModeADD_VERTEX_ON_EDGE,
	EditModeADD_VERTEX_ON_EDGE_DELETE_EDGE,
	EditModeADD_VERTEX_ON_EDGE_DELETE_HOLE,
	EditModeCREATE_POLYGONS,
	EditModeEDIT_POLYGONS
};


//========================================================
// Create compound message string for the given mode
//========================================================
static XeString _GetMessage(const int PType, const int PComponentIndex)
{
	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color      = Xe_ForegroundRGBA8pColor;
	const uint32_t    LButtonNameRGBAp8Color = LPrefs->ButtonNameRGBAp8Color;

	switch(PType)
	{
		case EditModeADD_EDGE:
		return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, Lng_Adding_Edge,
		   E3dp_LMouseButtonS,  XeTEXT, Lng_pick_Edge_end_Vertex,
		   E3dp_RMouseButtonS,  XeTEXT, Lng_cancel_Edge,
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));

		case EditModeADD_VERTEX_ON_EDGE:
		return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,       XeFTEXT,"Edge #%d   ", PComponentIndex,
		   E3dp_LMouseButtonS,  XeTEXT,  ": add Vertex on Edge    ",
		   E3dp_MMouseButtonS,  XeTEXT,  ": add Vertex on middle of Edge    ",
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));

		case EditModeADD_VERTEX_ON_EDGE_DELETE_EDGE:
			return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,       XeFTEXT, "Edge #%d   ", PComponentIndex,
		   E3dp_LMouseButtonS,  XeTEXT, ": add Vertex on Edge    ",
		   E3dp_MMouseButtonS,  XeTEXT, ": add Vertex on middle of Edge    ",
		   E3dp_RMouseButtonS,  XeTEXT, ": delete Edge    ",
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));

		case EditModeADD_VERTEX_ON_EDGE_DELETE_HOLE:
			return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,       XeFTEXT, "Edge #%d   ", PComponentIndex,
		   E3dp_LMouseButtonS,  XeTEXT, ": add Vertex on Edge    ",
		   E3dp_MMouseButtonS,  XeTEXT, ": add Vertex on middle of Edge    ",
		   E3dp_RMouseButtonS,  XeTEXT, ": delete hole    ",
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));

		case EditModeCREATE_POLYGONS:
			return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Create Polygons. Move pointer over a Vertex or an Edge  ",
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT,XeFontDEFAULT,      XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));

		case EditModeEDIT_POLYGONS:
			return(XeString_NewVa(TRUE,
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Edit Polygons. Move pointer over a Vertex or an Edge  ",
		   XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
		   XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
		   0));
	}

	return(NULL);
}


//extern EBool  _PolygonAdvVerbose;


//================================================================
// Move selected Polygons of a Mesh, to separate PolyGroups
//================================================================
static EIndex _MeshSeparateSelectedPolygons(E3dMesh* PMesh,  EIndex** PNewPolyGroupIndicesP, EIndex* PNewPolyGroupsNP,  EOps* POps, E3dOperatorEditMesh** PAddPolyGroupsOperatorP, EBool* PMessagePrintedP)
{
	if(PMesh->PolyGroups.Count == 0) return(E_NO_DATA);

	EIndex  LNPolysMoved = 0;

	E3dOperatorEditMesh*  LEditPolyGroupsOperator = NULL;
	E3dOperatorEditMesh*  LAddPolyGroupsOperator = NULL;

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

// We'll create a new PolyGroup for each source PolyGroup, because the VertexNode struct might be different on the source PolyGroups.
//
		if((LPolyGroup->Selected || PMesh->Selection == E3dObjectSelOBJECT) && LPolyGroup->Polygons.Count > 0)
		{
			const EIndex  LNSelectedPolygons = E3dPolyGroup_NumOfSelectedPolygons(LPolyGroup);

			if(LNSelectedPolygons > 0 && LNSelectedPolygons < LPolyGroup->Polygons.Count)
			{
				if(LPolyGroup->Polygons.Count > 1)
				{
					const EIndex  LNUnselectedPolys = LPolyGroup->Polygons.Count - LNSelectedPolygons;

					if(PNewPolyGroupIndicesP) ELst_IndexAdd(PNewPolyGroupIndicesP, PNewPolyGroupsNP, LNewPGIndex);

					E3dPolyGroup*  LNewPolyGroup;E_New(E3dPolyGroup, LNewPolyGroup);

					if(POps)
					{
						if(LAddPolyGroupsOperator == NULL) LAddPolyGroupsOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);

						E3dOpPolyGroup*  LOpPolyGroup = E3d_EditMeshAddOpPolyGroup(LAddPolyGroupsOperator,  LNewPolyGroup, LNewPGIndex, EEditADD);if(LOpPolyGroup)
						{
						}
					}
					else
					{
						E3dMesh_AppendPolyGroup(PMesh, LNewPolyGroup);
					}

					LNewPGIndex++;

					{
						const int  LL = LPolyGroup->Name ? (int)strlen(LPolyGroup->Name) : 0;
						char*      LName = EMalloc(4 + LL + 1 + 19);
						if(LPolyGroup->Name) sprintf(LName, "new-%s", LPolyGroup->Name);
						else sprintf(LName, "new-%lld", (long long)LPGI);
						EObject_SetName(LNewPolyGroup, LName, FALSE);
					}

					if(_SeparatePolygons_CopyMaterials) E_AssignToMember(E3dScene_MaterialCopy(E3d_Scene, NULL, LPolyGroup->Material, FALSE), LNewPolyGroup,Material);
					else E_AssignToMember(LPolyGroup->Material, LNewPolyGroup,Material);

					E3dPolyGroup_CopyCore(LPolyGroup, LNewPolyGroup);

					E3dPolyGroup_TextureMappersCopy(LPolyGroup, LNewPolyGroup);

					if(POps == NULL) E3dPolyGroup_TextureMappingUpdate((E3dVertexGeometry*)PMesh, LPolyGroup,  TRUE);  // Update texture mapping on the old PolyGroup

					const E3dVertexNodeSize  LVertexNodeSize = LNewPolyGroup->VertexNodeSize;

					{
						LNewPolyGroup->Polygons.Items = E3dPolygons_New(LNSelectedPolygons);
						LNewPolyGroup->Polygons.Count = LNewPolyGroup->Polygons.CountAllocated = LNSelectedPolygons;
						EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;E3dPolygon*  LDPolygon = LNewPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++)
						{
							if(LPolygon->Flags & E3dPolygonSELECTED)
							{
// If we have a POps, that means we're storing the operation for undo, so we need to deep-copy the affected Polygons
//
								if(POps) E3d_PolygonCopy(LPolygon, LVertexNodeSize, LDPolygon);
								else memcpy(LDPolygon, LPolygon, sizeof(E3dPolygon));

								LDPolygon++;
								LNPolysMoved++;
							}
						}
					}

					if(POps)
					{
// Add E3dOperatorEditMesh for removing the selected Polygons
//
						EIndex*  LSelectedPolygon_Indices = NULL;const EIndex  LNumOfSelectedPolygonsT = E3dPolyGroup_GetMarkedPolygonIndices(LPolyGroup, E3dPolygonSELECTED, &LSelectedPolygon_Indices);if(LNSelectedPolygons)
						{
							if(LEditPolyGroupsOperator == NULL)
							{
								LEditPolyGroupsOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
								E_AssignToMember(PMesh, LEditPolyGroupsOperator,MeshNewT);
							}

							E3dOpPolyGroup*  LOpPolyGroup = E3d_EditMeshAddOpPolyGroup(LEditPolyGroupsOperator,  LPolyGroup, LPGI, EEditMODIFY);

							LOpPolyGroup->PolygonsToDelete_Indices = LSelectedPolygon_Indices;LOpPolyGroup->PolygonsToDelete_Count = LNumOfSelectedPolygonsT;
						}
					}
					else
					{
// Remove the selected Polygons from the original PolyGroup by copying the unselected ones into a new array and replacing the old array with this new one.
//
//printf("%s() %s:%d Removing Polys from <<%s>>\n", __FUNCTION__, __FILE__, __LINE__, LPolyGroup->Name);fflush(stdout);
						E3dPolygon*  LPolygons = E3dPolygons_New(LNUnselectedPolys);if(LPolygons)
						{
							EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;E3dPolygon*  LDPolygon = LPolygons;for(;LPI < LPN;LPI++, LPolygon++)
							{
								if((LPolygon->Flags & E3dPolygonSELECTED) == 0)
								{
									memcpy(LDPolygon, LPolygon, sizeof(E3dPolygon));
									LDPolygon++;
								}
//							else { printf(" %p\n", LPolygon->VertexNodes);fflush(stdout); }
							}
							EFree(LPolyGroup->Polygons.Items);

							LPolyGroup->Polygons.Items = LPolygons;
							LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LNUnselectedPolys;
						}
					}
				}
				else
				{
					E3dp_PrintMessage(ELogMESSAGE, 3000, Lng_SeparatePolygons_OnlyOnePolygon, LPolyGroup->Name);
					*PMessagePrintedP = TRUE;
				}
			}
		}
	}

	if(LEditPolyGroupsOperator) EList_ObjectAddA_s(POps->Operators, 1, LEditPolyGroupsOperator);
	if(LAddPolyGroupsOperator) EList_ObjectAddA_s(POps->Operators, 1, LAddPolyGroupsOperator);

	if(PAddPolyGroupsOperatorP) *PAddPolyGroupsOperatorP = LAddPolyGroupsOperator;

	return(LNPolysMoved);
}


//========================================
// Menu callback
//========================================
static void _MCB_SeparateSelectedPolygons(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
E3d_Verbosity |= E3dVbTEXTURES|E3dVbTEXTUREMAPPING;
	E3dScene*  LScene = E3d_Scene;
	EIndex     LNPolysMoved = 0;
	EBool      LMessagePrinted = FALSE;

	{
		EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
		{
//		EIndex  LObjI = 0;for(;LObjI < LNObjects;LObjI++)

// Only process the first Mesh
//
			const EIndex  LObjI = 0;

			{
				E3dMesh*  LMesh = (E3dMesh*)LObjects.Items[LObjI];

				if(LMesh->PolyGroups.Count > 0)
				{
					EListI(, EIndex, LNewPolyGroupIndices);

					EOps  LOps;EOps_Init(&LOps);

					E3dOperatorEditMesh*  LAddPolyGroupsOperator = NULL;

					const EIndex  LResult = _MeshSeparateSelectedPolygons(LMesh,  &LNewPolyGroupIndices, &LNewPolyGroupIndices_Count,  &LOps, &LAddPolyGroupsOperator,  &LMessagePrinted);if(LResult > 0)
					{
						LNPolysMoved += LResult;

						E_AssignToMember(LMesh, LAddPolyGroupsOperator,MeshNewT);

						EChangeFlags  LChanged = 0;(void)LChanged;

// Run Operators
//
						LChanged |= EOps_Run(&LOps, EOpDO);

						if(LOps.Operators.Items)
						{
							E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);
							E3dpUndoStack_AddOps(&LOps, E3dLng_Move_selected_Polygons_to_new_PolyGroups);    // Add OperatorList to undo stack
						}
					}

					EOps_FreeContents(&LOps);

					EList_FreeChk(LNewPolyGroupIndices);
				}
			}

			EList_FreeA_s(LObjects);
		}
	}

	if(LNPolysMoved)
	{
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
		E3dp_PrintMessage(ELogMESSAGE, 3000, Lng_SeparatePolygons_MovedPolygons, LNPolysMoved);
	}
	else if(!LMessagePrinted) E3dp_PrintMessage(ELogWARNING, 3000, Lng_SeparatePolygons_SelectPolygons);

E3d_Verbosity &= ~(E3dVbTEXTURES|E3dVbTEXTUREMAPPING);
}


//========================================
// Menu callback
//========================================
static void _MCB_SeparateSelectedPolygonsToNewMesh(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
E3d_Verbosity |= E3dVbTEXTURES|E3dVbTEXTUREMAPPING;
	E3dScene*  LScene = E3d_Scene;
	EIndex     LNPolysMoved = 0;
	EBool      LMessagePrinted = FALSE;

	{
		EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
		{
//			EIndex  LObjI = 0;for(;LObjI < LNObjects;LObjI++)

// Only process the first Mesh
//
			const EIndex  LObjI = 0;
			{
				E3dMesh*  LMesh = (E3dMesh*)LObjects.Items[LObjI];

				if(LMesh->PolyGroups.Count > 0)
				{
					EBool  LDoIt = TRUE;

// Check if we'll actually perform the operation
//
					{
						EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
						{
							E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];

							if((LPolyGroup->Selected || LMesh->Selection == E3dObjectSelOBJECT) && LPolyGroup->Polygons.Count > 0)
							{
								const EIndex  LNSelectedPolygons = E3dPolyGroup_NumOfSelectedPolygons(LPolyGroup);if(LNSelectedPolygons)
								{
									LDoIt = TRUE;break;
								}
							}
						}
					}

					if(LDoIt)
					{
						EListI(, EIndex, LNewPolyGroupIndices);

						EOps  LOps;EOps_Init(&LOps);

						E3dOperatorEditMesh*  LAddPolyGroupsOperator = NULL;

						const EIndex  LResult = _MeshSeparateSelectedPolygons(LMesh,  &LNewPolyGroupIndices, &LNewPolyGroupIndices_Count,  &LOps, &LAddPolyGroupsOperator, &LMessagePrinted);if(LResult > 0)
						{
							LNPolysMoved += LResult;

//printf("%s() %s:%d  <%s> %lld\n", __FUNCTION__, __FILE__, __LINE__, LMesh->Name, (long long)LNewPolyGroupIndices_Count);fflush(stdout);
							E3dModel*  LModel = E3dObject_GetSelectingModel((E3dObject*)LMesh, FALSE);if(LModel)
							{
								if(LMesh->PolyGroups.Count > 1)
								{
//printf("%s() %s:%d %s<%s>  %lld %p%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, LMesh->Name, (long long)LMesh->PolyGroups.Count, LMesh->WingedEdges, ECodeNormal);fflush(stdout);
									E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, E3dp_Prefs.OptimizeFor == EOptimizeSPEED, FALSE);
								}

								E3dMesh_SeparatePolyGroups(LMesh,  LNewPolyGroupIndices, LNewPolyGroupIndices_Count,  LModel, 0, FALSE, TRUE, NULL);

/*
								E3dOperatorEditObjects*  LAddMeshOperator = (E3dOperatorEditObjects*)EOperator_New(E3dOperatorEditObjects_class);
								E3dOpObject*  LOpObject = E3dEditObjects_AddOpObject(LAddMeshOperator, LNewMesh);
								LOpObject->Operation = EEditADD;
*/

								E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);

								if(LOps.Operators.Items)
								{
									E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);
									E3dpUndoStack_AddOps(&LOps, E3dLng_Move_selected_Polygons_to_new_Meshes);    // Add OperatorList to undo stack
								}
							}
						}

						EOps_FreeContents(&LOps);

						EList_FreeChk(LNewPolyGroupIndices);
					}
				}
			}

			EList_FreeA_s(LObjects);
		}
	}

	if(LNPolysMoved)
	{
		E3dScene_ObjectsUpdateModelList(LScene);
		E3dScene_LayoutOnSchematics(LScene, TRUE);

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);
		E3dp_PrintMessage(ELogMESSAGE, 3000, Lng_SeparatePolygons_MovedPolygons, LNPolysMoved);
	}
	else if(!LMessagePrinted) E3dp_PrintMessage(ELogWARNING, 3000, Lng_SeparatePolygons_SelectPolygons);

E3d_Verbosity &= ~(E3dVbTEXTURES|E3dVbTEXTUREMAPPING);
}


//========================================
// Menu callback
//========================================
static void _MCB_SeparateSelectedPolyGroups(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LNPolyGroupsMoved = 0;
	EChangeFlags  LChanged = 0;(void)LChanged;

	EOps  LOps;EOps_Init(&LOps);

	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNMeshes = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED|E3dVISIBLE, FALSE,  &LObjects);if(LNMeshes > 0)
	{
		E3dMesh*  LMesh = (E3dMesh*)LObjects.Items[0];

    if(LMesh->Selection == E3dObjectSelPOLYGROUP)
    {
			E3dModel*  LModel = E3dObject_GetSelectingModel((E3dObject*)LMesh, FALSE);

      E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
      LNPolyGroupsMoved += E3dMesh_SeparatePolyGroups(LMesh,  NULL, 0,  LModel, 0, FALSE, TRUE, LOperator);
      EList_ObjectAddA_s(LOps.Operators, 1, LOperator);
      LChanged |= LOperator->Class->Func(&LOps, (EOperator*)LOperator, EOpDO);  // Run operator

//		LNPolyGroupsMoved += E3dMesh_SeparatePolyGroups((E3dMesh*)LObject,  NULL, 0,  LModel, 0, FALSE, TRUE, NULL);
    }

    if(LOps.Operators.Items)
    {
      E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);
      E3dpUndoStack_AddOps(&LOps, E3dLng_Move_selected_Polygons_to_new_Meshes);    // Add OperatorList to undo stack
    }

    EOps_FreeContents(&LOps);

    if(LNPolyGroupsMoved)
    {
      E3dScene_ObjectsUpdateModelList(LScene);
      E3dScene_LayoutOnSchematics(LScene, TRUE);

      E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);

      if(LNPolyGroupsMoved > 1) E3dp_PrintMessage(ELogMESSAGE, 3000, "Moved %d PolyGroups to a new Mesh", LNPolyGroupsMoved);
      else E3dp_PrintMessage(ELogMESSAGE, 3000, "Moved %d PolyGroup to a new Mesh", LNPolyGroupsMoved);
    }
    else E3dp_PrintMessage(ELogWARNING, 3000, "Select PolyGroups to move to a new Mesh");

		EList_FreeA_s(LObjects);
	}
}


/*
static inline int _EdgeBinSearchCmp(m0, m1)
{
	 m0.Start < LStart ? -1 : (m0.Start > LStart ? 1 : (m0.End < LEnd ? -1 : (m0.End > LEnd ? 1 : 0)))
}
*/


static inline int _EdgeBinSearchCmp(const E3dEdge* P0, const E3dEdge* P1)
{
	_E3dEdges_Cmp_pp(P0, P1);
}


typedef struct
{
	E3dVertexIndex  Start, End;
	E3dEdgeIndex    Index;
} E3dEdgeAndIndex;


static inline int _EdgeAndIndexBinSearchCmp(const E3dEdgeAndIndex* P0, const E3dEdgeAndIndex* P1)
{
	_E3dEdges_Cmp_pp(P0, P1);
}


/*
static int _EdgeAndIndexCmp_Index(const void* P0, const void* P1)
{
	const E3dEdgeAndIndex*  L0 = (const E3dEdgeAndIndex*)P0;const EIndex  L0I = L0->Index;
	const E3dEdgeAndIndex*  L1 = (const E3dEdgeAndIndex*)P1;const EIndex  L1I = L1->Index;

	if(L0I < L1I) return(-1);
	else if(L0I > L1I) return(1);
	else return(0);
}
*/


static int _EdgeAndIndexCmp_StartEnd(const void* P0, const void* P1)
{
	const E3dEdgeAndIndex*  L0 = (const E3dEdgeAndIndex*)P0;
	const E3dEdgeAndIndex*  L1 = (const E3dEdgeAndIndex*)P1;

	_E3dEdges_Cmp_pp(L0, L1);
}



//================================================
// Add Vertex to Polygon with checking
//================================================
static EChangeFlags _PolygonAddVertexNode(EOps* POps, E3dOperatorEditMesh* POperator, E3dMesh* PMesh, E3dPolyGroup* PPolyGroup, E3dPolygon* PPolygon, const EIndex PVertexIndex)
{
	const E3dVertex*         LVertices = PMesh->Vertices.Items;const EIndex  LNVertices = PMesh->Vertices.Count;const E3dVertexSize  LVertexSize = PMesh->VertexSize;
	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;
	EChangeFlags             LChanged = E3dChgSELECTION;

	if(PPolygon->NumOfVertexNodes)
	{
		EIndex  LI = 0;const EIndex  LN = PPolygon->NumOfVertexNodes;const E3dVertexNode*  LVertexNode = PPolygon->VertexNodes;for(;LI < LN;LI++)
		{
			if(LVertexNode->VertexID == PVertexIndex)
			{
				E3dp_PrintMessage(ELogWARNING, 3000, "Vertex #%d is already in this Polygon", PVertexIndex);
				return(LChanged);
			}
			EStruct_Inc(LVertexNode, LVertexNodeSize);
		}
	}

	E3dPolygon*  LPolygon = PPolygon;

	{
		E3dPolygon**    LPolygons;
		EBool           LAddPoly = FALSE;


#ifdef E3dPolygonAddVertexNodeUNDOABLE
//		EOps                  LOps;EOps_Init(&LOps);
		E3dOperatorEditMesh*  LOperator = NULL;
		EBool                 LCallOperator = FALSE;
		EIndex                LPolygonsToAdd_Count = 0;
		E3dPolygon*           LNewPolygon = NULL;
#endif

		const int  LNormalOffset = E3dVertexNodeAttribute_Offset(&PPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
		E3dVertexNode*  LNewVertexNode;

		if(PPolygon->NumOfVertices == 2)
		{
// Here, the Polygon is not added to the PolyGroup yet, so we can add the new VertexNode to it directly, without worrying about undo/redo
//
			LNewVertexNode = E3dPolygon_AddVertexNode(PPolygon, LVertexNodeSize, PVertexIndex, E3dVNODE_END);
			LPolygon = &_AddPolygon_TmpPolygon;
			E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LVertices, LNVertices, LVertexSize);

// Set the normal of the first 3 VertexNodes
//
			if(LNormalOffset >= 0)
			{
				E3dCoord3*  LNewVertexNodeNormal = (E3dCoord3*)((uint8_t*)LPolygon->VertexNodes + LNormalOffset);
				*LNewVertexNodeNormal = LPolygon->Normal;EStruct_Inc(LNewVertexNodeNormal, LVertexNodeSize);
				*LNewVertexNodeNormal = LPolygon->Normal;EStruct_Inc(LNewVertexNodeNormal, LVertexNodeSize);
				*LNewVertexNodeNormal = LPolygon->Normal;
			}
			LAddPoly = TRUE;
		}
		else if(PPolygon->NumOfVertices > 2)  // Does the PPolygon exist on PPolyGroup yet? (until we have the 3rd VertexNode, it's not added to PPolyGroup)
		{
			if(_AddPolygon_PolygonIndex < 0 || _AddPolygon_PolygonIndex >= PPolyGroup->Polygons.Count)
			{
printf("%s() %s:%d %sINVALID _AddPolygon_PolygonIndex %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)_AddPolygon_PolygonIndex, ECodeNormal);fflush(stdout);
				return(0);
			}

#ifdef E3dPolygonAddVertexNodeUNDOABLE
			if(LOperator == NULL)
			{
				LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
				E_AssignToMember(PMesh, LOperator,MeshNewT);
				EList_ObjectAddA_s(POps->Operators, 1, LOperator);
			}
#endif

			LPolygon = PPolyGroup->Polygons.Items + _AddPolygon_PolygonIndex;
			E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LVertices, LNVertices, LVertexSize);

// Adding more VertexNodes (after the 3rd one) to a Polygon means splitting the last Edge (more precisely, the Edge that was the last one before we added this new Vertex)
//
			{
				const E3dVertexNode*  LVertexNode0 = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LPolygon->NumOfExteriorVertices - 1);
				const E3dVertexNode*  LVertexNode1 = LPolygon->VertexNodes;
				E3dVertexIndex  LStart, LEnd;if(LVertexNode0->VertexID < LVertexNode1->VertexID) { LStart = LVertexNode0->VertexID;LEnd = LVertexNode1->VertexID; } else { LStart = LVertexNode1->VertexID;LEnd = LVertexNode0->VertexID; }

//printf("%s() %s:%d VN %lld  Find Edge %lld -> %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LPolygon->NumOfVertexNodes,  (long long)LStart, (long long)LEnd);fflush(stdout);

				const EIndex  LEdgeToDeleteIndex = E3dMesh_EdgeGetIndex(PMesh, LStart, LEnd);

// Add old Edge for deletion
//
				if(LEdgeToDeleteIndex >= 0)
				{
					ENew(LOperator->EdgesToDelete_Indices);LOperator->EdgesToDelete_Indices[0] = LEdgeToDeleteIndex;
					LOperator->EdgesToDelete_Count = 1;

					EIndex  LNewEdgesN = 2;

// Add new Edges for addition
//
					E3dVertexIndex  LNewEdge0Start = LStart, LNewEdge0End = PVertexIndex;
					if(LNewEdge0Start > LNewEdge0End) E_Swap(LNewEdge0Start, LNewEdge0End);
					const EBool  LNewEdge0Exists = E3dMesh_EdgeGetIndex(PMesh, LNewEdge0Start, LNewEdge0End) >= 0;
					if(LNewEdge0Exists) LNewEdgesN--;

					E3dVertexIndex  LNewEdge1Start = PVertexIndex, LNewEdge1End = LEnd;
					if(LNewEdge1Start > LNewEdge1End) E_Swap(LNewEdge1Start, LNewEdge1End);
					const EBool  LNewEdge1Exists = E3dMesh_EdgeGetIndex(PMesh, LNewEdge1Start, LNewEdge1End) >= 0;
					if(LNewEdge1Exists) LNewEdgesN--;

//printf("%s() %s:%d  LNewEdgesN %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LNewEdgesN);fflush(stdout);
					if(LNewEdgesN)
					{
						const E3dEdge*  LOldEdge = PMesh->Edges.Items + LEdgeToDeleteIndex;

						ENewNClr(LNewEdgesN, LOperator->EdgesToAdd);
						E3dEdge*  LNewEdge = LOperator->EdgesToAdd;

						if(!LNewEdge0Exists) { E3dEdge_Copy(LOldEdge, LNewEdge);E3dEdge_SetVertices(LNewEdge,  LNewEdge0Start, LNewEdge0End);LNewEdge++; }
						if(!LNewEdge1Exists) { E3dEdge_Copy(LOldEdge, LNewEdge);E3dEdge_SetVertices(LNewEdge,  LNewEdge1Start, LNewEdge1End);LNewEdge++; }

						LOperator->EdgesToAdd_Count = LNewEdgesN;
					}
				}
			}


// Set the normals of the VertexNodes
//
#ifdef E3dPolygonAddVertexNodeUNDOABLE
			{
				E3dOpPolyGroup*  LOpPolyGroup = E3d_EditMeshAddOpPolyGroup(LOperator,  PPolyGroup, -1, EEditMODIFY);
				E3dOpPolygon*    LOpPolygon   = E3dOpPolyGroup_AddPolygon(LOpPolyGroup, LPolygon);
				ENew(LOpPolygon->VertexNodesToModify_Indices);LOpPolygon->VertexNodesToModify_Indices[0] = LPolygon->NumOfExteriorVertices;

				EMallocT(E3dVertexNode*, LVertexNodeSize, LNewVertexNode);
//printf("%s() %s:%d  %sEMallocT() got %p%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, LNewVertexNode, ECodeNormal);fflush(stdout);

				LOpPolygon->VertexNodesToModify = (uint8_t*)LNewVertexNode;
				LOpPolygon->VertexNodesToModify_Count = 1;

//				E3dAttributes_Interpolate1D(&PPolyGroup->VertexAttributes,  LStartVertexNode, LEndVertexNode, PU,  LTmpVertexNode);
				LNewVertexNode->VertexID = PVertexIndex;
			}

			LCallOperator = TRUE;

#else
			LNewVertexNode = E3dPolygon_AddVertexNode(PPolygon, LVertexNodeSize, PVertexIndex, E3dVNODE_END);
#endif
			if(LNormalOffset >= 0)
			{
				E3dCoord3*  LNormal = (E3dCoord3*)((uint8_t*)LNewVertexNode + LNormalOffset);
				*LNormal = LPolygon->Normal;
			}
		}
		else
		{
			LNewVertexNode = E3dPolygon_AddVertexNode(PPolygon, LVertexNodeSize, PVertexIndex, E3dVNODE_END);
			if(LNormalOffset >= 0)
			{
				E3dCoord3*  LNormal = (E3dCoord3*)((uint8_t*)LNewVertexNode + LNormalOffset);
				*LNormal = E3dCoord3_s(0.0, 0.0, 0.0);
			}
		}


		const E3dVertexNodeSize  LCustomMembersOffset = sizeof(E3dVertexNode),  // Start of custom members (after VertexID, Normal...)
		                         LCustomMembersSize = LVertexNodeSize - LCustomMembersOffset;

		if(LCustomMembersSize > 0)
		{
			E3dVertexNode**  LVertexNodes;E3dPolyGroup** LPolyGroups;const EIndex  LNumOfPolygonsOnVertex = E3dMesh_VertexGetPolygons(PMesh, PVertexIndex, &LPolygons, &LVertexNodes, &LPolyGroups);if(LNumOfPolygonsOnVertex)
			{
//printf("%s() %s:%d  LNumOfPolygonsOnVertex %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LNumOfPolygonsOnVertex);fflush(stdout);

				E3dCoordinate  LSmallestAngleDistance = 0.0;

				const E3dVertexNode*  LSrcVertexNode = NULL;

// If more than one Polygon already uses this Vertex, get the Vertex attributes (texture coordinates, UVs etc.)
// from the one that's at the smallest angle from the new Polygon
//
				EIndex  LI = 0;for(;LI < LNumOfPolygonsOnVertex;LI++)
				{
					const E3dPolygon*    LTPolygon = LPolygons[LI];
					const E3dCoordinate  LDot = E_Dot3_vv(LPolygon->Normal, LTPolygon->Normal), LAngleDistance = E3d_Abs(LDot);

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

					if(LPolyGroups[LI] == PPolyGroup && (LSrcVertexNode == NULL || LSmallestAngleDistance > LAngleDistance))
					{
						LSrcVertexNode = LVertexNodes[LI];

//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						LSmallestAngleDistance = LAngleDistance;
					}
				}

// Copy VertexNode into the new one
//
				if(LSrcVertexNode)
				{
//printf("%s() %s:%d  %smemcpy() to %p%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, LNewVertexNode, ECodeNormal);fflush(stdout);
					memcpy((uint8_t*)LNewVertexNode + LCustomMembersOffset, (uint8_t*)LSrcVertexNode + LCustomMembersOffset, LCustomMembersSize);
				}
				else memset((uint8_t*)LNewVertexNode + LCustomMembersOffset, 0, LCustomMembersSize);

				EFree(LPolygons);
				EFree(LPolyGroups);
				EFree(LVertexNodes);
			}
			else memset((uint8_t*)LNewVertexNode + LCustomMembersOffset, 0, LCustomMembersSize);
		}

#ifdef E3dPolygonAddVertexNodeUNDOABLE
		if(LCallOperator) LChanged |= LOperator->Class->Func(NULL, (EOperator*)LOperator, EOpDO);
#endif


		if(LAddPoly)
		{
// Number of VertexNodes in _AddPolygon_TmpPolygon reached 3: time to actually add the Polygon to the PolyGroup
//
#ifdef E3dPolygonAddVertexNodeUNDOABLE
			if(LOperator == NULL)
			{
				LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
				E_AssignToMember(PMesh, LOperator,MeshNewT);
				EList_ObjectAddA_s(POps->Operators, 1, LOperator);
			}

			LPolygonsToAdd_Count = 1;
			ENew(LNewPolygon);
			LPolygon = LNewPolygon;

#else
			LPolygon = E3dPolyGroup_AddPolygon(PPolyGroup);
#endif
			memcpy(LPolygon, &_AddPolygon_TmpPolygon, sizeof(E3dPolygon));E3dPolygon_Init(&_AddPolygon_TmpPolygon);

#ifndef E3dPolygonAddVertexNodeUNDOABLE
			LPolygon->Flags |= E3dPolygonSELECTED;
#endif

#ifdef E3dPolygonAddVertexNodeUNDOABLE
			{
// Figure out Edges to add
// We use binary searches in PMesh->Edges, so PMesh->Edges must be sorted, or we need to create a sorted copy
//
				EListAI(, E3dEdgeAndIndex, LEdgesToAddI);
				EBool  LSetEdgesToAdd_Indices = FALSE;

//E3dMesh_EdgesPrint(PMesh);

//#define _EdgeBinSearchCmp(m0, m1)  m0.Start < LStart ? -1 : (m0.Start > LStart ? 1 : (m0.End < LEnd ? -1 : (m0.End > LEnd ? 1 : 0)))

				switch(E3dMesh_EdgesSorted(PMesh))
				{
					case E_YES:
						{
//printf("%s() %s:%d Edges were sorted\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
							const EIndex    LEN = PMesh->Edges.Count;
							const E3dEdge*  LEdges = PMesh->Edges.Items;

// If the Mesh Edges were sorted, insert new Edges at the proper locations, to maintain the sorted order
//
							LSetEdgesToAdd_Indices = TRUE;

							EIndex  LVC = 0;const EIndex  LVN = LNewPolygon->NumOfExteriorVertices;const E3dVertexNode*  LTVertexNode = LNewPolygon->VertexNodes;const E3dVertexNode*  LNextVertexNode = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LTVertexNode);for(;LVC < LVN;LVC++)
							{
								LTVertexNode    = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LTVertexNode);
								LNextVertexNode = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LNextVertexNode);
								const E3dVertexIndex  LV0 = LTVertexNode->VertexID, LV1 = LNextVertexNode->VertexID;
								E3dVertexIndex        LStart, LEnd;
								if(LV0 <= LV1) { LStart = LV0;LEnd = LV1; }
								else { LStart = LV1;LEnd = LV0; }

// Check if this Edge exists in PMesh
//
//printf("   %lld -> %lld", (long long)LStart, (long long)LEnd);fflush(stdout);

								E3dEdge  LTEdge;LTEdge.Start = LStart;LTEdge.End = LEnd;

								E3dEdgeIndex  LEdgeIndex, LInsertHere = -1;E_BinarySearchCmpEqIns(LEdges, LEN,  &LTEdge,  _EdgeBinSearchCmp, LEdgeIndex, LInsertHere);

								if(LEdgeIndex < 0)
								{
									E3dEdgeAndIndex*  LEdgeToAddI = EList_StructAddA(LEdgesToAddI, 16);
									LEdgeToAddI->Start = LStart;
									LEdgeToAddI->End   = LEnd;
									LEdgeToAddI->Index = LInsertHere;
								}

//printf(" LEdgeIndex %lld  LInsertHere %lld\n", (long long)LEdgeIndex, (long long)LInsertHere);fflush(stdout);
							}
						}
					break;

					case E_NO:
						{
// We can't change the order of the original Edges of PMesh because there might be an operator in the undo stack that refers to Edge indices.
// So, we have to create a "copy" (not a full copy, just Start and End members, to save memory in case of a large Mesh) and sort that.
//
							const EIndex      LEN = PMesh->Edges.Count;
							E3dEdgeAndIndex*  LEdgeIs;

							{
								ENewN(LEN, LEdgeIs);
								EIndex  LEC = 0;const E3dEdge*  LEdge = PMesh->Edges.Items;E3dEdgeAndIndex*  LEdgeI = LEdgeIs;for(;LEC < LEN;LEC++, LEdge++, LEdgeI++)
								{
									LEdgeI->Start = LEdge->Start;
									LEdgeI->End   = LEdge->End;
									LEdgeI->Index = LEN;     // The original index of the Edge, so we can refer back to PMesh->Edges after LEdgeIs is sorted
								}

								qsort(LEdgeIs, LEN, sizeof(E3dEdgeAndIndex), _EdgeAndIndexCmp_StartEnd);
							}

							{
//printf("%s() %s:%d  Sorted Edges\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
								EIndex  LVC = 0;const EIndex  LVN = LNewPolygon->NumOfExteriorVertices;const E3dVertexNode*  LTVertexNode = LNewPolygon->VertexNodes;const E3dVertexNode*  LNextVertexNode = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LTVertexNode);for(;LVC < LVN;LVC++)
								{
									LTVertexNode    = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LTVertexNode);
									LNextVertexNode = E3d_PolygonContourNextVertexNode(LNewPolygon, LVertexNodeSize, LNextVertexNode);
									const E3dVertexIndex  LV0 = LTVertexNode->VertexID, LV1 = LNextVertexNode->VertexID;
									E3dVertexIndex        LStart, LEnd;
									if(LV0 <= LV1) { LStart = LV0;LEnd = LV1; }
									else { LStart = LV1;LEnd = LV0; }

// Check if this Edge exists in PMesh
//
//printf("   %lld -> %lld", (long long)LStart, (long long)LEnd);fflush(stdout);

									E3dEdgeAndIndex  LTEdgeI;LTEdgeI.Start = LStart;LTEdgeI.End = LEnd;

									E3dEdgeIndex  LEdgeIndex, LInsertHere = -1;E_BinarySearchCmpEqIns(LEdgeIs, LEN,  &LTEdgeI,  _EdgeAndIndexBinSearchCmp, LEdgeIndex, LInsertHere);

									if(LEdgeIndex < 0)
									{
										E3dEdgeAndIndex*  LEdgeToAddI = EList_StructAddA(LEdgesToAddI, 16);
										LEdgeToAddI->Start = LStart;
										LEdgeToAddI->End   = LEnd;
										LEdgeToAddI->Index = LInsertHere;
									}

//printf(" LEdgeIndex %lld  LInsertHere %lld\n", (long long)LEdgeIndex, (long long)LInsertHere);fflush(stdout);
								}
							}
							EFree(LEdgeIs);
						}
					break;

					case E_CORRUPT_DATA:
//printf("%s() %s:%d bad Edges\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					break;

					case E_NO_DATA:
//printf("%s() %s:%d no Edges\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					break;
				}


//#undef _EdgeBinSearchCmp

//printf("\n");fflush(stdout);


				if(LEdgesToAddI)
				{
// Sort LEdgesToAddI by Index
//
					if(LSetEdgesToAdd_Indices)
					{
						qsort(LEdgesToAddI, LEdgesToAddI_Count, sizeof(E3dEdgeAndIndex), _EdgeAndIndexCmp_StartEnd);

						ENewN(LEdgesToAddI_Count, LOperator->EdgesToAdd_Indices);

// Correct insertion positions (as we insert Edges, the insertion index of ones after that new Edge will move up by one...)
//
//printf("%s() %s:%d EdgesToAdd_Indices:", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						EIndex  LEC = 0;for(;LEC < LEdgesToAddI_Count;LEC++)
						{
							LOperator->EdgesToAdd_Indices[LEC] = LEdgesToAddI[LEC].Index + LEC;
//							printf("  %lld -> %lld %lld", (long long)LEdgesToAddI[LEC].Start, (long long)LEdgesToAddI[LEC].End, (long long)LOperator->EdgesToAdd_Indices[LEC]);fflush(stdout);
						}
//printf("\n");fflush(stdout);
					}

					E3dEdge*  LEdgesToAdd;ENewN(LEdgesToAddI_Count, LEdgesToAdd);if(LEdgesToAdd)
					{
						EIndex  LEC = 0;const EIndex  LEN = LEdgesToAddI_Count;const E3dEdgeAndIndex*  LEdgeI = LEdgesToAddI;E3dEdge*  LEdge = LEdgesToAdd;for(;LEC < LEN;LEC++, LEdgeI++, LEdge++)
						{
							E3dEdge_Init(LEdge,  LEdgeI->Start, LEdgeI->End);
						}
						LOperator->EdgesToAdd = LEdgesToAdd;LOperator->EdgesToAdd_Count = LEdgesToAddI_Count;
					}

					EFree(LEdgesToAddI);
				}


				LChanged |= E3d_MeshPolyGroupPolygonsAddDel(POps, LOperator,  PMesh, PPolyGroup,  NULL, 0,  LNewPolygon, LPolygonsToAdd_Count,  E3dLng_Polygon_add_Vertex);

				LPolygon = PPolyGroup->Polygons.Items + PPolyGroup->Polygons.Count - 1;
				LPolygon->Flags |= E3dPolygonSELECTED;
			}
#endif  // E3dPolygonAddVertexNodeUNDOABLE

			_AddPolygon_PolygonIndex = LPolygon - PPolyGroup->Polygons.Items;
		}


		if(LPolygon)
		{
			if(LPolygon->NumOfVertices > 3) E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  PMesh->Vertices.Items, PMesh->Vertices.Count, PMesh->VertexSize,  TRUE);
		}

		E3dMeshPolyGroup_RefreshNormals(PMesh, PPolyGroup, PPolyGroup->VertexNormalType, PPolyGroup->SmoothingAngle);


		E3dp_PrintMessage(ELogMESSAGE, 3000, "Added Vertex #%d   # of vertices: %d", PVertexIndex, LPolygon->NumOfVertices);

#ifndef E3dPolygonAddVertexNodeUNDOABLE
		LChanged |= E3dChgMeshSHAPE;
#endif
	}


	return(LChanged);
}


#define CROSS_ssa(dest, v1, v2)  dest.X = - v2.Y, dest.Y = v2.X, dest.Z = v1.X * v2.Y - v1.Y * v2.X


//================================================================================
// 2D point-in-triangle test with barycentric coordinates
//================================================================================
static inline EBool _PointIn2DTriangle(const E3dCoord2* PPoint, const E3dCoord2* PVertex0, const E3dCoord2* PVertex1, const E3dCoord2* PVertex2)
{
	E3dCoord2  LEdge1;LEdge1.X = PVertex1->X - PVertex0->X;LEdge1.Y = PVertex1->Y - PVertex0->Y;
	E3dCoord2  LEdge2;LEdge2.X = PVertex2->X - PVertex0->X;LEdge2.Y = PVertex2->Y - PVertex0->Y;

	E3dCoord2  LPVector, LTVector;
	E3dCoord3  LQVector;


// Begin calculating determinant - also used to calculate U parameter
//
// Cross-product of ray direction and LEdge2
//
	LPVector.X =  LEdge2.Y;
	LPVector.Y = -LEdge2.X;

	const E3dCoordinate  LDet = E_Dot2_vv(LEdge1, LPVector);
	const E3dCoordinate  LInvDet = 1.0 / LDet;

// Calculate distance from LVertex0 to ray origin
//
	LTVector.X = PPoint->X - PVertex0->X, LTVector.Y = PPoint->Y - PVertex0->Y;

// Calculate U parameter and test bounds
//
	{
		E3dCoordinate  LU = E_Dot2_vv(LTVector, LPVector) * LInvDet;
		if(LU < 0.0 || LU > 1.0) return(FALSE);

// Prepare to test V parameter
//
		CROSS_ssa(LQVector, LTVector, LEdge1);

// Calculate V parameter and test bounds
//
		E3dCoordinate  LV = -LQVector.Z * LInvDet;

		if(LV < 0.0 || LU + LV > 1.0) return(FALSE);
	}

	return(TRUE);
}



static EBool _PointInPolygon2D(const E3dCoord2* P2DPoint, const E3dPolygon* PPolygon, const E3dCoord2* P2DPoints, const EIndex* POrigVertexNodeIndices)
{
	if(PPolygon->NumOfVertices == 3)    // Triangle?
	{
		if(_PointIn2DTriangle(P2DPoint,  P2DPoints + POrigVertexNodeIndices[0], P2DPoints + POrigVertexNodeIndices[1], P2DPoints + POrigVertexNodeIndices[2])) return(TRUE);
	}
	else
	{
		const E3dITriangle*  LITriangle = PPolygon->ITriangles;const EIndex  LN =  PPolygon->ITriangles_Count;
		EIndex  LI = 0;for(;LI < LN;LI++, LITriangle++)
		{
			if(_PointIn2DTriangle(P2DPoint,  P2DPoints + POrigVertexNodeIndices[LITriangle->VertexNodes[0]], P2DPoints + POrigVertexNodeIndices[LITriangle->VertexNodes[1]], P2DPoints + POrigVertexNodeIndices[LITriangle->VertexNodes[2]])) return(TRUE);
		}
	}
	return(FALSE);
}


//================================================================
// Add a new Edge between two Vertices of a Mesh
//================================================================
static EBool _MeshAddEdge(EOps* POps, E3dMesh* PMesh,  const EIndex PIndex0, const EIndex PIndex1,  const EBool PTestOnly)
{
//printf("Vtx %d to Vtx %d\n", LIndex0, LIndex1);fflush(stdout);

// Check if there is already an Edge between LIndex0 and LIndex1
//
	{
		E3dEdge  LEdge;LEdge.Start = PIndex0;LEdge.End = PIndex1;

		if(E3dMesh_EdgeGetPolygons(PMesh, &LEdge, NULL, NULL))
		{
			E3dp_PrintMessage(ELogWARNING, 5000, "There is already an Edge there");

			return(FALSE);
		}
	}


	E3dPolyGroup*        LPolyGroup = NULL;
	E3dPolygon*          LPolygon = NULL;
	EIndex               LPolyVStart = -1, LPolyVEnd = -1;
	const E3dVertexSize  LVertexSize = PMesh->VertexSize;

// Find the Polygon that contains both Vertices
//
	{
		EBool	 LKeepGoing = TRUE;

		EIndex  LPGI = 0;const EIndex  LGN = PMesh->PolyGroups.Count;for(;LPGI < LGN && LKeepGoing;LPGI++)
		{
			E3dPolyGroup*            LTPolyGroup = PMesh->PolyGroups.Items[LPGI];
			const E3dVertexNodeSize  LVertexNodeSize = LTPolyGroup->VertexNodeSize;

			EIndex       LPI = 0;const EIndex  LPN = LTPolyGroup->Polygons.Count;E3dPolygon*  LTPolygon = LTPolyGroup->Polygons.Items;for(;LPI < LPN && LKeepGoing;LPI++, LTPolygon++)
			{
				E3dVertexNode*  LVertexNode = LTPolygon->VertexNodes;
				LPolyVStart = -1;LPolyVEnd = -1;

				EIndex  LVC = 0;const EIndex  LVN = LTPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
				{
					if(LVertexNode->VertexID == PIndex0)
					{
						LPolyVStart = LVC;
						if(LPolyVEnd >= 0)
						{
							LPolyGroup = LTPolyGroup;LPolygon = LTPolygon;LPolyVStart = LVC;LKeepGoing = FALSE;break;
						}
					}
					else if(LVertexNode->VertexID == PIndex1)
					{
						LPolyVEnd = LVC;
						if(LPolyVStart >= 0)
						{
							LPolyGroup = LTPolyGroup;LPolygon = LTPolygon;LPolyVEnd = LVC;LKeepGoing = FALSE;break;
						}
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}
		}
	}

	if(LPolyVStart < 0 || LPolyVEnd < 0)
	{
		E3dp_PrintMessage(ELogWARNING, 5000, "The start and end Vertex must be on the same Polygon");
		return(FALSE);
	}


// It doesn't make sense to add a new Edge to a hole contour...
// New Edge on the exterior (unless it closes a 'concave gap'), between the exterior and a hole,
// or between two holes are ok, as long as the new Edge doesn't intersect any of the existing ones
//
	{
		if(LPolyVStart >= LPolygon->NumOfExteriorVertices || LPolyVEnd >= LPolygon->NumOfExteriorVertices)
		{
			E3dp_PrintMessage(ELogWARNING, 5000, "You can only add an Edge to the exterior contour of a Polygon");
			return(FALSE);
		}
	}




// Make sure the new Edge won't intersect any of the existing Edges in the Polygon.
// Edges that share a Vertex with the new Edge need not and SHOULD NOT be tested!
//
	if(LPolygon)
	{
		const E3dVertex*         LMeshVertices = PMesh->Vertices.Items;const EIndex  LMeshNVertices = PMesh->Vertices.Count;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;
		const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
		E3dCoord2*               L2DVertices = E3dPolygon_FlattenScaled(LPolygon, LVertexNodeSize,  LMeshVertices, LMeshVertexSize,  100.0);if(L2DVertices)
		{
			const EIndex      LVN = LPolygon->NumOfVertexNodes;
			EBool             LIntersection = FALSE;
			const E3dCoord2*  LNewEdgeStart2DVertex = NULL;
			const E3dCoord2*  LNewEdgeEnd2DVertex = NULL;

			{
				EIndex  LVC = 0;const E3dCoord2*  L2DVertex = L2DVertices;E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;for(;LVC < LVN;LVC++, L2DVertex++)
				{
					const EIndex  LVertexIndex = LVertexNode->VertexID;if(LVertexIndex > -1)
					{
						if(LVC == LPolyVStart) LNewEdgeStart2DVertex = L2DVertex;
						else if(LVC == LPolyVEnd) LNewEdgeEnd2DVertex = L2DVertex;
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}


//printf("%s() %s:%d  New Edge : % 7.2f, % 7.2f    % 7.2f, % 7.2f\n", __FUNCTION__, __FILE__, __LINE__, XY_p(LNewEdgeStart2DVertex),  XY_p(LNewEdgeEnd2DVertex));fflush(stdout);

			{
				EIndex  LVC = 0;const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;for(;LVC < LVN;LVC++)
				{
					const EIndex  LVertexIndex = LVertexNode->VertexID;if(LVertexIndex > -1)
					{
						if(LVC != LPolyVStart && LVC != LPolyVEnd)
						{
							const EIndex  LVC1 = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);

							if(LVC1 != LPolyVStart && LVC1 != LPolyVEnd)
							{
								const E3dCoord2*  LStart2DVertex = L2DVertices + LVC;
								const E3dCoord2*  LEnd2DVertex   = L2DVertices + LVC1;

								if(E3d_Intersect2D(LNewEdgeStart2DVertex->X, LNewEdgeStart2DVertex->Y,  LNewEdgeEnd2DVertex->X, LNewEdgeEnd2DVertex->Y,  LStart2DVertex->X, LStart2DVertex->Y,  LEnd2DVertex->X, LEnd2DVertex->Y))
								{
//printf("%s() %s:%d  INTERSECTION  with  % 7.2f, % 7.2f    % 7.2f, % 7.2f\n", __FUNCTION__, __FILE__, __LINE__,  XY_p(LStart2DVertex),  XY_p(LEnd2DVertex));fflush(stdout);
//assert(0);
									LIntersection = TRUE;break;
								}

							}
						}
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}


			if(LIntersection)
			{
				E3dp_PrintMessage(ELogWARNING, 3000, Lng_NewEdgeIntersect);
				EFree(L2DVertices);
				return(FALSE);
			}


			{
				E3dCoord2*  L2DVerticesForNormal;
				EIndex      LVN0 = 1, LVN1 = 1;


// Make sure the new Edge won't create an inverted, or a 0-area Polygon
//

// Compute NumOfExteriorVertices for the new Polygons
//
				{
					EIndex  LVC = LPolyVStart;for(;LVC != LPolyVEnd;)
					{
						LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);LVN0++;
					}
					for(LVC = LPolyVEnd;LVC != LPolyVStart;)
					{
						LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);LVN1++;
					}
				}

// Allocate array for 2DPositions with the greater VertexCount
//
				if(LVN0 > LVN1)
				{
					ENewN(LVN0, L2DVerticesForNormal);
				}
				else ENewN(LVN1, L2DVerticesForNormal);


// Compute normal of flattened Polygon0
//
				{
					EIndex  LVC = LPolyVStart;E3dCoord2*  L2DVertex = L2DVerticesForNormal;for(;LVC != LPolyVEnd;L2DVertex++)
					{
      			*L2DVertex = L2DVertices[LVC];
						LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);
					}
     			*L2DVertex = L2DVertices[LVC];  // The last VertexNode is not done in the for() loop
				}


				{
					const E3dCoordinate  LNormalZ = E3d_2DPolygonNormal(L2DVerticesForNormal, LVN0);if(LNormalZ <= 0.0)
					{
						if(LNormalZ < 0)
						{
							if(_CheckForCreatingInvertedPolygon)
							{
								E3dp_PrintMessage(ELogWARNING, 3000, Lng_NewEdgeInvPoly);
								EFree(L2DVerticesForNormal);
								EFree(L2DVertices);
								return(FALSE);
							}
						}
						else
						{
							E3dp_PrintMessage(ELogWARNING, 3000, Lng_NewEdge0AreaPoly);
							EFree(L2DVerticesForNormal);
							EFree(L2DVertices);
							return(FALSE);
						}
					}
				}

// Compute normal of flattened Polygon1
//
				{
					EIndex  LVC = LPolyVEnd;E3dCoord2*  L2DVertex = L2DVerticesForNormal;for(;LVC != LPolyVStart;L2DVertex++)
					{
      			*L2DVertex = L2DVertices[LVC];
						LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);
					}
     			*L2DVertex = L2DVertices[LVC];  // The last VertexNode is not done in the for() loop
				}

				{
					E3dCoordinate  LNormalZ = E3d_2DPolygonNormal(L2DVerticesForNormal, LVN1);if(LNormalZ <= 0.0)
					{
						if(LNormalZ < 0.0)
						{
							if(_CheckForCreatingInvertedPolygon)
							{
								E3dp_PrintMessage(ELogWARNING, 3000, Lng_NewEdgeInvPoly);
								EFree(L2DVerticesForNormal);
								EFree(L2DVertices);
								return(FALSE);
							}
						}
						else
						{
							E3dp_PrintMessage(ELogWARNING, 3000, Lng_NewEdge0AreaPoly);
							EFree(L2DVerticesForNormal);
							EFree(L2DVertices);
							return(FALSE);
						}
					}
				}

				EFree(L2DVerticesForNormal);

				{
					const EIndex    LPolyIndex = LPolygon - LPolyGroup->Polygons.Items;  // This must be before E3dPolyGroup_AddPolygons(), because E3dPolyGroup_AddPolygons() might reallocate the Polygons array
#if E3dAddEdgeUNDOABLE
					E3dPolygon*     LNewPolygon0;ENewN(2, LNewPolygon0);E3dPolygons_Init(LNewPolygon0, 2);
#else
					E3dPolygon*     LNewPolygon0 = E3dPolyGroup_AddPolygons(LPolyGroup, 2);
#endif

					E3dPolygon*     LNewPolygon1 = LNewPolygon0 + 1;
					E3dVertexNode*  LDVertexNode;


					LPolygon = LPolyGroup->Polygons.Items + LPolyIndex;  // LPolyGroup->Polygons was reallocated...

// Create new Polygon0
//
					EIndex*         LPolygon0OrigVertexNodeIndices;ENewN(LVN0, LPolygon0OrigVertexNodeIndices);  // Tells, in new Polygon0, which VertexNodes in the original Polygon were used
					E3dPolygon_VertexNodesNew(LNewPolygon0, LVertexNodeSize, LVN0);
					LDVertexNode = LNewPolygon0->VertexNodes;

					{
						EIndex*  LOrigVertexNodeIndexP = LPolygon0OrigVertexNodeIndices;
						EIndex   LVC = LPolyVStart;E3dVertexNode*  LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LVC);for(;LVC != LPolyVEnd;)
						{
							memcpy(LDVertexNode, LVertexNode, LVertexNodeSize);*LOrigVertexNodeIndexP++ = LVC;
							LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LVC);
							EStruct_Inc(LDVertexNode, LVertexNodeSize);
						}

						memcpy(LDVertexNode, LVertexNode, LVertexNodeSize);*LOrigVertexNodeIndexP++ = LVC;  // The last VertexNode is not done in the for() loop
					}

					LNewPolygon0->NumOfVertices = LVN0;
					LNewPolygon0->NumOfExteriorVertices = LVN0;



// Create new Polygon1
//
					EIndex*  LPolygon1OrigVertexNodeIndices;ENewN(LVN1, LPolygon1OrigVertexNodeIndices);	// Tells, in new Polygon1, which VertexNodes in the original Polygon were used
					E3dPolygon_VertexNodesNew(LNewPolygon1, LVertexNodeSize, LVN1);
					LDVertexNode = LNewPolygon1->VertexNodes;

					{
						EIndex*  LOrigVertexNodeIndexP = LPolygon1OrigVertexNodeIndices;
						EIndex   LVC = LPolyVEnd;E3dVertexNode*  LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LVC);for(;LVC != LPolyVStart;)
						{
							memcpy(LDVertexNode, LVertexNode, LVertexNodeSize);*LOrigVertexNodeIndexP++ = LVC;

							LVC = E3d_PolygonContourNextVertexNodeIndex(LPolygon, LVertexNodeSize, LVC);LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LVC);
							EStruct_Inc(LDVertexNode, LVertexNodeSize);
						}
						memcpy(LDVertexNode, LVertexNode, LVertexNodeSize);*LOrigVertexNodeIndexP++ = LVC;	// The last VertexNode is not done in the for() loop
					}
					LNewPolygon1->NumOfVertices = LVN1;
					LNewPolygon1->NumOfExteriorVertices = LVN1;




// Add holes to the appropriate new Polygons
//

// To do that, we first have to triangulate the new Polygons, so we can do point-in-triangle tests
//
					E3dPolygon_UpdateNormal(LNewPolygon0, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize);
					E3dPolygon_Triangulate(LNewPolygon0, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize,  TRUE);

					E3dPolygon_UpdateNormal(LNewPolygon1, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize);
					E3dPolygon_Triangulate(LNewPolygon1, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize,  TRUE);

					EBool  LPolygon0Changed = FALSE, LPolygon1Changed = FALSE;

					if(LPolygon->NumOfVertexNodes > LPolygon->NumOfExteriorVertices)
					{
						const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;  // VertexNodes of the original Polygon
						EIndex  LVC = LPolygon->NumOfExteriorVertices;const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LPolygon->NumOfExteriorVertices);for(;LVC < LVN;)
						{
							const EIndex  LVertexNodesInHole = -LVertexNode->VertexID;

							{
// Check in which new Polygon the first VertexNode of this hole falls
//
								EStruct_Inc(LVertexNode, LVertexNodeSize);
//								const EIndex  LVertexIndex = LVertexNode->VertexID;

								LVC++;    // Skip contour separator
								const E3dCoord2*  L2DVertex = L2DVertices + LVC;
								if(_PointInPolygon2D(L2DVertex, LNewPolygon0, L2DVertices, LPolygon0OrigVertexNodeIndices))
								{
									LNewPolygon0->VertexNodes = ERealloc(LNewPolygon0->VertexNodes, LVertexNodeSize * (LNewPolygon0->NumOfVertexNodes + 1 + LVertexNodesInHole));
									memcpy((uint8_t*)LNewPolygon0->VertexNodes + LVertexNodeSize * LNewPolygon0->NumOfVertexNodes, (uint8_t*)LVertexNode - LVertexNodeSize, LVertexNodeSize * (1 + LVertexNodesInHole));
									LNewPolygon0->NumOfVertexNodes += 1 + LVertexNodesInHole;
									LNewPolygon0->NumOfVertices += LVertexNodesInHole;
									LPolygon0Changed = TRUE;
//printf("%s() %s:%d hole in Polygon0\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
								}
								else if(_PointInPolygon2D(L2DVertex, LNewPolygon1, L2DVertices, LPolygon1OrigVertexNodeIndices))
								{
									LNewPolygon1->VertexNodes = ERealloc(LNewPolygon1->VertexNodes, LVertexNodeSize * (LNewPolygon1->NumOfVertexNodes + 1 + LVertexNodesInHole));
									memcpy((uint8_t*)LNewPolygon1->VertexNodes + LVertexNodeSize * LNewPolygon1->NumOfVertexNodes, (uint8_t*)LVertexNode - LVertexNodeSize, LVertexNodeSize * (1 + LVertexNodesInHole));
									LNewPolygon1->NumOfVertexNodes += 1 + LVertexNodesInHole;
									LNewPolygon1->NumOfVertices += LVertexNodesInHole;
									LPolygon1Changed = TRUE;
//printf("%s() %s:%d hole in Polygon1\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
								}
								else
								{
									printf("%s() %s:%d ERROR : hole is not within any of the new Polygons???\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
								}

								LVC += LVertexNodesInHole;  // Skip contour Vertex indices
							}

							LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVC);
						}

						if(LPolygon0Changed)
						{
							E3dPolygon_UpdateNormal(LNewPolygon0, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize);
							E3dPolygon_Triangulate(LNewPolygon0, LVertexNodeSize,  LMeshVertices, LMeshNVertices, LVertexSize,  TRUE);
						}

						if(LPolygon1Changed)
						{
							E3dPolygon_UpdateNormal(LNewPolygon1, LVertexNodeSize,  LMeshVertices, LMeshNVertices,  LVertexSize);
							E3dPolygon_Triangulate(LNewPolygon1, LVertexNodeSize,  LMeshVertices, LMeshNVertices,  LVertexSize,  TRUE);
						}
					}


					EFree(LPolygon0OrigVertexNodeIndices);
					EFree(LPolygon1OrigVertexNodeIndices);
					EFree(L2DVertices);


// Create and run operator to delete the old Polygon and add the new Polygons
// This makes this operation undo/redo-able
//
#if E3dAddEdgeUNDOABLE
					{
						EIndex*  LPolygonsToDelIndices;ENew(LPolygonsToDelIndices);LPolygonsToDelIndices[0] = LPolyIndex;

						E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
						E_AssignToMember(PMesh, LOperator,MeshNewT);
						E3dEdge*  LNewEdge;ENew(LNewEdge);
						LOperator->EdgesToAdd = LNewEdge;
						if(PIndex0 < PIndex1) E3dEdge_Init(LNewEdge,  PIndex0, PIndex1);else E3dEdge_Init(LNewEdge,  PIndex1, PIndex0);
						LOperator->EdgesToAdd_Count = 1;
						E3d_MeshPolyGroupPolygonsAddDel(POps, LOperator,  PMesh, LPolyGroup,  LPolygonsToDelIndices, 1,  LNewPolygon0, 2,  E3dLng_Mesh_add_Edge);
						EList_ObjectAddA_s(POps->Operators, 1, LOperator);
					}
#else
// Delete original Polygon
//
					E3dPolyGroup_DeletePolygonsByIndex(LPolyGroup, &LPolyIndex, 1);
#endif
				}
			}

			if(_AddEdge_StartVertexIndex > -1)
			{
				E3dVertex*  LVertex = EStruct_ByIndex(PMesh->Vertices.Items, PMesh->VertexSize, _AddEdge_StartVertexIndex);
				LVertex->Flags &= ~E3dVtxTMPSELECTED;
				_AddEdge_StartVertexIndex = -1;
				E3dp_PrintMessage(ELogMESSAGE, 3000, "Added Edge");
			}
			return(TRUE);
		}

	}

	return(FALSE);
}


//========================================================
// EUNUSED_CODE
// Delete an Edge from a Mesh
//
// Description
//  Deletes the given Edge from PMesh and re-indexes
//  the Edge references of the PolyGroups of PMesh.
//========================================================
void E3d_MeshDeleteEdge(E3dMesh* PMesh, E3dEdge* PEdge)
{
	const EIndex  LEdgeIndex = PEdge - PMesh->Edges.Items;

	if(LEdgeIndex < PMesh->Edges.Count - 1) memmove(PEdge, PEdge + 1, sizeof(E3dEdge) * (PMesh->Edges.Count - LEdgeIndex - 1));
	PMesh->Edges.Count -= 1;

	if(PMesh->Edges.Count <= 0) E3dMesh_EdgesFree(PMesh);
	else { EResizeNT(E3dEdge, PMesh->Edges.Count, PMesh->Edges.Items);PMesh->Edges.CountAllocated = PMesh->Edges.Count; }

// Reindex Edge list of PolyGroups
//
	EIndex  LPGI = 0;const EIndex  LGN = PMesh->PolyGroups.Count;for(;LPGI < LGN;LPGI++)
	{
		E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];

		EIndex  LEC = 0;for(;LEC < LPolyGroup->Edges.Count;)
		{
			const EIndex  LTEdgeIndex = LPolyGroup->Edges.Items[LEC];
			if(LTEdgeIndex >= LEdgeIndex)
			{
				if(LTEdgeIndex == LEdgeIndex) EList_IndexRemoveByIndex_s(LPolyGroup->Edges, LEC);
				else
				{
// LPolyGroup->Edges is probably reallocated in ELst_RemoveInt, so we must use LPolyGroup->Edges here
//
					LPolyGroup->Edges.Items[LEC] -= 1;  // Decrease index if it's after the deleted Edge
					LEC++;
				}
			}
			else LEC++;
		}
	}

}


//================================================================
// Delete Edge from a Mesh and merge the Polygons that share it
//================================================================
static int _MeshDeleteEdge(E3dMesh* PMesh, const E3dEdge* PEdge)
{
	EBool  LWeCreatedVertexLinks = FALSE;

	if(PMesh->VertexLinksNew == NULL) { E3dMesh_GetVertexLinksNew(PMesh, E3dVertexLinksEDGES);LWeCreatedVertexLinks = TRUE; }

	{
		const E3dVertexLinkNew*  LVertexLinks = PMesh->VertexLinksNew;
		const EIndex             LVertexLinksStride = PMesh->VertexLinksStride;
		const E3dVertexLinkNew*  LVertexLinksS = EStruct_ByIndex(LVertexLinks, LVertexLinksStride, PEdge->Start);
		const E3dVertexLinkNew*  LVertexLinksE = EStruct_ByIndex(LVertexLinks, LVertexLinksStride, PEdge->End);

		if(LVertexLinksS->EdgeEnds.Count == 2 || LVertexLinksE->EdgeEnds.Count == 2)
		{
			if(E3dp_Prefs.OptimizeFor == EOptimizeMEMORY && LWeCreatedVertexLinks) E3dMesh_VertexLinksNewFree(PMesh);
			return(E3dEdgeVERTEX_WITH_VALENCE_2);
		}
	}

	if(E3dp_Prefs.OptimizeFor == EOptimizeMEMORY && LWeCreatedVertexLinks) E3dMesh_VertexLinksNewFree(PMesh);

	int           LResult = 0;
	unsigned int  LDescription;E3dEdgePolygonInfo*  LEdgeDescriptors;const EIndex  LNumOfPolygonsOnEdge = E3dMesh_EdgeGetPolygons(PMesh, PEdge, &LEdgeDescriptors, &LDescription);


	if(LNumOfPolygonsOnEdge != 2)
	{
		EFreeChk(LEdgeDescriptors);
		return(E3dEdgeNOT_SHARED_BY_2_POLYS);
	}

	if(LEdgeDescriptors[0].PolyGroupIndex != LEdgeDescriptors[1].PolyGroupIndex)
	{
		EFreeChk(LEdgeDescriptors);
		return(E3dEdgePOLYS_IN_DIFFERENT_POLYGROUPS);
	}

	EFreeChk(LEdgeDescriptors);

	EIndex*  LEdgeIndices;ENew(LEdgeIndices);LEdgeIndices[0] = PEdge - PMesh->Edges.Items;

	E3dMesh_DeleteComponents(PMesh,  LEdgeIndices, 1,  FALSE,  NULL,  E3dLng_Mesh_delete_Edge,  NULL);

	return(LResult);
}


//========================================================================================
// Delete a Vertex from a Mesh, and take care of the Polygons that share that Vertex
//========================================================================================
static EBool _MeshDeleteVertex(E3dMesh* PMesh, E3dVertex* PVertex)
{
	const E3dVertexSize  LVertexSize = PMesh->VertexSize;
	const EIndex         LVertexIndex = EStruct_GetIndex(PMesh->Vertices.Items, LVertexSize, PVertex);
	EIndex*              LVertexIndices;const EIndex  LNumOfVertices = E3dMesh_VertexGetConnectedVertices(PMesh, LVertexIndex, &LVertexIndices);
	EBool                LChanged = FALSE;

	if(LNumOfVertices != 0 && LNumOfVertices != 2)
	{
		E3dp_PrintMessage(ELogWARNING, 3000, "For now, delete Vertex only works for Vertices with 0 or 2 Edges on them");
		return(FALSE);
	}

// Delete all shared Edges (Edges used by at least 2 Polygons) that meet at this Vertex
//
	if(LNumOfVertices)
	{
/*
		E3dEdge*  LMeshEdges = PMesh->Edges;

		EIndex  LI = 0;for(;LI < LNumOfVertices;LI++)
		{
			const EIndex  LVertexIndex = PMesh->Vertices, LVertexSize, PVertex);

// Find Edge in Edge list of Mesh and delete it
//
			EIndex  LEC = 0;const EIndex  LEN = PMesh->Edges.Count;E3dEdge*  LEdge = LMeshEdges;for(;LEC < LEN;LEC++, LEdge++)
			{
				if((LEdge->Start == LVertexIndex && LEdge->End == LVertices[LI]) || (LEdge->Start == LVertices[LI] && LEdge->End == LVertexIndex))
				{
printf("Deleting Edge %p\n", LEdge);fflush(stdout);
					switch(_MeshDeleteEdge(PMesh, LEdge))
					{
						case 0:    // Success
							LChanged = TRUE;
						break;
					}
				}
			}
		}
		EFree(LVertices);
*/
	}


// Create undoable operator for removing this Vertex
//
	{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
		E3dOperatorAddDeleteVertices*  LOperator = (E3dOperatorAddDeleteVertices*)EOperator_New(E3dOperatorDeleteVertices_class);
		ENew(LOperator->Vertices_Indices);LOperator->Vertices_Indices[0] = LVertexIndex;LOperator->Vertices_Count = 1;
//		E3dOperatorEditMesh*  LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
//		ENew(LOperator->VerticesToDelete_Indices);LOperator->VerticesToDelete_Indices[0] = LVertexIndex;LOperator->VerticesToDelete_Count = 1;

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

		EOps  LOps;EOps_Init(&LOps);

		EList_ObjectAddA_s(LOps.Operators, 1, LOperator);

// Run operator
//
		LChanged |= LOperator->Class->Func(&LOps, (EOperator*)LOperator, EOpDO);

		if(LOps.Operators.Items)
		{
			E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);
			E3dpUndoStack_AddOps(&LOps, E3dLng_Mesh_delete_Vertex);    // Add OperatorList to undo stack
		}

		EOps_FreeContents(&LOps);
	}

	return(LChanged);
}


static void _FinishEdge(E3dMesh* PMesh)
{
	if(PMesh)
	{
		E3dVertex*  LVertex = EStruct_ByIndex(PMesh->Vertices.Items, PMesh->VertexSize, _AddEdge_StartVertexIndex);

		LVertex->Flags &= ~E3dVtxTMPSELECTED;
		_AddEdge_Mesh = NULL;
		_AddEdge_StartVertexIndex = -1;
	}
	E3dp_PrintMessage(ELogMESSAGE, 3000, "Cancelled Edge");
}


static void _FinishPolygon(E3dMesh* PMesh, E3dPolyGroup* PPolyGroup, E3dPolygon* PPolygon)
{
	E3dVertex*               LVertices = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;
	E3dVertexNode*           LVertexNode = PPolygon->VertexNodes;
	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;

	EIndex  LI = 0;const EIndex  LN = PPolygon->NumOfVertexNodes;for(;LI < LN;LI++)
	{
		const E3dVertexIndex  LVertexIndex = LVertexNode->VertexID;
//printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LVertexIndex);fflush(stdout);
		if(LVertexIndex > -1)
		{
			E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexIndex);
			LVertex->Flags &= ~E3dVtxTMPSELECTED;
		}
		EStruct_Inc(LVertexNode, LVertexNodeSize);
	}

	PPolygon->Flags &= ~E3dPolygonSELECTED;

	_AddPolygon_PolygonIndex = -1;
	E3dPolygon_FreeContents(&_AddPolygon_TmpPolygon);

	if(LN > 2) E3dp_PrintMessage(ELogMESSAGE, 3000, "Finished Polygon #%d   with %d Vertices", _AddPolygon_PolyGroup->Polygons.Count - 1, PPolygon->NumOfVertices, PPolygon->NumOfExteriorVertices);
	else E3dp_PrintMessage(ELogMESSAGE, 3000, "Cancelled Polygon");
}


//========================================
// Vertex was picked
//========================================
static EChangeFlags _PickVertexB(EOps* POps, E3dPickCallbackStruct* PPickStruct, E3dItem* PPickedItems, E3dMesh* PMesh, E3dVertex* PVertex)
{
	E3dPolyItem*  LPolyItem = (E3dPolyItem*)PPickedItems;
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;

	switch(PPickStruct->Button)
	{
		case Button1:
		case Button3:
			if(PPickStruct->Button == Button3 && (_AddPolygon_PolygonIndex == -1 && _AddPolygon_TmpPolygon.NumOfVertexNodes == 0))
			{
				E3dPolygon_FreeContents(&_AddPolygon_TmpPolygon);
				return(0);
			}

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

			E3dp_PickStop();    // Must stop picking if we change a Geometry

// Make sure this operation is for the Mesh we started with
//
			PVertex->Flags |= E3dVtxTMPSELECTED;

// Start new Polygon
//
			if(_AddPolygon_PolygonIndex == -1 && _AddPolygon_TmpPolygon.NumOfVertexNodes == 0)
			{
				_AddPolygon_Mesh = PMesh;

				if(PMesh->PolyGroups.Count > 0)
				{
					_AddPolygon_PolyGroup = PMesh->PolyGroups.Items[0];
					if(PMesh->Selection == E3dObjectSelPOLYGROUP)
					{
						EIndex  LI = 0;const EIndex  LN = PMesh->PolyGroups.Count;for(;LI < LN;LI++) if(PMesh->PolyGroups.Items[LI]->Selected)
						{
							_AddPolygon_PolyGroup = PMesh->PolyGroups.Items[LI];break;
						}
					}
				}
				else _AddPolygon_PolyGroup = E3dMesh_PolyGroupAdd(PMesh);

				E3dPolygon*  LPolygon = &_AddPolygon_TmpPolygon;
				LPolygon->Normal = E3dCoord3_s(0.0, 1.0, 0.0);

//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);XeApp_Sync(Xe_App);sleep(2);
				LChanged |= _PolygonAddVertexNode(POps, NULL, _AddPolygon_Mesh, _AddPolygon_PolyGroup, LPolygon, LPolyItem->PointIndex);
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);XeApp_Sync(Xe_App);sleep(2);
			}
			else if(PMesh == _AddPolygon_Mesh)
			{
				E3dPolyGroup*  LPolyGroup = _AddPolygon_PolyGroup;
				if(_AddPolygon_PolygonIndex >= LPolyGroup->Polygons.Count)
				{
printf("%s() %s:%d %sINVALID _AddPolygon_PolygonIndex %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)_AddPolygon_PolygonIndex, ECodeNormal);fflush(stdout);
					return(0);
				}
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);

				E3dPolygon*  LPolygon = _AddPolygon_PolygonIndex >= 0 ? LPolyGroup->Polygons.Items + _AddPolygon_PolygonIndex : &_AddPolygon_TmpPolygon;

				LChanged |= _PolygonAddVertexNode(POps, NULL, _AddPolygon_Mesh, LPolyGroup, LPolygon, LPolyItem->PointIndex);
			}
			else E3dp_PrintMessage(ELogWARNING, 5000, "This is not the Mesh and PolyGroup where you started the Polygon");

			if(PPickStruct->Button == Button3)
			{
				E3dPolyGroup*  LPolyGroup = _AddPolygon_PolyGroup;
				if(_AddPolygon_PolygonIndex >= LPolyGroup->Polygons.Count)
				{
printf("%s() %s:%d %sINVALID _AddPolygon_PolygonIndex %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)_AddPolygon_PolygonIndex, ECodeNormal);fflush(stdout);
					return(0);
				}
				else
				{
					E3dPolygon*  LPolygon = _AddPolygon_PolygonIndex >= 0 ? LPolyGroup->Polygons.Items + _AddPolygon_PolygonIndex : &_AddPolygon_TmpPolygon;
//printf("%s() %s:%d  %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)_AddPolygon_PolygonIndex);fflush(stdout);
					_FinishPolygon(PMesh, LPolyGroup, LPolygon);
				}
			}

			E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);

			E3dp_PickResume();  // Resume picking
		return(LChanged);

		case Button2:
			if(PVertex)    // If on a Vertex, MMB: remove Vertex from Polygon(s)
			{
				E3dp_PickStop();    // Must stop picking if we change a Geometry

				_MeshDeleteVertex(PMesh, PVertex);

				E3dp_PickResume();    // Resume picking
				return(E3dChgMeshSHAPE);
			}
		break;
	}


	return(0);
}


//================================================================
// Vertex picked
//================================================================
static unsigned int _PickVertex(EOps* POps, E3dPickCallbackStruct* PPickStruct, E3dItem* PPickedItems, E3dMesh* PMesh, E3dVertex* PVertex, const EBool PNewVertex)
{
	const E3dVertexSize  LVertexSize = PMesh->VertexSize;

// Start new Edge
//
	if(PPickStruct->Modifiers & EShiftMask)
	{
// If there is an Edge(loop) in progress, finish/cancel it
//
		if(_AddEdge_StartVertexIndex >= 0)
		{
			if(_AddEdge_StartVertexIndex < PMesh->Vertices.Count)
			{
				E3dVertex*  LVertex = EStruct_ByIndex(PMesh->Vertices.Items, LVertexSize, _AddEdge_StartVertexIndex);
				LVertex->Flags &= ~E3dVtxTMPSELECTED;
			}
			else
			{
				printf("%s() %s:%d  %s_AddEdge_StartVertexIndex %lld is out of bounds%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)_AddEdge_StartVertexIndex, ECodeNormal);fflush(stdout);
			}
		}
		else
		{
			E3dp_ChangeCompoundMessage(_GetMessage(EditModeADD_EDGE, 0));
		}

		PVertex->Flags |= E3dVtxTMPSELECTED;
		_AddEdge_StartVertexIndex = EStruct_GetIndex(PMesh->Vertices.Items, LVertexSize, PVertex);
		_AddEdge_Mesh = PMesh;
		return(0);
	}
	else if(_AddEdge_StartVertexIndex >= 0)
	{
// Finish new Edge
//
		EIndex  LStartVertexIndex = _AddEdge_StartVertexIndex;

		E3dp_PickStop();    // Must call this when picking is active before we change a Geometry

		const EIndex  LIndex = EStruct_GetIndex(PMesh->Vertices.Items, LVertexSize, PVertex);
		const EBool  LEdgeAdded = _MeshAddEdge(POps, PMesh, LStartVertexIndex, LIndex, TRUE);if(LEdgeAdded)
		{
			E3dp_PrintMessage(ELogMESSAGE, 5000, "Edge added between Vertex %d and %d", LStartVertexIndex, LIndex);

			if(PPickStruct->Button != Button3)
			{
// Start new Edge
//
				_AddEdge_StartVertexIndex = EStruct_GetIndex(PMesh->Vertices.Items, LVertexSize, PVertex);PVertex->Flags |= E3dVtxTMPSELECTED;
				_AddEdge_Mesh = PMesh;
			}
		}

		E3dp_PickResume();  // Resume picking

		return(~E3dUpdateTRIANGULATE);
	}
/*
	else if(_AddPolygon_PolygonIndex >= 0)
	{
		_PickVertexB(PPickStruct, PPickedItems, PMesh, PVertex);
		return(~E3dUpdateTRIANGULATE);
	}
*/
	else if(!PNewVertex)
	{
		return(_PickVertexB(POps, PPickStruct, PPickedItems, PMesh, PVertex));
	}
	return(0);
}


static E3dVertex* _MeshAddVertexOnEdge(E3dMesh* PMesh, const E3dEdge* PEdge, const E3dCoord3* PPosition3, const E3dVertex* PVertexA, const E3dVertex* PVertexB, const E3dCoordinate PU, const EBool PUndoable)
{
	const EIndex          LNewVertexIndex = PMesh->Vertices.Count;
	const E3dVertexSize   LVertexCoreSize = E3dVertexCore_Size(), LVertexSize = PMesh->VertexSize, LVertexSizeAboveCore = LVertexSize - LVertexCoreSize;
	E3dOperatorEditMesh*  LOperator = NULL;
	E3dVertex*            LVertex;

	if(PUndoable)
	{
		LOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);
		E_AssignToMember(PMesh, LOperator,MeshNewT);
		EResourceList_Copy(&PMesh->VertexAttributes, &LOperator->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);
		LOperator->VertexSize = PMesh->VertexSize;
		ENew(LOperator->VerticesToAdd_Indices);LOperator->VerticesToAdd_Indices[0] = LNewVertexIndex;
		LVertex = E3dVertices_Add((E3dVertex*)LOperator->VerticesToAdd, LVertexSize, LOperator->VerticesToAdd_Count, 1, TRUE);

printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
		LOperator->VerticesToAdd = (uint8_t*)LVertex;
		LOperator->VerticesToAdd_Count++;
	}
	else
	{
		E3dMesh_FreeCaches(PMesh);
		LVertex = E3dMesh_VerticesAdd(PMesh, 1);
	}

// Add new Vertex to Mesh
//
	if(LVertex)
	{
		E3dM_VertexInit(LVertex);LVertex->Position = *PPosition3;
		if(LVertexSizeAboveCore > 0)
		{
printf("%s() %s:%d  %sIMPLEMENT ME%s interpolate Mesh Vertex attributes\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
			memset((uint8_t*)LVertex + LVertexCoreSize, 0, LVertexSizeAboveCore);

			E3dAttributes_Interpolate1D(&PMesh->VertexAttributes,  PVertexA, PVertexB, PU,  LVertex);
		}

		E3dEdgePolygonInfo*  LEdgeDescriptors;const EIndex  LNumOfPolygonsOnEdge = E3dMesh_EdgeGetPolygons(PMesh, PEdge, &LEdgeDescriptors, NULL);

//Printf("Edge [%d - %d] on %d Polygon(s)\n", PEdge->Start, PEdge->End, LNumOfPolygonsOnEdge);fflush(stdout);

		EIndex  LPI = 0;for(;LPI < LNumOfPolygonsOnEdge;LPI++)
		{
			EIndex  LStartNodeIndex = -1, LEndNodeIndex = -1;

			E3dEdgePolygonInfo*      LEdgeDescriptor = LEdgeDescriptors + LPI;
			E3dPolygon*              LPolygon = LEdgeDescriptor->Polygon;
			E3dPolyGroup*            LPolyGroup = PMesh->PolyGroups.Items[LEdgeDescriptor->PolyGroupIndex];
			const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

// Find VertexNodes that index PEdge->Start and PEdge->End
//
			{
				EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;for(;LVC < LVN;LVC++)
				{
					if(LVertexNode->VertexID == PEdge->Start)
					{
						LStartNodeIndex = LVC;if(LEndNodeIndex > -1)
						break;
					}
					else if(LVertexNode->VertexID == PEdge->End)
					{
						LEndNodeIndex = LVC;if(LStartNodeIndex > -1)
						break;
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}

			if(LStartNodeIndex > -1 && LEndNodeIndex > -1)
			{
				E3dVertexNode*  LStartVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LStartNodeIndex);
				E3dVertexNode*  LEndVertexNode   = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LEndNodeIndex);

				if(PUndoable)
				{
// Find Polygon contour Edge starting VertexNode (the smaller one, unless it's the last Edge of the contour)
//
					EIndex  LVNI0, LVNI1;
					if(LStartNodeIndex < LEndNodeIndex)
					{
						LVNI0 = LStartNodeIndex;
						LVNI1 = LEndNodeIndex;
					}
					else
					{
						LVNI0 = LEndNodeIndex;
						LVNI1 = LStartNodeIndex;
					}

					if(LVNI1 - LVNI0 > 1) LVNI0 = LVNI1;

					E3dOpPolyGroup*  LOpPolyGroup = E3d_EditMeshAddOpPolyGroup(LOperator,  LPolyGroup, -1, EEditMODIFY);
					E3dOpPolygon*    LOpPolygon = E3dOpPolyGroup_AddPolygon(LOpPolyGroup, LPolygon);
					ENew(LOpPolygon->VertexNodesToModify_Indices);LOpPolygon->VertexNodesToModify_Indices[0] = LVNI0 + 1;

					E3dVertexNode*  LTmpVertexNode;EMallocT(E3dVertexNode*, LVertexNodeSize, LTmpVertexNode);
					LOpPolygon->VertexNodesToModify = (uint8_t*)LTmpVertexNode;
					LOpPolygon->VertexNodesToModify_Count = 1;

					E3dAttributes_Interpolate1D(&LPolyGroup->VertexAttributes,  LStartVertexNode, LEndVertexNode, PU,  LTmpVertexNode);
					LTmpVertexNode->VertexID = LNewVertexIndex;


// Add old Edge for deletion
//
					ENew(LOperator->EdgesToDelete_Indices);LOperator->EdgesToDelete_Indices[0] = PEdge - PMesh->Edges.Items;
					LOperator->EdgesToDelete_Count = 1;

// Add new Edges for addition
//
					ENewNClr(2, LOperator->EdgesToAdd);
					E3dEdge*  LNewEdge = LOperator->EdgesToAdd;

					{
						const EIndex  LS = PEdge->Start, LE = PEdge->End;

						E3dEdge_Copy(PEdge, LNewEdge);E3dEdge_SetVertices(LNewEdge,  LS, LNewVertexIndex);LNewEdge++;
						E3dEdge_Copy(PEdge, LNewEdge);E3dEdge_SetVertices(LNewEdge,  LE, LNewVertexIndex);LNewEdge++;
					}

					LOperator->EdgesToAdd_Count = 2;

//printf("%s() %s:%d  %d - %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LStartNodeIndex, (int)LEndNodeIndex);fflush(stdout);
				}
				else
				{
					E3dVertexNode*  LTmpVertexNode = EMalloc(LVertexNodeSize);if(LTmpVertexNode)
					{
// Interpolate Vertex normals, texture coordinates etc.
// We must do this before adding the new VertexNode, because the LPolygon->VertexNodes array might change.
//
						E3dAttributes_Interpolate1D(&LPolyGroup->VertexAttributes,  LStartVertexNode, LEndVertexNode, PU,  LTmpVertexNode);
						LTmpVertexNode->VertexID = LNewVertexIndex;

						{
							E3dVertexNode*  LVertexNode = E3dPolygon_InsertVertexNode(LPolygon, LVertexNodeSize, LNewVertexIndex, LStartNodeIndex, LEndNodeIndex);
							memcpy(LVertexNode, LTmpVertexNode, LVertexNodeSize);
						}

						E3d_PolygonCorrectHoleVertexCounts(LPolygon, LVertexNodeSize);

						E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  PMesh->Vertices.Items, PMesh->Vertices.Count, LVertexSize);
						E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  PMesh->Vertices.Items, PMesh->Vertices.Count, LVertexSize,  TRUE);

// Set the normals of the VertexNodes
//
						const int  LNormalOffset = E3dVertexNodeAttribute_Offset(&LPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);if(LNormalOffset >= 0)
						{
							EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;E3dCoord3*  LNormal = (E3dCoord3*)((uint8_t*)LPolygon->VertexNodes + LNormalOffset);for(;LVC < LVN;LVC++, EStruct_Inc(LNormal, LVertexNodeSize))
							{
								*LNormal = LPolygon->Normal;
							}
						}

						EFree(LTmpVertexNode);
					}
				}
			}
		}

		EFreeChk(LEdgeDescriptors);

		if(PUndoable)
		{
// Run operator
//
			EOps  LOps;EOps_Init(&LOps);
			EList_ObjectAddA_s(LOps.Operators, 1, LOperator);

			LOperator->Class->Func(&LOps, (EOperator*)LOperator, EOpDO);

			if(LOps.Operators.Items)
			{
				E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOps.OpNodes,  NULL);

// Add Operator to LOps
//
				E3dpUndoStack_AddOps(&LOps, E3dLng_Mesh_add_Vertex);  // Add OperatorList to undo stack
			}

			EOps_FreeContents(&LOps);
		}
		else
		{
			const EIndex  LEdgeIndex = PEdge - PMesh->Edges.Items;
			E3dEdge*      LNewEdge = EList_StructInsertA_s(PMesh->Edges, 16, LEdgeIndex + 1);
			E3dEdge*      LOldEdge = PMesh->Edges.Items + LEdgeIndex;

			E3dEdge_Copy(LOldEdge, LNewEdge);
			LNewEdge->Start = LNewVertexIndex;
			LNewEdge->End   = LOldEdge->End;

			LOldEdge->End = LNewVertexIndex;
			E3dMesh_PolyGroupsEdgesFromMeshEdges(PMesh, TRUE, FALSE);
		}
	}

	return(LVertex);
}


static void _OnEdge(E3dPickCallbackStruct* PPickStruct, E3dMesh* PMesh, E3dEdge* PEdge, E3dItem* PPickedItems)
{
	E3dScene*     LScene = E3d_Scene;
	const EIndex  LEdgeIndex = PEdge - PMesh->Edges.Items;

	switch(PPickStruct->Button)
	{
		default:    // Pointer motion
			{
				unsigned int  LDescription;const EIndex  LNumOfPolygonsOnEdge = E3dMesh_EdgeGetPolygons(PMesh, PEdge, NULL, &LDescription);

				if(LNumOfPolygonsOnEdge == 2)
				{
					E3dp_ChangeCompoundMessage(_GetMessage(EditModeADD_VERTEX_ON_EDGE_DELETE_EDGE, (int)LEdgeIndex));
				}
				else
				{
					if(LDescription & E3dPolyEDGE_IS_IN_HOLE_CONTOUR)
					{
						E3dp_ChangeCompoundMessage(_GetMessage(EditModeADD_VERTEX_ON_EDGE_DELETE_HOLE, (int)LEdgeIndex));
//printf("%s() %s:%d HOLE\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					}
					else
					{
						E3dp_ChangeCompoundMessage(_GetMessage(EditModeADD_VERTEX_ON_EDGE, (int)LEdgeIndex));
//printf("%s() %s:%d EXTERIOR\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
					}
				}
			}
		break;

		case Button1:
			if(PEdge)
			{
// Active Edge: Add Vertex on Edge at click-position
//
				E3dRay      LRay;
				E3dModel*   LCameraModel;
				E3dCamera*  LCamera = E3dWindow_CastPrimaryRay(PPickStruct->E3DWindow,  PPickStruct->PtrX, PPickStruct->PtrY,  &LRay, &LCameraModel);if(LCamera)
				{
					E3dModel*   LModel = E3dObject_GetSelectingModel((E3dObject*)PMesh, TRUE);
					E3dMatrix   LWorldToLocalMatrix;
					const E3dVertexSize  LVertexSize = PMesh->VertexSize;
					E3dVertex*  LStartVertex = EStruct_ByIndex(PMesh->Vertices.Items, LVertexSize, PEdge->Start);
					E3dVertex*  LEndVertex   = EStruct_ByIndex(PMesh->Vertices.Items, LVertexSize, PEdge->End);

					E3dMatrix_Invert3x4(LModel->LocalToWorldMatrix, LWorldToLocalMatrix);

// Transform the ray into the Mesh's local space
//
					EMatrix_Transform3x4_v(LWorldToLocalMatrix,  LRay.Origin);

// The ray direction is a unit VECTOR, so transform it with the top-left 3x3
// sub-matrix
//
					EMatrix_Transform3x3_v(LWorldToLocalMatrix, LRay.Direction);


					E3dCoord3            LClosestPoint;
					E3dCoordinate        LU;
					const E3dCoordinate  LDst = E3dRay_ClosestPointOnSegment(&LRay,  LStartVertex->Position.X, LStartVertex->Position.Y, LStartVertex->Position.Z,  LEndVertex->Position.X, LEndVertex->Position.Y, LEndVertex->Position.Z,  &LClosestPoint, &LU);if(LDst >= 0.0)
					{
						E3dWindow*  L3DWindow = E3dp_Active3DWindow;

						E3dpWindow*  L3DpWindow = E_DynamicCast(L3DWindow, E3dpWindow);if(L3DpWindow)
						{
							E3dp_PickStop();    // Must stop picking if we change a Geometry

							if(L3DpWindow->Settings.UseEdgeSteps)
							{
								const E3dCoordinate  LSnapV = 1.0 / (E3dCoordinate)L3DpWindow->Settings.EdgeSteps;
								const E3dCoordinate  LDX = LEndVertex->Position.X - LStartVertex->Position.X, LDY = LEndVertex->Position.Y - LStartVertex->Position.Y, LDZ = LEndVertex->Position.Z - LStartVertex->Position.Z;

								LU = E3d_SnapCoordinateTo(LU, LSnapV);

// Stay away from the end points if possible. It doesn't
// make much sense to add a new Vertex there...
//
#define EPSILON  0.001

								if(LU < EPSILON) LU = LSnapV;
								else if(LU > 1.0 - EPSILON) LU = 1.0 - LSnapV;

								LClosestPoint.X = LStartVertex->Position.X + LDX * LU;
								LClosestPoint.Y = LStartVertex->Position.Y + LDY * LU;
								LClosestPoint.Z = LStartVertex->Position.Z + LDZ * LU;
							}

							E3dMesh_FreeCaches(PMesh);

							E3dVertex*  LVertex = _MeshAddVertexOnEdge(PMesh, PEdge, &LClosestPoint, LStartVertex, LEndVertex, LU, TRUE);

							E3dp_PickResume();  // Resume picking

							_PickVertex(NULL, PPickStruct, PPickedItems, PMesh, LVertex, TRUE);

							if(LVertex) E3dp_PrintMessage(ELogMESSAGE, 3000, "Added Vertex #%d", PMesh->Vertices.Count - 1);

							E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);
							E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
						}
					}
				}
			}
		break;

		case Button2:  // Add bisector Vertex on Edge
			if(PEdge)
			{
				const E3dVertexSize  LVertexSize = PMesh->VertexSize;
				E3dVertex*           LStartVertex = EStruct_ByIndex(PMesh->Vertices.Items, LVertexSize, PEdge->Start);
				E3dVertex*           LEndVertex = EStruct_ByIndex(PMesh->Vertices.Items, LVertexSize, PEdge->End);
				E3dCoord3            LPosition;
				E3dCoordinate        LU = 0.5, LUInv = 1.0 - LU;

				E3dp_PickStop();		// Must stop picking if we change a Geometry

				LPosition.X = LStartVertex->Position.X * LUInv + LEndVertex->Position.X * LU;
				LPosition.Y = LStartVertex->Position.Y * LUInv + LEndVertex->Position.Y * LU;
				LPosition.Z = LStartVertex->Position.Z * LUInv + LEndVertex->Position.Z * LU;

				E3dMesh_FreeCaches(PMesh);
				E3dVertex*  LVertex = _MeshAddVertexOnEdge(PMesh, PEdge, &LPosition, LStartVertex, LEndVertex, LU, TRUE);

				if(LVertex) E3dp_PrintMessage(ELogMESSAGE, 3000, "Added Vertex #%d", PMesh->Vertices.Count - 1);

				E3dp_PickResume();  // Resume picking

				_PickVertex(NULL, PPickStruct, PPickedItems, PMesh, LVertex, TRUE);

				E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);
				E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);

				return;
			}
		break;

		case Button3:  // Delete Edge
			if(PEdge)
			{
// Must stop picking if we change a Geometry
//
				E3dp_PickStop();

				{
					E3dEdgePolygonInfo*  LEdgeDescriptors;unsigned int  LDescription;const EIndex  LNumOfPolygonsOnEdge = E3dMesh_EdgeGetPolygons(PMesh, PEdge, &LEdgeDescriptors, &LDescription);
					if(LNumOfPolygonsOnEdge == 1)
					{
						if(LDescription & E3dPolyEDGE_IS_IN_HOLE_CONTOUR)
						{
//printf("%s() %s:%d %sHOLE%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
// Delete hole
//
							E3dEdgePolygonInfo*  LEdgeDescriptor = LEdgeDescriptors;
							E3dPolygon*          LPolygon = LEdgeDescriptor->Polygon;
							E3dPolyGroup*        LPolyGroup = PMesh->PolyGroups.Items[LEdgeDescriptor->PolyGroupIndex];
							const int            LVertexNodeSize = LPolyGroup->VertexNodeSize;
							E3dVertexNode*       LContourStartVertexNode = NULL;
							E3dVertexNode*       LContourEndVertexNode = NULL;
							const EIndex         LEdgeStartVertexNodeIndex = LEdgeDescriptor->StartVertexNodeIndex;

// Find contour start
//
							{
								E3dVertexNode*  LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LEdgeStartVertexNodeIndex - 1);
								EIndex  LVC = LEdgeStartVertexNodeIndex - 1;for(;LVC > 0;LVC--)
								{
									if(LVertexNode->VertexID < 0) LContourStartVertexNode = LVertexNode;
									EStruct_Dec(LVertexNode, LVertexNodeSize);
								}
							}

// Find contour start
//
							{
								E3dVertexNode*  LVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LEdgeStartVertexNodeIndex + 1);
								const EIndex    LVN = LPolygon->NumOfVertexNodes;
								LContourEndVertexNode = EStruct_ByIndex(LPolygon->VertexNodes, LVertexNodeSize, LVN);
								EIndex  LVC = LEdgeStartVertexNodeIndex + 1;for(;LVC < LVN;LVC++)
								{
									if(LVertexNode->VertexID < 0) LContourEndVertexNode = LVertexNode;
									EStruct_Inc(LVertexNode, LVertexNodeSize);
								}
							}

							if(LContourStartVertexNode)
							{
//								const EIndex	LNumOfVertexNodesInContour = -LContourStartVertexNode->VertexID;
								const EIndex	LNumOfVertexNodesInContour = ((uint8_t*)LContourEndVertexNode - (uint8_t*)LContourStartVertexNode) / LVertexNodeSize - 1;

								memmove(LContourStartVertexNode, LContourEndVertexNode, (uint8_t*)LPolygon->VertexNodes + LPolygon->NumOfVertexNodes * LVertexNodeSize - (uint8_t*)LContourEndVertexNode);
								LPolygon->NumOfVertexNodes -= (LNumOfVertexNodesInContour + 1);
								LPolygon->NumOfVertices -= LNumOfVertexNodesInContour;
							}

							E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  PMesh->Vertices.Items, PMesh->Vertices.Count, PMesh->VertexSize);
							E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  PMesh->Vertices.Items, PMesh->Vertices.Count, PMesh->VertexSize,  TRUE);

							E3dMesh_RecomputeNormals(PMesh, TRUE, TRUE);

// E3dFIXME - we could manually remove the Edges corresponding to the contour...
//
//							E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateMeshSHAPE|E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
							E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateMeshSHAPE|E3dUpdateTRIANGULATE), E3dCallALL,  LScene);

							E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
						}
						else
						{
							E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_boundary_Edge_Try_deleting_Vertices);
//printf("%s() %s:%d %sEXTERIOR%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
						}
					}
					else
					{
//printf("%s() %s:%d %sDELETE EDGE%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
						switch(_MeshDeleteEdge(PMesh, PEdge))
						{
							case 0:    // Success
								E3dMesh_RecomputeNormals(PMesh, TRUE, TRUE);

//								const u_long64  LCyclesStart = _x86_GetCycleCount();

								E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateMeshSHAPE|E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);

//								const u_long64  LCyclesEnd = _x86_GetCycleCount();printf("Cycles %lld\n", LCyclesEnd - LCyclesStart);fflush(stdout);

								E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
							break;

							case E3dEdgePOLYS_IN_DIFFERENT_POLYGROUPS:  E3dp_PrintMessage(ELogWARNING, 5000, E3dLng_Can_not_delete_Edge_that_separates_2_PolyGroups);break;
							case E3dEdgeIN_HOLE:                        E3dp_PrintMessage(ELogWARNING, 5000, "This Edge is part of a hole contour");break;
							case E3dEdgeVERTEX_WITH_VALENCE_2:          E3dp_PrintMessage(ELogWARNING, 5000, "This Edge is made of multiple segments. Delete dividing Vertices first");break;
						}
					}

					EFreeChk(LEdgeDescriptors);
				}

				E3dp_PickResume();  // Resume picking
			}
		break;
	}

	return;
}


static void _OnVertex(E3dPickCallbackStruct* PPickStruct, E3dMesh* PMesh, E3dVertex* PVertex, E3dItem* PPickedItems)
{
	E3dScene*         LScene = E3d_Scene;
	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color      = Xe_ForegroundRGBA8pColor;
	const uint32_t    LButtonNameRGBAp8Color = LPrefs->ButtonNameRGBAp8Color;

	switch(PPickStruct->Button)
	{
		default:		// Pointer motion
			if(_AddPolygon_PolygonIndex >= 0)  E3dp_ChangeCompoundMessage(XeString_NewVa(TRUE,
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Editing Polygon   ",
           E3dp_LMouseButtonS,  XeTEXT, ": add this Vertex    ",
           E3dp_MMouseButtonS,  XeTEXT, ": remove this Vertex    ",
           E3dp_RMouseButtonS,  XeTEXT, ": finish Polygon    ",
           XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
           0));
			else if(_AddEdge_StartVertexIndex >= 0) E3dp_ChangeCompoundMessage(XeString_NewVa(TRUE,
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Adding Edge   ",
           E3dp_LMouseButtonS,  XeTEXT, ": pick end Vertex   ",
           E3dp_MMouseButtonS,  XeTEXT, ": finish Edge    ",
           XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
           0));
			else E3dp_ChangeCompoundMessage(XeString_NewVa(TRUE,
           XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Edit polygons  ",
           E3dp_LMouseButtonS,  XeTEXT, ": start new Polygon   ",
           E3dp_LMouseButtonS,  XeTEXT, "+Shift",  XeTEXT, ": start new Edge   ",
           E3dp_MMouseButtonS,
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, ": delete Vertex   ",
           XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
           XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
           0));
		break;

		case Button1:
		case Button2:
		case Button3:
			{
				EOps  LOps;EOps_Init(&LOps);
				const EChangeFlags  LUpdateFlags = _PickVertex(&LOps, PPickStruct, PPickedItems, PMesh, PVertex, FALSE);

//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
				if(LOps.Operators.Count)
				{
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
// E3dFIXME
// Change Ops Name depending on what we're doing (adding VertexNode to Polygon being edited, adding Vertex to Edge being edited etc.)
					E3dpUndoStack_AddOps(&LOps, E3dLng_Mesh_add_Vertex);  // Add OperatorList to undo stack
					EOps_FreeContents(&LOps);
				}

				if(LUpdateFlags) E3dWindows_RedrawByChangeFlags(LScene, LUpdateFlags);
			}
		break;
	}
}


#ifdef E3dADDVERTEX_UNDOABLE
#define _MeshVertexAdd()\
	E3dOperatorEditMesh*  LAddVertexOperator = (E3dOperatorEditMesh*)EOperator_New(E3dOperatorEditMesh_class);\
	E_AssignToMember(LMesh, LAddVertexOperator,MeshNewT);\
	const E3dVertexSize  LVertexSize = LMesh->VertexSize;const E3dVertexSize  LVertexSizeAboveCore = LVertexSize - LVertexCoreSize;\
	EResourceList_Copy(&LMesh->VertexAttributes, &LAddVertexOperator->VertexAttributes,  EResSEMANTIC_STATIC | EResUI_STATIC, EResARGS);\
	LAddVertexOperator->VertexSize = LMesh->VertexSize;\
	ENew(LAddVertexOperator->VerticesToAdd_Indices);LAddVertexOperator->VerticesToAdd_Indices[0] = LMesh->Vertices.Count;\
	E3dVertex*  LNewVertex = E3dVertices_Add((E3dVertex*)LAddVertexOperator->VerticesToAdd, LMesh->VertexSize, LAddVertexOperator->VerticesToAdd_Count, 1, FALSE);\
	if(LVertexSizeAboveCore > 0) memset((uint8_t*)LNewVertex + LVertexCoreSize, 0, LVertexSizeAboveCore)
#else
#define _MeshVertexAdd()\
	E3dVertex*  LNewVertex = E3dMesh_VerticesAdd(LMesh, 1);\
	if(LVertexSizeAboveCore > 0) memset((uint8_t*)LNewVertex + LVertexCoreSize, 0, LVertexSizeAboveCore)
#endif  // E3dADDVERTEX_UNDOABLE


//================================================================
// Polygon editing's pick callback handler
//================================================================
static void _PickCB_PolyonVertexOrEdge(E3dPickCallbackStruct* PPickStruct, EPointer PClientData,  const EIndex PNumOfItems, E3dItem* PPickedItems)
{
	E3dScene*  LScene = E3d_Scene;
	if(PPickStruct->Reason == E3dCR_ABORT)
	{
		_AddPolygon_Mesh = NULL;
		_AddPolygon_PolyGroup = NULL;
		_AddPolygon_PolygonIndex = -1;

		_AddEdge_Mesh = NULL;
		_AddEdge_StartVertexIndex = -1;


		E3dp_DrawMeshVerticesIfPolyGroupIsSelected = FALSE;
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
		return;
	}

	if(PPickStruct->Button != 0 || PPickStruct->HighlightedItemChanged)
	{
		{
			E3dMesh*       LMesh = NULL;
//			E3dPolyGroup*  LPolyGroup = NULL;
			E3dEdge*       LEdge = NULL;
			E3dVertex*     LMVertex = NULL;
			EIndex         LIndex = -1;

			if(PPickedItems)
			{
				if(PPickedItems[0].Item.Type == E3dItemEDGE)
				{
					E3dEdgeItem*  LEdgeItem = (E3dEdgeItem*)PPickedItems;

					LMesh = (E3dMesh*)(LEdgeItem->Object);
//					LPolyGroup = LEdgeItem->PolyGroup;
					LEdge = LEdgeItem->Edge;
				}

				if(PPickedItems[0].Item.Type == E3dItemVERTEX)
				{
					E3dPolyItem*  LPolyItem = (E3dPolyItem*)PPickedItems;

					LMesh = (E3dMesh*)(LPolyItem->Object);
					LMVertex = LPolyItem->Vertex;LIndex = LPolyItem->PointIndex;
				}
			}

(void)LIndex;

// Edge operations
//
			if(LEdge)
			{
				_OnEdge(PPickStruct, LMesh, LEdge, PPickedItems);
				return;
			}

			if(LMVertex)
			{
				_OnVertex(PPickStruct, LMesh, LMVertex, PPickedItems);
				return;
			}
		}

		const E3dpPrefs*  LPrefs = &E3dp_Prefs;
		const uint32_t    LLabelRGBAp8Color      = Xe_ForegroundRGBA8pColor;
		const uint32_t    LButtonNameRGBAp8Color = LPrefs->ButtonNameRGBAp8Color;
		const E3dVertexSize  LVertexCoreSize = E3dVertexCore_Size();

		switch(PPickStruct->Button)
		{
			default:    // Pointer motion
				if(_AddPolygon_PolygonIndex >= 0)
				{
					E3dp_ChangeCompoundMessage(XeString_NewVa(TRUE,
					 XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Create Polygon   ",
					 E3dp_LMouseButtonS,  XeTEXT, ": add new Vertex   ",
					 E3dp_RMouseButtonS,  XeTEXT, ": finish Polygon   ",
					 XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
					 XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
					 0));
				}
				else
				{
					if(_AddEdge_StartVertexIndex >= 0) E3dp_ChangeCompoundMessage(XeString_NewVa(TRUE,
					 XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, "Adding Edge   ",
					 E3dp_RMouseButtonS,  XeTEXT, ": cancel Edge   ",
					 XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",
					 XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeTEXT, E3dLng_end_mode,
					 0));
					else E3dp_ChangeCompoundMessage(_GetMessage(EditModeEDIT_POLYGONS, 0));
				}
			break;

			case Button1:
				{
					EOps  LOps;EOps_Init(&LOps);

// Add new Vertex to the Mesh and add it to the Polygon being edited
//
// If an Edge is being added, return
//
					if(_AddEdge_StartVertexIndex >= 0) return;

// If no Polygon is being edited, return
//
					if(_AddPolygon_PolygonIndex == -1 && _AddPolygon_TmpPolygon.NumOfVertexNodes == 0) return;

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

					E3dMesh*       LMesh      = _AddPolygon_Mesh;
					E3dPolyGroup*  LPolyGroup = _AddPolygon_PolyGroup;

// The first new Vertex will be the intersection of the probing ray and the plane
// that goes through the Vertex of the Mesh closest to the Camera and is perpendicular
// to the line-of-sight
//
					E3dRay  LRay;E3dModel*  LCameraModel;E3dCamera*  LCamera = E3dWindow_CastPrimaryRay(PPickStruct->E3DWindow, PPickStruct->PtrX, PPickStruct->PtrY, &LRay, &LCameraModel);if(LCamera)
					{
						E3dMatrix  LViewerToWorldMatrix;E3dMatrix_Copy(LCameraModel->LocalToWorldMatrix, LViewerToWorldMatrix);
						E3dMatrix	 LWorldToViewerMatrix;E3dMatrix_Invert3x4(LViewerToWorldMatrix, LWorldToViewerMatrix);

						E3dModel*  LModel = E3dObject_GetSelectingModel((E3dObject*)LMesh, TRUE);
						E3dMatrix  LMatrix;E3dMatrix_Invert3x4(LModel->LocalToWorldMatrix, LMatrix);

						E3dp_PickStop();  // Must stop picking if we change a Geometry

						if(_AddPolygon_PolygonIndex == -1)
						{
// No Polygon created yet (less than 3 Vertices specified so far)
//
							E3dMatrix  LLocalToViewerMatrix;

							E3dPolygon*  LPolygon = &_AddPolygon_TmpPolygon;
							LPolygon->Normal = E3dCoord3_s(0.0, 1.0, 0.0);

// Get Vertex closest to the Camera
//
							if(LModel)
							{
								E3dMatrix_Copy(LWorldToViewerMatrix, LLocalToViewerMatrix);
								E3dMatrix_Mult4x4(LModel->LocalToWorldMatrix, LLocalToViewerMatrix);
							}
							else return;

							E3dVertex*     LNearestVertex = NULL;
							E3dCoordinate  LNearestZ = 0.0;
							E3dPlane       LPlane;

							{
								EIndex  LVC = 0;const EIndex  LVN = LMesh->Vertices.Count;E3dVertex*  LVertex = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize))
								{
									E3dCoord3  L;EMatrix_Transform3x4_vv(LLocalToViewerMatrix,  LVertex->Position,  L);

// Make sure it's in front of the Camera
//
									if(L.Z < 0.0)
									{
										if(LNearestVertex == NULL)
										{
											LNearestZ = L.Z;LNearestVertex = LVertex;
										}
										else if(L.Z > LNearestZ)
										{
											LNearestZ = L.Z;LNearestVertex = LVertex;
										}
									}
								}
							}

							{
								E3dCoord3  L;
								if(LNearestVertex)
								{
									EMatrix_Transform3x4_vv(LModel->LocalToWorldMatrix, LNearestVertex->Position,  L);
								}
								else
								{
// If no nearest Vertex was found (e.g. the Mesh is behind the Camera),
// we'll just pick the point on the ray at t = 1
//
									L.X = LRay.Origin.X + LRay.Direction.X;
									L.Y = LRay.Origin.Y + LRay.Direction.Y;
									L.Z = LRay.Origin.Z + LRay.Direction.Z;
								}

								E3dCamera_GetViewPlaneThroughPoint(LCameraModel,  L.X, L.Y, L.Z,  &LPlane);
							}

// t is the parameter for the ray where it intersects the plane
//
							const E3dCoordinate  LT = (LPlane.D - LPlane.A * LRay.Origin.X - LPlane.B * LRay.Origin.Y - LPlane.C * LRay.Origin.Z) / (LPlane.A * LRay.Direction.X + LPlane.B * LRay.Direction.Y + LPlane.C * LRay.Direction.Z);

//printf("Plane: %f %f %f %f\n", LPlane.A, LPlane.B, LPlane.C, LPlane.D);fflush(stdout);

// Add new Vertex to Mesh
//
							{
								_MeshVertexAdd();

								if(LNewVertex)
								{
printf("%s() %s:%d %sADD VERTEX%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
// Now transform this position into the Mesh's local system
//
									EMatrix_Transform3x4_sv(LMatrix, LRay.Origin.X + LT * LRay.Direction.X,  LRay.Origin.Y + LT * LRay.Direction.Y,  LRay.Origin.Z + LT * LRay.Direction.Z,  LNewVertex->Position);

									LNewVertex->Flags = E3dVtxVISIBLE|E3dVtxTMPSELECTED;

#ifdef E3dADDVERTEX_UNDOABLE
// Run Operator
//
									LAddVertexOperator->Class->Func(&LOps, (EOperator*)LAddVertexOperator, EOpDO);

// Add Operator to LOps
//
									EList_ObjectAddA_s(LOps.Operators, 1, LAddVertexOperator);
#endif  // E3dADDVERTEX_UNDOABLE

									_PolygonAddVertexNode(&LOps, NULL, LMesh, LPolyGroup, LPolygon, LMesh->Vertices.Count - 1);

									E3dPolygon_Triangulate(LPolygon, LPolyGroup->VertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize,  TRUE);
								}
							}
						}
						else
						{
							if(_AddPolygon_PolygonIndex < 0 || _AddPolygon_PolygonIndex >= LPolyGroup->Polygons.Count)
							{
printf("%s() %s:%d %sINVALID _AddPolygon_PolygonIndex %lld%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)_AddPolygon_PolygonIndex, ECodeNormal);fflush(stdout);
								return;
							}

							E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items + _AddPolygon_PolygonIndex;

							switch(LPolygon->NumOfVertexNodes)
							{
// If one Vertex was already specified for the Polygon, get a plane goes through it and intersects the line-of-sight.
// The new Vertex will be at the intersection of this plane and the ray.
//
								case 1:
								case 2:
									{
										E3dPlane  LPlane;

										{
											const E3dVertex*  LVertex = EStruct_ByIndex(LMesh->Vertices.Items, LMesh->VertexSize, LPolygon->VertexNodes[0].VertexID);
											E3dCoord3         L;EMatrix_Transform3x4_vv(LModel->LocalToWorldMatrix,  LVertex->Position,  L);

											E3dCamera_GetViewPlaneThroughPoint(LCameraModel,  L.X, L.Y, L.Z,  &LPlane);
										}

// t is the parameter for the ray where it intersects the plane
//
										const E3dCoordinate  LT = (LPlane.D - LPlane.A * LRay.Origin.X - LPlane.B * LRay.Origin.Y - LPlane.C * LRay.Origin.Z) / (LPlane.A * LRay.Direction.X + LPlane.B * LRay.Direction.Y + LPlane.C * LRay.Direction.Z);


// Add new Vertex to Mesh
//
										_MeshVertexAdd();

										if(LNewVertex)
										{
printf("%s() %s:%d %sADD VERTEX%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
// Now transform this position into the Mesh's local system
//
											EMatrix_Transform3x4_sv(LMatrix,  LRay.Origin.X + LT * LRay.Direction.X, LRay.Origin.Y + LT * LRay.Direction.Y, LRay.Origin.Z + LT * LRay.Direction.Z,  LNewVertex->Position);

											LNewVertex->Flags = E3dVtxVISIBLE|E3dVtxTMPSELECTED;

#ifdef E3dADDVERTEX_UNDOABLE
											_PolygonAddVertexNode(&LOps, LAddVertexOperator, LMesh, LPolyGroup, LPolygon, LMesh->Vertices.Count - 1);

// Run Operator
//
											LAddVertexOperator->Class->Func(&LOps, (EOperator*)LAddVertexOperator, EOpDO);

// Add Operator to LOps
//
											EList_ObjectAddA_s(LOps.Operators, 1, LAddVertexOperator);
#else
											_PolygonAddVertexNode(NULL, NULL,               LMesh, LPolyGroup, LPolygon, LMesh->Vertices.Count - 1);
#endif


											if(LPolygon->NumOfVertexNodes == 3)
											{
											}
										}
									}
								break;

// If the Polygon already has at least 3 Vertices, the new Vertex will be
// at the intersection of the Polygon's plane and the ray.
//
								default:
									{
										E3dPlane  LPlane;E3dPolygon_TrGetPlane(LPolygon,  LMesh->Vertices.Items, LMesh->VertexSize,  LModel->LocalToWorldMatrix, &LPlane);

//printf("Plane: %f,%f,%f,%f\n", LPlane.A, LPlane.B, LPlane.C, LPlane.D);fflush(stdout);

// t is the parameter for the ray where it intersects the plane
//
										const E3dCoordinate  LT = (LPlane.D - LPlane.A * LRay.Origin.X - LPlane.B * LRay.Origin.Y - LPlane.C * LRay.Origin.Z) / (LPlane.A * LRay.Direction.X + LPlane.B * LRay.Direction.Y + LPlane.C * LRay.Direction.Z);

// Add new Vertex to Mesh
//
										{
											_MeshVertexAdd();

											if(LNewVertex)
											{
// Now transform this position into the Mesh's local system
//
												EMatrix_Transform3x4_sv(LMatrix,  LRay.Origin.X + LT * LRay.Direction.X, LRay.Origin.Y + LT * LRay.Direction.Y, LRay.Origin.Z + LT * LRay.Direction.Z,  LNewVertex->Position);
												LNewVertex->Flags = E3dVtxVISIBLE|E3dVtxTMPSELECTED;

#ifdef E3dADDVERTEX_UNDOABLE
												_PolygonAddVertexNode(&LOps, LAddVertexOperator, LMesh, LPolyGroup, LPolygon, LMesh->Vertices.Count - 1);
// Run Operator
//
												LAddVertexOperator->Class->Func(&LOps, (EOperator*)LAddVertexOperator, EOpDO);

// Add Operator to LOps
//
												EList_ObjectAddA_s(LOps.Operators, 1, LAddVertexOperator);

#else
												_PolygonAddVertexNode(&LOps, NULL,               LMesh, LPolyGroup, LPolygon, LMesh->Vertices.Count - 1);
#endif
												E3dPolygon_Triangulate(LPolygon, LPolyGroup->VertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize,  TRUE);
											}
										}
									}
								break;
							}
						}


						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);

						E3dp_PickResume();  // Resume picking

						if(LOps.Operators.Count)
						{
							E3dpUndoStack_AddOps(&LOps, E3dLng_Mesh_add_Vertex);  // Add OperatorList to undo stack
							EOps_FreeContents(&LOps);
						}
					}
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
				}
			return;

			case Button3:
				E3dp_PickStop();    // Must stop picking if we change a Geometry

// Finish/cancel Polygon?
//
				if(_AddPolygon_PolygonIndex >= 0 || _AddPolygon_TmpPolygon.NumOfVertexNodes)
				{
					E3dPolyGroup*  LPolyGroup = _AddPolygon_PolyGroup;
					if(_AddPolygon_PolygonIndex >= 0)
					{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						if(_AddPolygon_PolygonIndex < LPolyGroup->Polygons.Count) _FinishPolygon(_AddPolygon_Mesh, _AddPolygon_PolyGroup, LPolyGroup->Polygons.Items + _AddPolygon_PolygonIndex);
					}
					else
					{
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						_FinishPolygon(_AddPolygon_Mesh, _AddPolygon_PolyGroup, &_AddPolygon_TmpPolygon);
					}
				}

// Finish/cancel Edge?
//
				if(_AddEdge_StartVertexIndex >= 0) _FinishEdge(_AddEdge_Mesh);

				E3dp_PickResume();    // Resume picking

				E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
			break;
		}
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_CreateEditPolygon(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	E3dItem    LItem;

	if(E3dScene_GetSingleSelection(LScene, &LItem, E3dItemMESH|E3dItemPOLYGROUP, 0) != E3dItemNONE)
	{
		E3dp_ResetWorkModes();

		switch((EPointerInt)PClientData)
		{
			case CR_CREATE_POLYGONS:
				if(_CreatePolygonMessage == NULL) _CreatePolygonMessage = _GetMessage(EditModeCREATE_POLYGONS, 0);
				if(E3dp_PickRequest(XeString_Copy(_CreatePolygonMessage), E3dItemVERTEX|E3dItemEDGE,  _PickCB_PolyonVertexOrEdge, NULL,  0, TRUE) == E_SUCCESS)
				{
					E3dp_DrawMeshVerticesIfPolyGroupIsSelected = TRUE;
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
				}
			break;

			case CR_EDIT_POLYGONS:
				if(_EditPolygonMessage == NULL) _EditPolygonMessage = _GetMessage(EditModeEDIT_POLYGONS, 0);
				if(E3dp_PickRequest(XeString_Copy(_EditPolygonMessage), E3dItemVERTEX|E3dItemEDGE,  _PickCB_PolyonVertexOrEdge, NULL,  0, TRUE) == E_SUCCESS)
				{
					E3dp_DrawMeshVerticesIfPolyGroupIsSelected = TRUE;
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
				}
			break;
		}

	}
	else E3dp_PrintMessage(ELogWARNING, 3000, "Select a Mesh or a PolyGroup");
}



typedef struct
{
	EList(, E3dPolygon*, Polygons);
	EBool  Tested;
} E3dPolyGroupEntry;


//========================================================================================================
// Make Polygon winding orders consistent within a Mesh
//
// If PPolyGroup is not NULL, only process PPolyGroup. Otherwise process all PolyGroups of PMesh.
// Return value
//  The number of Polygons inverted
//========================================================================================================
static EIndex _E3dMesh_FixPolygonDirections(E3dMesh* PMesh, E3dPolyGroup* PPolyGroup, E3dScene* PScene)
{
	const EIndex  LPGN = PMesh->PolyGroups.Count;if(LPGN > 0)
	{
		E3dScene*    LScene = E3d_Scene;
		EIndex       LPolysInverted = 0;
		const ETime  LStartTime = E_GetMilliseconds();ETime  LLastProgressbarUpdateTime = 0;
		EBool        LProgressBarOn = FALSE;

		{
			EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
			{
				E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];

				EIndex  LI = 0;const EIndex  LN = LPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LI < LN;LI++, LPolygon++)
				{
// E3dPolygon_GetAllConnectedPolygons uses these marker flags, so we need to clear them first.
// - E3dPolygonMARK0 indicates that a Polygon has been tested for neighbors
// - E3dPolygonMARK1 indicates that a Polygon is already in the output list
//
					LPolygon->Flags &= ~(E3dPolygonMARK0|E3dPolygonMARK1);
				}
			}
		}

		if(PMesh->Selection == E3dObjectSelPOLYGROUP)
		{
			EIndex  LPGI = 0;const EIndex  LGN = PMesh->PolyGroups.Count;for(;LPGI < LGN;LPGI++)
			{
				E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];
				if(LPolyGroup->Selected)
				{
				}
			}
		}
		else
		{
			E3dPolyGroupEntry  LUnreachedPolygonLists[LPGN];memset(LUnreachedPolygonLists, 0, sizeof(LUnreachedPolygonLists));
			EIndex             LTotalPolygons = 0;

			if(PPolyGroup) LTotalPolygons += PPolyGroup->Polygons.Count;
			else
			{
				EIndex  LPGI = 0;for(LPGI = 0;LPGI < LPGN;LPGI++)
				{
					E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];LTotalPolygons += LPolyGroup->Polygons.Count;
				}
			}

			EIndex  LTotalUntestedPolygons = LTotalPolygons;
			EIndex  LUntestedPolygons = 0;

			do
			{
				LUntestedPolygons = 0;

// There may be several islands of non-edge-connected Polygons. We must process them separately.
//
				EIndex  LPGI = 0;E3dPolyGroupEntry*  LPolyGroupEntry = LUnreachedPolygonLists;for(LPGI = 0;LPGI < LPGN;LPGI++, LPolyGroupEntry++)
				{
					E3dPolyGroup*  LPolyGroup = PMesh->PolyGroups.Items[LPGI];const EIndex  LPN = LPolyGroup->Polygons.Count;
					E3dPolygon*    LPolygons = LPolyGroup->Polygons.Items;
					E3dPolygon*    LPolygon = NULL;

					if(LPN == 0) continue;
					if(LPolyGroupEntry->Tested)
					{
						if(LPolyGroupEntry->Polygons_Count) LPolygon = LPolyGroupEntry->Polygons[0];
						else continue;
					}
					else LPolygon = LPolygons;

					{
						E3dPolyInfo*  LPolygonEntries;const EIndex  LPolygonEntries_Count = E3dPolygon_GetAllConnectedPolygons(LPolyGroup, LPolygon, E3dBY_EDGE, FALSE, &LPolygonEntries);

//printf("%s() %s:%d  PolyGroup %2d  Polygon %3d  Found %s%d%s\n", __FUNCTION__, __FILE__, __LINE__, (int)LPGI, (int)(EPointerInt)(LPolygon - LPolygons), ECodeGreen, (int)LPolygonEntries_Count, ECodeNormal);fflush(stdout);

						if(LPolygonEntries_Count)
						{
// Find dominant Polygon direction
//
							EIndex  LBackwardC = 0;

							{
								EIndex  LI = 0;const EIndex  LN = LPolygonEntries_Count;const E3dPolyInfo*  LPolygonInfo = LPolygonEntries;for(;LI < LN;LI++, LPolygonInfo++)
								{
									if(LPolygonInfo->Flags & E3dBACKWARD) LBackwardC++;
								}
							}

//printf("%s() %s:%d  %d %d %d\n", __FUNCTION__, __FILE__, __LINE__, LPolygonEntries_Count, LBackwardC, LPolygonEntries_Count - LBackwardC);fflush(stdout);

							if(LBackwardC > (LPolygonEntries_Count - LBackwardC))
							{
								EIndex  LI = 0;const EIndex  LN = LPolygonEntries_Count;const E3dPolyInfo*  LPolygonInfo = LPolygonEntries;for(;LI < LN;LI++, LPolygonInfo++)
								{
									if((LPolygonInfo->Flags & E3dBACKWARD) == 0)
									{
										const E3dPolyGroup*  LTPolyGroup = LPolygonInfo->PolyGroup;if(PPolyGroup == NULL || LTPolyGroup == PPolyGroup)
										{
											E3dPolygon_Invert(LPolygonInfo->Polygon, LTPolyGroup->VertexNodeSize, E3dVertexNodeAttribute_Offset(&LTPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0));LPolysInverted++;
											LPolygon->Flags |= E3dPolygonSELECTED;
										}
									}
								}
							}
							else
							{
								EIndex  LI = 0;const EIndex  LN = LPolygonEntries_Count;const E3dPolyInfo*  LPolygonInfo = LPolygonEntries;for(;LI < LN;LI++, LPolygonInfo++)
								{
									if(LPolygonInfo->Flags & E3dBACKWARD)
									{
										const E3dPolyGroup*  LTPolyGroup = LPolygonInfo->PolyGroup;if(PPolyGroup == NULL || LTPolyGroup == PPolyGroup)
										{
											E3dPolygon_Invert(LPolygonInfo->Polygon, LTPolyGroup->VertexNodeSize, E3dVertexNodeAttribute_Offset(&LTPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0));LPolysInverted++;
											LPolygon->Flags |= E3dPolygonSELECTED;
										}
									}
								}
							}

							LTotalUntestedPolygons -= LPolygonEntries_Count;
							EFree(LPolygonEntries);
						}
						else
						{
// Mark single-polygon islands
//
							LPolygon->Flags |= E3dPolygonSELECTED;
//						LTotalUntestedPolygons--;
						}

// Collect un-reached Polygons
//
						if(LPolyGroupEntry->Tested)
						{
							const EIndex  LOldUnreachedPolygons_Count = LPolyGroupEntry->Polygons_Count;
							EIndex        LUnreachedPolygons_Count = 0;

// Count remaining un-reached Polygons in this PolyGroup
//
							{
								EIndex  LPI = 0;E3dPolygon**  LSrc = LPolyGroupEntry->Polygons;for(;LPI < LOldUnreachedPolygons_Count;LPI++, LSrc++)
								{
									E3dPolygon*  LTPolygon = *LSrc;
									if((LTPolygon->Flags & (E3dPolygonMARK0|E3dPolygonMARK1)) == 0) LUnreachedPolygons_Count++;
								}
							}

//printf("%s() %s:%d  %d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LPolygonEntries_Count, (int)(LOldUnreachedPolygons_Count - LUnreachedPolygons_Count));fflush(stdout);

							E3dPolygon**  LNewUnreachedPolygons;ENewN(LUnreachedPolygons_Count, LNewUnreachedPolygons);
							if(LUnreachedPolygons_Count)
							{
								EIndex  LPI = 0;E3dPolygon**  LSrc = LPolyGroupEntry->Polygons;E3dPolygon** LDst = LNewUnreachedPolygons;for(;LPI < LOldUnreachedPolygons_Count;LPI++, LSrc++)
								{
									E3dPolygon*  LTPolygon = *LSrc;
									if((LTPolygon->Flags & (E3dPolygonMARK0|E3dPolygonMARK1)) == 0)
									{
										*LDst++ = LTPolygon;
									}
								}

								EFreeChk(LPolyGroupEntry->Polygons);LPolyGroupEntry->Polygons = LNewUnreachedPolygons;
								LPolyGroupEntry->Polygons_Count = LUnreachedPolygons_Count;
								LUntestedPolygons += LUnreachedPolygons_Count;
							}
						}
						else
						{
							EIndex  LUnreachedPolygons_Count = 0;

// Count un-reached Polygons
//
							{
								EIndex  LPI = 0;E3dPolygon*  LTPolygon = LPolygons;for(;LPI < LPN;LPI++, LTPolygon++)
								{
									if((LTPolygon->Flags & (E3dPolygonMARK0|E3dPolygonMARK1)) == 0) LUnreachedPolygons_Count++;
								}
							}

//printf("%s() %s:%d  %d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LPolygonEntries_Count, (int)(LPN - LUnreachedPolygons_Count));fflush(stdout);

							{
								EResizeNT(E3dPolygon*, LUnreachedPolygons_Count, LPolyGroupEntry->Polygons);
								E3dPolygon**  LDst = LPolyGroupEntry->Polygons;
								EIndex  LPI = 0;E3dPolygon*  LTPolygon = LPolygons;for(;LPI < LPN;LPI++, LTPolygon++)
								{
									if((LTPolygon->Flags & (E3dPolygonMARK0|E3dPolygonMARK1)) == 0)
									{
										*LDst++ = LTPolygon;
									}
								}
							}
							LPolyGroupEntry->Polygons_Count = LUnreachedPolygons_Count;
							LUntestedPolygons += LUnreachedPolygons_Count;
							LPolyGroupEntry->Tested = TRUE;
						}

					}

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

					{
						const ETime  LNow = E_GetMilliseconds();
						const float  LProgress = (float)(LTotalPolygons - LTotalUntestedPolygons) / (float)LTotalPolygons;
						if(!LProgressBarOn && LNow - LStartTime > 500)
						{
							E3dProgressIndicator_SetMessage("Unifying Polygon orientations on Mesh %s", PMesh->Name);E3dProgressIndicator_Set(LProgress, NULL, (EPointer)NULL);XeApp_Sync(Xe_App);
							LProgressBarOn = TRUE;
						}

						if(LProgressBarOn)
						{
							if(LNow - LLastProgressbarUpdateTime > 50)
							{
								E3dProgressIndicator_Set(LProgress, NULL, (EPointer)NULL);
								LLastProgressbarUpdateTime = LNow;
							}
						}
					}
				}
			} while(LUntestedPolygons > 0);

			EIndex  LPGI = 0;E3dPolyGroupEntry*  LPolyGroupEntry = LUnreachedPolygonLists;for(;LPGI < LPGN;LPGI++, LPolyGroupEntry++)
			{
				EFreeChk(LPolyGroupEntry->Polygons);
			}
		}

		if(LProgressBarOn)
		{
			E3dProgressIndicator_Set(-1.0, NULL, (EPointer)NULL);
		}

		E3dObject_UpdateForDisplay((E3dObject*)PMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  PScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);

		return(LPolysInverted);
	}
	else return(E_NO_DATA);
}



//========================================================
// Make Polygon winding orders consistent within a Mesh
//========================================================
static void _MCB_FixPolygonDirections(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LPolysInverted = 0;
		EIndex  LObjI = 0;for(;LObjI < LNObjects;LObjI++)
		{
			E3dObject*  LObject = LObjects.Items[LObjI];
			E3dMesh*    LMesh = (E3dMesh*)LObject;
			LPolysInverted += _E3dMesh_FixPolygonDirections(LMesh, NULL, LScene);
		}

		if(LPolysInverted)
		{
			if(LPolysInverted > 1) E3dp_PrintMessage(ELogMESSAGE, 3000, "Inverted %d Polygons", LPolysInverted);
			else E3dp_PrintMessage(ELogMESSAGE, 3000, "Inverted 1 Polygon");
		}
		EList_FreeA_s(LObjects);
	}
}


static inline EBool _BitCheck(const uint8_t* PArray, const EIndex PBit)
{
	return((PArray[PBit >> 3] & (1 << (PBit & 0x07))) != 0);
}


static inline void _BitSet(uint8_t* PArray, const EIndex PBit)
{
	PArray[PBit >> 3] |= (1 << (PBit & 0x07));
}


//========================================
// Menu callback
//========================================
static void _MCB_FillSelectedHoles(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*   LScene = E3d_Scene;
	E3dObjItem  LItem;

	if(E3dScene_GetSingleSelection(LScene, &LItem, E3dItemMESH, 0) != E3dItemNONE)
	{
		E3dMesh*  LMesh = (E3dMesh*)LItem.Object;if(LMesh->Vertices.Count > 0)
		{
// Find independent Edge loops
//
			EListAI_s(, EIndex, LEdgesToProcess);

			const E3dEdge*  LEdges = LMesh->Edges.Items;const EIndex  LEN = LMesh->Edges.Count;

			{
				EIndex  LEC = 0;const E3dEdge*  LEdge = LEdges;for(;LEC < LEN;LEC++, LEdge++)
				{
					if(LEdge->Flags & E3dEdgeSELECTED) LEdgesToProcess.Count++;
				}
			}

			if(LEdgesToProcess.Count > 0)
			{
				ENewN(LEdgesToProcess.Count, LEdgesToProcess.Items);LEdgesToProcess.CountAllocated = LEdgesToProcess.Count;

				{
					EIndex  LEC = 0, LEI = 0;const E3dEdge*  LEdge = LEdges;for(;LEC < LEN;LEC++, LEdge++)
					{
						if(LEdge->Flags & E3dEdgeSELECTED)
						{
							LEdgesToProcess.Items[LEI] = LEC;LEI++;
						}
					}
				}

				E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);
				const E3dVertexLinkNew*  LVertexLinks = E3dMesh_GetVertexLinksNew(LMesh, E3dVertexLinksEDGES);
				const EIndex             LVertexLinksStride = LMesh->VertexLinksStride;

				const EIndex  LVBytes = (LMesh->Vertices.Count + 7) >> 3;
				uint8_t*  LVertexVisited;ENewNClr(LVBytes, LVertexVisited);

				EListAI_s(, EIndex, LPolygonVertexIndices);

				E3dPolyGroup*  LPolyGroup = NULL;

				EIndex         LNPolygonsAdded = 0;
//int  c = 0;
				EIndex  LEIC = 0;for(;LEIC < LEdgesToProcess.Count;)
				{
// Pick the next Edge from LEdgesToProcess
//
					const E3dEdge*  LEdge = LMesh->Edges.Items + LEdgesToProcess.Items[LEIC];

//printf("%s() %s:%d  %sGREEN%s  LEIC %lld (%lld) of %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LEIC,  (long long)LEdgesToProcess.Items[LEIC], (long long)LEdgesToProcess.Count);fflush(stdout);

// Start tracing an Edge loop
//
					EClrN(LVBytes, LVertexVisited);
					EIndex        LVertexIndex = LEdge->Start;_BitSet(LVertexVisited, LVertexIndex);// if(c++ < 2000000000) { E3dVertex*  LVertex = EStruct_ByIndex(LMesh->Vertices, LMesh->VertexSize, LVertexIndex);LVertex->Flags |= E3dVtxSELECTED; }
					const EIndex  LStartVertexIndex = LVertexIndex;
					EIndex        LPrevVertexIndex = LVertexIndex;
					EList_IndexAddA_s(LPolygonVertexIndices, 64, LVertexIndex);
					LVertexIndex = LEdge->End;_BitSet(LVertexVisited, LVertexIndex);// if(c++ < 2000000000) { E3dVertex*  LVertex = EStruct_ByIndex(LMesh->Vertices, LMesh->VertexSize, LVertexIndex);LVertex->Flags |= E3dVtxSELECTED; }
					EList_IndexAddA_s(LPolygonVertexIndices, 64, LVertexIndex);

					EList_IndexRemoveByIndexNoRealloc_s(LEdgesToProcess, LEIC);

					while(1)
					{
						const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(LVertexLinks, LVertexLinksStride,  LVertexIndex);ENewNClr(LVBytes, LVertexVisited);
						EIndex  LVC = 0;const EIndex  LVN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LVC < LVN;LVC++, LEdgeEnd++)
						{
							const EIndex  LVIndex = LEdgeEnd->VertexIndex;
							if(LVIndex == LPrevVertexIndex) continue;  // Don't go backwards on the Edge

							const EIndex  LEdgeIndex = LEdgeEnd->EdgeIndex;LEdge = LEdges + LEdgeIndex;
							if(LEdge->Flags & E3dEdgeSELECTED) { EList_IndexRemoveNoRealloc_s(LEdgesToProcess, LEdgeIndex);LEIC = 0; }
							if(LVIndex == LStartVertexIndex)
							{
//printf("%s() %s:%d  %sPolygon%s  Verts %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LPolygonVertexIndices.Count);fflush(stdout);
								const EIndex  LVNN = LPolygonVertexIndices.Count;

								if(LVNN >= 3)
								{
									if(LPolyGroup == NULL)
									{
										LPolyGroup = E3dMesh_PolyGroupAdd(LMesh);
									}
									const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
									E3dPolygon*              LPolygon = E3dPolyGroup_AddPolygon(LPolyGroup);
									LPolygon->VertexNodes = (E3dVertexNode*)EMalloc(LVertexNodeSize * LVNN);memset(LPolygon->VertexNodes, 0, LVertexNodeSize * LVNN);

									EIndex  LVNC = 0;E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;for(;LVNC < LVNN;LVNC++, EStruct_Inc(LVertexNode, LVertexNodeSize))
									{
										LVertexNode->VertexID = LPolygonVertexIndices.Items[LVNC];
									}

									LPolygon->NumOfVertexNodes = LPolygon->NumOfVertices = LPolygon->NumOfExteriorVertices = LVNN;
									LNPolygonsAdded++;

									E3dPolygon_UpdateNormal(LPolygon, LVertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize);
									const EIndex  LTriResult = E3dPolygon_Triangulate(LPolygon, LVertexNodeSize,  LMesh->Vertices.Items, LMesh->Vertices.Count, LMesh->VertexSize,  TRUE);
									if(LTriResult == 0)
									{
										printf("%s() %s:%d  E3dPolygon_Triangulate() %sFAILED%s on Vertices:", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);

										{
											EIndex  LI = 0;for(;LI < LVNN;LI++)
											{
												printf(" %lld", (long long)LPolygonVertexIndices.Items[LI]);
												E3dVertex*  LVertex = EStruct_ByIndex(LMesh->Vertices.Items, LMesh->VertexSize, LPolygonVertexIndices.Items[LI]);LVertex->Flags |= E3dVtxSELECTED;
											}
											printf("\n");fflush(stdout);
										}
									}
									else if(LTriResult < 0)
									{
										printf("%s() %s:%d  %s%s%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, EResult_ToString(LTriResult), ECodeNormal);fflush(stdout);
									}

//printf("%s() %s:%d  %sPolygon%s  Verts %lld  Tris %lld\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, (long long)LVNN, (long long)LTriResult);fflush(stdout);
								}
								else
								{
printf("%s() %s:%d  %sFewer than 3 Vertices%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
								}

								LPolygonVertexIndices.Count = 0;
								goto LoopDone;
							}
							else
							{
								if(_BitCheck(LVertexVisited, LVIndex))
								{
									printf("%s() %s:%d  %sVertex %lld was visited in this Edge loop, but it's not the starting Vertex%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, (long long)LVIndex, ECodeNormal);fflush(stdout);
									goto LoopDone;
								}
								else
								{
									if(LEdge->Flags & E3dEdgeSELECTED)
									{
										EList_IndexAddA_s(LPolygonVertexIndices, 64, LVIndex);
										LPrevVertexIndex = LVertexIndex;
										LVertexIndex = LVIndex;
										_BitSet(LVertexVisited, LVIndex);
										break;
									}
								}
							}
						}
					}
LoopDone:;
				}

				EList_FreeA_s(LPolygonVertexIndices);
				EList_FreeA_s(LEdgesToProcess);

				EFree(LVertexVisited);

				if(LNPolygonsAdded > 0)
				{
					E3dMesh_WingedEdgesFree(LMesh);
					E3dMesh_VertexLinksNewFree(LMesh);
					E3dMesh_PolyGroupsEdgesFromMeshEdges(LMesh, TRUE, FALSE);
					_E3dMesh_FixPolygonDirections(LMesh, LPolyGroup, LScene);
					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateTRIANGULATE|E3dUpdateEDGES), E3dCallALL,  LScene);
					E3dp_PrintMessage(ELogMESSAGE, 5000, "Added Polygons: %lld.", (long long)LNPolygonsAdded);
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
				}
			}
			else E3dp_PrintMessage(ELogWARNING, 3000, "Select Edges along Mesh holes.");
		}
	}
	else E3dp_PrintMessage(ELogWARNING, 3000, "Select a Mesh.");
}


static EScriptFunction*  _ScriptFunctions[8];static EIndex  _ScriptFunctions_Count = 0;


typedef struct
{
	E3dMesh*       Mesh;
	E3dPolyGroup*  PolyGroup;
} E3dUnifyPolygonDirections;

#define ROffset(field)  (void*)(EPtrOffset(E3dUnifyPolygonDirections, field))
static EResource  _UnifyPolygonDirections_ResourcesI[]=
{
	{ "Mesh",       EResOBJECT,  ROffset(Mesh) },
	{ "PolyGroup",  EResOBJECT,  ROffset(PolyGroup) },
};
#undef ROffset

EList_s(static, EResource, _UnifyPolygonDirections_Resources) = { .Items = _UnifyPolygonDirections_ResourcesI, .Count= ECount(_UnifyPolygonDirections_ResourcesI) };

//========================================================
// Scriptable function for UnifyPolygonDirections
//========================================================
static int _Sc_UnifyPolygonDirections(EPointer PParams)
{
	if(PParams)
	{
		E3dUnifyPolygonDirections*  LParams = (E3dUnifyPolygonDirections*)PParams;

		if(LParams->Mesh)
		{
			_E3dMesh_FixPolygonDirections(LParams->Mesh, LParams->PolyGroup, E3d_Scene);
			return(E_SUCCESS);
		}
	}

	return(E_NO_DATA);
}

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

	E3dPolygon_Init(&_AddPolygon_TmpPolygon);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Create Polygons", '\0', NULL, NULL, FALSE, NULL, _MCB_CreateEditPolygon, (EPointer)CR_CREATE_POLYGONS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Edit Polygons", '\0', NULL, NULL, FALSE, NULL, _MCB_CreateEditPolygon, (EPointer)CR_EDIT_POLYGONS);
//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Draw hole on Polygon", '\0', NULL, NULL, FALSE, NULL, _MCB_CreateEditPolygon, (EPointer)0);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Fix Polygon directions", '\0', NULL, NULL, FALSE, NULL, _MCB_FixPolygonDirections, (EPointer)0);
	E3dScriptFunction_Register("UnifyPolygonDirections", "Unify Polygon directions", "Make Polygon directions consistent in a Mesh.",  _Sc_UnifyPolygonDirections, &_UnifyPolygonDirections_Resources,  _ScriptFunctions + _ScriptFunctions_Count++);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Polygons", "Fill selected holes", '\0', NULL, NULL, FALSE, NULL, _MCB_FillSelectedHoles, (EPointer)0);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Separate Polygons", '\0', NULL, NULL, FALSE, NULL, _MCB_SeparateSelectedPolygons, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Separate Polygons to Mesh", '\0', NULL, NULL, FALSE, NULL, _MCB_SeparateSelectedPolygonsToNewMesh, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Separate PolyGroups", '\0', NULL, NULL, FALSE, NULL, _MCB_SeparateSelectedPolyGroups, (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; }
		{ int  LI = 0;for(;LI < _ScriptFunctions_Count;LI++) { E3dScriptFunction_Remove(_ScriptFunctions[LI]); } _ScriptFunctions_Count = 0; }
	}

	E3dPolygon_FreeContents(&_AddPolygon_TmpPolygon);

	if(_CreatePolygonMessage) { XeString_Free(_CreatePolygonMessage);_CreatePolygonMessage = NULL; }
	if(_EditPolygonMessage)   { XeString_Free(_EditPolygonMessage);  _EditPolygonMessage   = NULL; }

	return(E_SUCCESS);
}
