//================================================================
// EQUINOX-3D plugin
//
// - Invert Geometry
// - Add back-faces
// - Invert Polygons
// - Separate (un-weld) selected Vertices of a Mesh
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Nov-24 16:00:10
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <assert.h>
#include <stdio.h>

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

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

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

#include <E3D/PolygonEditing.h>

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

#include <Xe/PushButton.h>

char*  E_PluginInfo = "Various Geometry tools, including:\n- Invert Geometry\n- Add back faces\n- Separate (un-weld) selected Vertices of a Mesh";


static EPlugin*  _Plugin = NULL;

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

//static const EBool  _SeparateSelectedObjects_AllInstances = TRUE;

static const char  _NewObjectAddingMode = E3dNEW_INSTANCE;
//static const char  _NewObjectAddingMode = E3dNEW_MODEL;


typedef struct
{
	E3dObjectComponentCore;

	E3dGeometry*   InputGeometry;
	EInterface     InputInterface;         // This is added to the profile Geometries as an output node

// Parameters
//
	EBool          BackSeparatePolyGroups;  // Make the back-side separate PolyGroup(s) than the front and the sides
} E3dInfo;


#define ROffsetP(field)  (void*)(EPtrOffset(E3dInfo, field))
#define ROffsetI(field)  EPtrOffset(E3dInfo, field)
static EResource  _Resources[]=
{
	{ "InputGeometry",  EResOBJECT,   ROffsetP(InputGeometry) },
	{ "Separate back",  EResBOOLEAN,  ROffsetP(BackSeparatePolyGroups) },
};
#undef ROffsetP
#undef ROffsetI

static E3dObjectComponentClass*  _ObjectComponent_class = NULL;

typedef struct
{
	XeWidget  BackSeparatePolyGroupsW;
} E3dResourcesGUI;


//================================================================
// Callback for Profile-Spline changes
// This function should Not call any 3DWindow redrawing function,
// just return with TRUE if a redraw is needed!
//================================================================
static EChangeFlags _InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS)
{
	E3dObject*    LObject  = (E3dObject*)PInterface->Object;
	E3dInfo*      LInfo    = (E3dInfo*)PInterface->ObjectComponent;
	EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);

	LInfo->InputChangeCount++;

// Only respond if the Object is instantiated (e.g. not in the undo stack after being deleted)
//
	if(LObject->Models.Count > 0)
	{
		E3dObject_CustomUpdate(LObject, LInfo,  PCBS->CallOutputs ? E3dCallALL : E3dCallALL & ~E3dCallOUTPUTS,  &LChanged);
	}

	return(LChanged);
}


//================================================
// Add back-face Constructor
//================================================
static int _Init(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LThis = (E3dInfo*)PInfo;

	LThis->InputGeometry = NULL;
	LThis->BackSeparatePolyGroups = TRUE;

	{
		EInterface*  LInput = &LThis->InputInterface;EInterface_Init(LInput);
		LInput->InputChanged = _InputChanged;
		LInput->Object = (ECoreObject*)PObject;
		LInput->ObjectComponent = (ECoreObject*)PInfo;
	}

	return(E_SUCCESS);
}


//========================================
// Add back-face Copy-constructor
//========================================
static void _Copy(const E3dObject* PSObject, const E3dObjectComponent* PSInfo,  E3dObject* PDObject, E3dObjectComponent* PDInfo)
{
	const E3dInfo*  LSInfo = (E3dInfo*)PSInfo;
	E3dInfo*        LDInfo = (E3dInfo*)PDInfo;

	_Init(PDObject, PDInfo);

#define _CopyField(f)  LDInfo->f = LSInfo->f
	_CopyField(BackSeparatePolyGroups);
#undef _CopyField

	E_AssignToMember(LSInfo->InputGeometry, LDInfo,InputGeometry);
	if(LSInfo->InputGeometry)
	{
		E3dBaseObject_AddOutput((E3dBaseObject*)LDInfo->InputGeometry, &LDInfo->InputInterface);
	}
}

//================================================
// Add back-face Destructor
//================================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*  LInfo = (E3dInfo*)PInfo;

	E3dBaseObject_RemoveOutput((E3dBaseObject*)LInfo->InputGeometry, &LInfo->InputInterface);

	E_DeleteChk(LInfo->InputGeometry);

	EInterface_Clear(&LInfo->InputInterface);

	return(E_SUCCESS);
}


//========================================
// Add back-face Update method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PInfoV,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dMesh*  LMesh;
	E3dInfo*  LInfo;

	if(PObject == NULL)
	{
		E_New(E3dMesh, LMesh);
		PObject = (E3dObject*)LMesh;

		LInfo = (E3dInfo*)E3dObjectComponent_Add(PObject, _ObjectComponent_class);
		if(PInfoV && PInfoV->Class == _ObjectComponent_class)
		{
			_Copy(PObject, PInfoV, PObject, (E3dObjectComponent*)LInfo);
		}

		E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->InputGeometry, &LInfo->InputInterface);
	}
	else
	{
		if(!EObject_IsSubClass(PObject, E3dMesh)) return(NULL);
		LInfo = (E3dInfo*)E3dObjectComponent_ByClass(PObject, _ObjectComponent_class, FALSE);

		if(!PCheckOnly && LInfo->AttributeChangeCount == 0 && LInfo->InputChangeCount == 0) return(NULL);    // If no attributes changed, bail

		LMesh = (E3dMesh*)PObject;

// When a Scene is loaded, Update methods are called to create this link.
// The Geometry will be up-to-date, so if the link wasn't there yet, just exit
// We should probably add a 3rd argument to this function ("Action=LINK"), so
// even input-less ComponentClasses don't rebuild their Geometry.
//
		if(PCheckOnly && LInfo->InputGeometry)
		{
			if(E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->InputGeometry, &LInfo->InputInterface) == E_SUCCESS)
			{
				return(PObject);
			}
		}
	}

	if(LMesh)
	{
		E3dMesh_FreeCaches(LMesh);
		E3dMesh_EdgesFree(LMesh);
		E3dMesh_PolyGroupsFree(LMesh);

		const E3dMesh*  LInputMesh = (E3dMesh*)LInfo->InputGeometry;if(LInputMesh)
		{
			const EIndex  LVN = LInputMesh->Vertices.Count;if(LVN > 0)
			{
				if(LMesh->Vertices.Count != LVN)
				{
					E3dMesh_VerticesFree(LMesh);
					E3dMesh_VerticesAdd(LMesh, LVN);
				}
				E3dVertices_Copy(LInputMesh->Vertices.Items, LInputMesh->VertexSize, &LInputMesh->VertexAttributes,  LMesh->Vertices.Items, LMesh->VertexSize, &LMesh->VertexAttributes,  LVN);
			}
			else E3dMesh_VerticesFree(LMesh);
		}

		if(LInputMesh->Edges.Count > 0)
		{
			const EIndex  LEN = LInputMesh->Edges.Count;
			ECopyN(LInputMesh->Edges.Items, LMesh->Edges.Items, LEN);LMesh->Edges.Count = LMesh->Edges.CountAllocated = LEN;
		}

		LMesh->SaveEdges = LInputMesh->SaveEdges;

		const EBool  LBackSeparatePolyGroups = LInfo->BackSeparatePolyGroups;

		const EIndex  LPGN = LInputMesh->PolyGroups.Count;if(LPGN)
		{
			E3dPolyGroup**  LPolyGroups = LInputMesh->PolyGroups.Items;
			E3dMatrix       LMatrix;E3dMatrix_LoadIdentity(LMatrix);

			EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
			{
				E3dPolyGroup*            LSrcPolyGroup = LPolyGroups[LPGI];
				const E3dVertexNodeSize  LVertexNodeSize = LSrcPolyGroup->VertexNodeSize;

				if(LBackSeparatePolyGroups)
				{
					{
//					E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LSrcPolyGroup, NULL,  NULL,  ECopyNAME|E3dCopyPOLYGONS|E3dCopyMATERIALS,  E3d_Scene);
						E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LSrcPolyGroup, NULL,  NULL,  ECopyNAME|E3dCopyPOLYGONS,  E3d_Scene);
						E3dMesh_AppendPolyGroup(LMesh, LNewPolyGroup);

						if(LSrcPolyGroup->Edges.Count > 0)
						{
							const EIndex  LEN = LSrcPolyGroup->Edges.Count;
							ECopyN(LSrcPolyGroup->Edges.Items, LNewPolyGroup->Edges.Items, LEN);LNewPolyGroup->Edges.Count = LEN;
						}
					}

					{
						E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LSrcPolyGroup, NULL,  NULL,  E3dCopyPOLYGONS,  E3d_Scene);
						const char*    LPGName = LSrcPolyGroup->Name;
						const int      LNL = (LPGName ? strlen(LPGName) : 0) + 6;char*  LName = EMalloc(LNL);sprintf(LName, "%s-back", LPGName ? LPGName : "");
						EObject_SetName(LNewPolyGroup, LName, FALSE);
						E3dMesh_AppendPolyGroup(LMesh, LNewPolyGroup);

						const int  LNormalOffset = E3dVertexNodeAttribute_Offset(&LNewPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
						{ EIndex  LPI = 0;const EIndex  LPN = LNewPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LNewPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++) E3dPolygon_Invert(LPolygon, LVertexNodeSize, LNormalOffset); }

						if(LSrcPolyGroup->Edges.Count > 0)
						{
							const EIndex  LEN = LSrcPolyGroup->Edges.Count;
							ECopyN(LSrcPolyGroup->Edges.Items, LNewPolyGroup->Edges.Items, LEN);LNewPolyGroup->Edges.Count = LEN;
						}
					}
				}
				else
				{
					E3dPolyGroup*  LNewPolyGroup = E3dPolyGroup_Copy(LSrcPolyGroup, NULL,  NULL,  ECopyNAME|E3dCopyPOLYGONS,  E3d_Scene);
					E3dMesh_AppendPolyGroup(LMesh, LNewPolyGroup);

					if(LSrcPolyGroup->Edges.Count > 0)
					{
						const EIndex  LEN = LSrcPolyGroup->Edges.Count;
						ECopyN(LSrcPolyGroup->Edges.Items, LNewPolyGroup->Edges.Items, LEN);LNewPolyGroup->Edges.Count = LEN;
					}

					const EIndex  LPN = LNewPolyGroup->Polygons.Count;if(LPN > 0)
					{
						const int  LNormalOffset = E3dVertexNodeAttribute_Offset(&LNewPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
						EResizeNT(E3dPolygon, LPN * 2, LNewPolyGroup->Polygons.Items);
						EIndex  LPI = 0;const E3dPolygon*  LSrcPolygon = LNewPolyGroup->Polygons.Items;E3dPolygon*  LDstPolygon = LNewPolyGroup->Polygons.Items + LPN;for(;LPI < LPN;LPI++, LSrcPolygon++, LDstPolygon++)
						{
							E3d_PolygonCopy(LSrcPolygon, LVertexNodeSize, LDstPolygon);
							E3dPolygon_Invert(LDstPolygon, LVertexNodeSize, LNormalOffset);
						}
						LNewPolyGroup->Polygons.Count = LNewPolyGroup->Polygons.CountAllocated = LPN * 2;
					}
				}
			}
		}

		E3dMesh_EdgeFlagsUpdate(LMesh);

		if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags |= E3dUpdateALL & ~E3dUpdateEDGES;

		LInfo->AttributeChangeCount = LInfo->InputChangeCount = 0;
	}

	return(PObject);
}


static void _DCB_ProfileShow(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	EChangeFlags     LChanged = 0;

	if(LInfo->InputGeometry)
	{
		if(E3dSel_ObjectSelect(E3d_Scene, (E3dObject*)LInfo->InputGeometry, FALSE, FALSE)) LChanged |= E3dChgSELECTION;

		E3dScene*  LScene = E3d_Scene;
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
		E3dp_PrintMessage(ELogMESSAGE, 3000, "Revolve: selected profile Geometry <%s>", LInfo->InputGeometry->Name);
	}
	else E3dp_PrintMessage(ELogMESSAGE, 3000, "Revolve: no profile Geometry!");
}


static E3dInfo*         _TmpInfo = NULL;
static E3dObjectPanel*  _TmpPanel = NULL;
static const char*      E3dpLng_Pick_a_Mesh_as_the_new_input = "Pick a Mesh as the new input";

#define _PickInputGeometryTypeWarningStr  E3dpLng_Pick_a_Mesh_as_the_new_input
#define _PickInputGeometryObjectTypes     E3dItemMESH

#include <E3D/PickInput.ci>

static void _RequestProfilePick(void)
{
	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color = Xe_ForegroundRGBA8pColor;

	E3dp_PickRequest(
	 XeString_NewVa(TRUE,
	 XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, _ObjectComponent_class->Name,  XeTEXT, ":    ",
	 E3dp_LMouseButtonS,     XeTEXT, ": pick a Mesh as the new input  ",
	 E3dp_RMouseButtonEscS,  XeTEXT, ": cancel",
	 0),
	 E3dItemMESH|E3dItemMODEL,  _PickCB_InputGeometry, NULL,  0, FALSE);
}


static void _DCB_ProfilePick(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	_TmpInfo = LInfo;
	_TmpPanel = LPanel;
	_RequestProfilePick();
}


static void _DCB_BackSeparatePolyGroups(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LPanel->Object;
	E3dInfo*         LInfo = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

	LInfo->BackSeparatePolyGroups = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	LInfo->AttributeChangeCount++;

	EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass(LObject);
	E3dObject_CustomUpdate(LObject, (E3dObjectComponent*)LInfo,  E3dCallALL,  &LChanged);
	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


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

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

				XeToggleButton_SetState(LResourcesGUI->BackSeparatePolyGroupsW, LInfo->BackSeparatePolyGroups, FALSE);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
				XeWidget  LTraverseWs[7];int  LTraverseN;  // For TextField traversal
				EArg      Args[256];int  ArgCnt;

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

// Input Geometry
//
				{
					EXeStart;
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 5);
					EXeSetArg(XeNSpacingX, 0);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 3);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Profile", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNSpacingLeft, 6);
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);EXeSetArg(XeNActiveColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Find);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Select the input Mesh (so you can find it).");
					LW = XeWidget_ManagedNew("Show", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_ProfileShow, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);EXeSetArg(XeNActiveColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNLabelType, XeIMAGE);EXeSetArg(XeNImageSizeYPtr, &Xe_IconSize);EXeSetImages2(_Xe_Theme->Image_Replace);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Pick a new input Mesh.");
					LW = XeWidget_ManagedNew("Pick", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_ProfilePick, (EPointer)LPanel);
				}


/*
				EXeStart;
				EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 12);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);
*/

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LInfo->BackSeparatePolyGroups);
				XeToolTip8("Make the back-side out of separate PolyGroups than the front and the side.\nThis allows assigning a diferent Material to the inside of objects.");
				LResourcesGUI->BackSeparatePolyGroupsW = LW = XeWidget_ManagedNew("Separate back", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_BackSeparatePolyGroups, (EPointer)LPanel);

				LTraverseN = 0;


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




//========================================
// Menu callback
//========================================
static void _MCB_Invert(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LDid = 0;
	EChangeFlags  LChanged = 0;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemGEOMETRY, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LI = 0;for(;LI < LNObjects; LI++)
		{
			E3dObject*  LObject = LObjects.Items[LI];

			if(E3dGeometry_Invert((E3dGeometry*)LObject, &LChanged) == E_SUCCESS)
			{
				LDid++;
				if(EObject_IsSubClass(LObject, E3dSpline) || EObject_IsSubClass(LObject, E3dFace))
				{
					E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL, E3dCallALL,  LScene);
				}
			}
		}

		EList_FreeA_s(LObjects);
	}

	if(LDid == 0) E3dp_PrintMessage(ELogWARNING, 5000, "Select Mesh, PolyGroup, Spline or Face to invert");

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


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

	if(E3dScene_GetSingleSelection(LScene, &LItem, E3dItemMESH, E3dVISIBLE) != E3dItemNONE)
	{
		E3dObject*  LInputObject = LItem.Object;
		EBool       LSuccess = FALSE;

		E3dInfo  LTInfo;E3dObjectComponent_InitStatic(&LTInfo, _ObjectComponent_class);
		E_Assign(LInputObject, LTInfo.InputGeometry);

		E3dModel*  LOrigModel = E3dObject_GetSelectingModel(LInputObject, FALSE);
		if(LOrigModel == NULL) LOrigModel = LInputObject->Models.Items[0];

		E3dObject*  LObject = E3dObject_CustomNew(LScene, NULL, LOrigModel, NULL, (E3dObjectComponent*)(&LTInfo), _NewObjectAddingMode, (E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0,  NULL);if(LObject)
		{
			((E3dGeometry*)LObject)->AutoLOD = FALSE;

			LSuccess = TRUE;
		  E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(LScene, TRUE);
		}

		E_Delete(LTInfo.InputGeometry);

		if(LSuccess)
		{
			if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dObject_Edit(LObject, _ObjectComponent_class);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);
		}
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "Select a Mesh");
}


//========================================
// Menu callback
//========================================
static void _MCB_InvertPolygons(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	int        LDid = 0;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemGEOMETRY, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LI = 0;for(;LI < LNObjects; LI++)
		{
			E3dObject*  LObject = LObjects.Items[LI];

			if(EObject_IsSubClass(LObject, E3dMesh))
			{
				E3dMesh*  LMesh = (E3dMesh*)LObject;

				if(LMesh->PolyGroups.Count)
				{
					E3dPolyGroup**  LPolyGroups = LMesh->PolyGroups.Items;
					EBool           LChanged = FALSE;

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

						if(LPolyGroup->Selected || LMesh->Selection != E3dObjectSelPOLYGROUP)
						{
							if(LPolyGroup->Polygons.Count > 0)
							{
								const int  LNormalOffset = E3dVertexNodeAttribute_Offset(&LPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);
								EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++) if(LPolygon->Flags & E3dPolygonSELECTED)
								{
									E3dPolygon_Invert(LPolygon, LVertexNodeSize, LNormalOffset);LChanged = TRUE;
								}
							}
						}
					}
					if(LChanged)
					{
						LDid++;
						E3dMesh_CreateRenderablePolygons(LMesh, TRUE);
						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateMeshSHAPE, 0,  LScene);
					}
				}
			}
		}
		EList_FreeA_s(LObjects);
	}

	if(LDid == 0) E3dp_PrintMessage(ELogWARNING, 5000, "Select Polygons to invert");
	else E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
}


//========================================================
// Split Mesh Vertices if their corresponding Polygon
// vertex-normals, texture coordinates etc. are different
//========================================================
static EIndex _MeshUnweldSelectedPoints(E3dMesh* PMesh)
{
	if(PMesh->Vertices.Count > 0)
	{
		const E3dVertexLinkPolys*  LVertexLinks = (E3dVertexLinkPolys*)E3dMesh_GetVertexLinksNew(PMesh, E3dVertexLinksPOLYGONS|E3dVertexLinksPOLYGROUPS);
		const EIndex               LVN = PMesh->Vertices.Count;
		EIndex*                    LNewToOldTable;ENewN(LVN, LNewToOldTable);
		EIndex                     LNewToOldTableAllocated = LVN;
		EIndex                     LNumOfVerticesAdded = 0, LNewNumOfVertices = LVN;

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

		EIndex  LVC = 0;const EIndex  LVertexLinksStride = PMesh->VertexLinksStride;const E3dVertex*  LVertex = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;const E3dVertexLinkPolys*  LVertexLinksRec = LVertexLinks;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize), EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
		{
			EIndex  LVNC = 1;const EIndex  LVNN = LVertexLinksRec->VertexNodes_Count;for(;LVNC < LVNN;LVNC++)
			{
				E3dVertexNode*  LVertexNode = LVertexLinksRec->VertexNodes[LVNC];

				if(LVertex->Flags & E3dVtxSELECTED)
				{
					if(LNewNumOfVertices >= LNewToOldTableAllocated) EResizeNC(EIndex, LNewToOldTableAllocated + 16, LNewToOldTable, LNewToOldTableAllocated);
					LNewToOldTable[LNewNumOfVertices] = LVC;

					LVertexNode->VertexID = LNewNumOfVertices;

					LNewNumOfVertices++;LNumOfVerticesAdded++;
				}
//printf("%s() unwelded vertex %d\n", __FUNCTION__, LVC);fflush(stdout);
			}
		}

		E3dMesh_VertexLinksNewFree(PMesh);

		E3dMesh_VerticesRemap(PMesh, LNewToOldTable, LNewNumOfVertices);

		EFree(LNewToOldTable);

		return(LNumOfVerticesAdded);
	}

	return(0);
}


//========================================
// Menu callback
//========================================
static void _MCB_SeparateSelectedPoints(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LTotalNumOfPointsAdded = 0;
	EChangeFlags  LChanged = 0;
	EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedGeometries = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedGeometries > 0)
	{
		EIndex  LI = 0;for(;LI < LNSelectedGeometries;LI++)
		{
			E3dObject*  LObject = LSelectedObjects.Items[LI];
			EIndex      LNumOfPointsAdded = _MeshUnweldSelectedPoints((E3dMesh*)LObject);

			if(LNumOfPointsAdded)
			{
				LTotalNumOfPointsAdded += LNumOfPointsAdded;

				LChanged |= E3dObject_GetChangeFlagsByClass(LObject) | E3dChgHIERARCHY;

				E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~E3dUpdateVERTEX_NORMAL, E3dCallALL,  LScene);
			}
		}

		EList_FreeA_s(LSelectedObjects);
	}

	if(LChanged)
	{
		if(LTotalNumOfPointsAdded > 1) E3dp_PrintMessage(ELogMESSAGE, 7000, "Added %d points", LTotalNumOfPointsAdded);
		else E3dp_PrintMessage(ELogMESSAGE, 7000, "Added %d point", LTotalNumOfPointsAdded);

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else E3dp_PrintMessage(ELogWARNING, 7000, "Select Vertices to separate");
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	{
		E3dObjectComponentClass  LClass =
		{
			.Name=        "Add back-faces",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_Resources),

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

	_Plugin = PPlugin;

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Geometry", "Invert", '\0', NULL, NULL, FALSE, NULL, _MCB_Invert, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Geometry", "Add back-faces", '\0', NULL, NULL, FALSE, NULL, _MCB_AddBackFaces, (EPointer)0);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Geometry", "Invert Polygons", '\0', NULL, NULL, FALSE, NULL, _MCB_InvertPolygons, (EPointer)0);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Geometry", "Separate Points",  '\0', NULL, NULL, FALSE, NULL, _MCB_SeparateSelectedPoints,  (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;
	}

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

	return(E_SUCCESS);
}
