//========================================================================
// EQUINOX-3D plugin
//
// - Tesselate Mesh: create HW renderable Polygons (convex, no holes)
// - Triangulate: convert Mesh or PolyGroup to triangles
// - Un-triangulate: convert Mesh or PolyGroup triangles to quads
// - Refresh Polygon normals
// - Refresh normals
// - Smooth normals
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 20:18:18
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//========================================================================
#include <stdio.h>

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

#include <E3D/Math.h>

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

#ifndef _E3D_Mesh_h
#include <E3D/Mesh.h>
#endif

#ifndef _E3D_GLVertexArrays_h
#include <E3D/GL/VertexArrays.h>
#endif

#define DEBUG  0

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

#include <Xe/PushButton.h>

char*  E_PluginInfo = "Tools for converting Polygons to triangles, and vice-versa: converting triangles to quads.";


EpIconDef3I(static, Triangulate);


static EPlugin*  _PluginRec = NULL;


static XeDWidget  _MenuButtons[16];static int  _NumOfMenuButtons = 0;
static XeDWidget  _ToolBarButtons[16];static int  _ToolBarButtons_Count = 0;

static EBool     E3d_RefreshNormalsGoOn = FALSE;

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


//========================================
// Menu callback
//========================================
static void _MCB_Tesselate(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	EIndex	   LDid = 0;
	EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedGeometries = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedGeometries > 0)
	{
		switch((EPointerInt)PClientData)
		{
			case 0:  // Tesselate: convert all concave Polygons and Polygons with holes to convex Polygons
				{
					EIndex  LObjI = 0;for(;LObjI < LNSelectedGeometries;LObjI++)
					{
						E3dObject*  LObject = LSelectedObjects.Items[LObjI];
						if(LObject->Locks_Count) E3dp_PrintMessage(ELogWARNING, 2500, "Object %s is locked", LObject->Name);
						else
						{
							E3dGeometry_Tesselate((E3dGeometry*)LObject, TRUE, FALSE);

//							E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL,  NULL);

// No E3dCallCHANGEPROCS, so the RTGI plugin doesn't destroy the SH data
//
							E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallCHANGEPROCS,  LScene);
							LDid++;
						}
					}
				}
			break;

			case 1:  // Convert all concave Polygons to triangles
				{
					EIndex  LObjI = 0;for(;LObjI < LNSelectedGeometries;LObjI++)
					{
						E3dObject*  LObject = LSelectedObjects.Items[LObjI];
						if(LObject->Locks_Count) E3dp_PrintMessage(ELogWARNING, 2500, "Object %s is locked", LObject->Name);
						else
						{
							E3dGeometry_Tesselate((E3dGeometry*)LObject, TRUE, TRUE);

//							E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL,  LScene);

// No E3dCallCHANGEPROCS, so the RTGI plugin doesn't destroy the SH data
//
							E3dObject_UpdateForDisplay(LObject,  E3dUpdateALL & ~(E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallCHANGEPROCS,  LScene);
							LDid++;
						}
					}
				}
			break;
		}

		EList_FreeA_s(LSelectedObjects);

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}

	if(LDid == 0) E3dp_PrintMessage(ELogWARNING, 5000, "Select Node, Mesh or PolyGroup to triangulate");
}


static const E3dEdge* _E(const E3dEdge* PEdges, const EIndex PNumOfEdges, const EIndex PStart, const EIndex PEnd)
{
	EIndex  LI = 0;const E3dEdge*  LEdge = PEdges;for(;LI < PNumOfEdges;LI++, LEdge++)
	{
		if(LEdge->Start == PStart && LEdge->End == PEnd) return(LEdge);
	}

	return(NULL);
}


static const E3dEdge* _EdgeFind(const E3dEdge* PEdges, const EIndex PNumOfEdges, const EIndex PStart, const EIndex PEnd)
{
	if(PStart < PEnd) return(_E(PEdges, PNumOfEdges, PStart, PEnd));
	else return(_E(PEdges, PNumOfEdges, PEnd, PStart));
}


//================================================================
// Check if an Edge should be selected for deletion
//================================================================
static inline EIndex _EdgeProcess(const E3dMesh* PMesh, const E3dWingedEdge* PWingedEdge, E3dEdge* PEdge)
{
	EIndex               LSelected = 0;
	const E3dCoordinate  LVertexAttributeEpsilon = 0.01;

	if((PEdge->Flags & E3dEdgeSELECTED) == 0 && PWingedEdge->Polygons_Count == 2)  // Check if the Edge is not selected, and if it is actually being shared by 2 Polygons
	{
		if(PWingedEdge->Polygons[0].PolyGroupIndex == PWingedEdge->Polygons[1].PolyGroupIndex)  // We can only delete Edges shared between Polygons within the same PolyGroup
		{
			const E3dPolygon*  LPolygon0 = PWingedEdge->Polygons[0].Polygon;
			const E3dPolygon*  LPolygon1 = PWingedEdge->Polygons[1].Polygon;

//			const EIndex         LEdgeIndex = PEdge - PMesh->Edges;
//printf("%s() %s:%d  Edge %d  %f  %d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LEdgeIndex, E3d_ACos(LNormalAngleCos) * E3dRADIANS_TO_DEGREES, (int)LPolygon0->NumOfVertices, (int)LPolygon1->NumOfVertices);fflush(stdout);
			if(LPolygon0->NumOfVertices == 3 && LPolygon1->NumOfVertices == 3)
			{
				const E3dCoordinate  LMaxNormalAngle = 30.0;  // Only remove Edge if the angle between the normals of the two triangles sharing it is less than this.
				const E3dCoordinate  LNormalAngleCosMin = E3d_Cos((90.0 - LMaxNormalAngle) * E3dDEGREES_TO_RADIANS);
				const E3dCoordinate  LNormalAngleCos = E_Dot3_vv(LPolygon0->Normal, LPolygon1->Normal);
#if DEBUG
printf("%s() %s:%d  Tris Cos %f  CosMin %f\n", __FUNCTION__, __FILE__, __LINE__, LNormalAngleCos, LNormalAngleCosMin);fflush(stdout);
#endif
				if(LNormalAngleCos > LNormalAngleCosMin)
				{
					const E3dPolyGroup*      LPolyGroup = PMesh->PolyGroups.Items[PWingedEdge->Polygons[0].PolyGroupIndex];
					const EResourceList*     LResourceList = &LPolyGroup->VertexAttributes;
					const E3dVertex*         LMeshVertices = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;
					const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
#if DEBUG
printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
#endif
					const E3dEdge*    LMeshEdges = PMesh->Edges.Items;const EIndex  LMeshEdges_Count = PMesh->Edges.Count;
					const EIndex      LEdgeStart = PEdge->Start;
					const EIndex      LEdgeEnd   = PEdge->End;
					const E3dVertex*  LVertexEdgeStart = EStruct_ByIndex(LMeshVertices, LVertexSize, LEdgeStart);
					const E3dVertex*  LVertexEdgeEnd   = EStruct_ByIndex(LMeshVertices, LVertexSize, LEdgeEnd);
					E3dCoord3         E_Sub3i_vv(LEdgeSE, LVertexEdgeEnd->Position, LVertexEdgeStart->Position);E3d_Normalize3_v(LEdgeSE);

					const E3dVertex*  LVertex0O = NULL;EIndex  LVertex0O_Index = -1;
					const E3dVertex*  LVertex1O = NULL;EIndex  LVertex1O_Index = -1;

					const E3dVertexNode*  LPolygon0EdgeStartVertexNode = NULL;
					const E3dVertexNode*  LPolygon0EdgeEndVertexNode = NULL;
					const E3dVertexNode*  LPolygon1EdgeStartVertexNode = NULL;
					const E3dVertexNode*  LPolygon1EdgeEndVertexNode = NULL;

					{
						int  LVI = 0;const E3dVertexNode*  LPolygon0VertexNode = LPolygon0->VertexNodes;const E3dVertexNode*  LPolygon1VertexNode = LPolygon1->VertexNodes;for(;LVI < 3;LVI++)
						{
							const EIndex  LPolygon0VertexNodeVertexID = LPolygon0VertexNode->VertexID;
							const EIndex  LPolygon1VertexNodeVertexID = LPolygon1VertexNode->VertexID;

							if(LPolygon0VertexNodeVertexID == LEdgeStart) LPolygon0EdgeStartVertexNode = LPolygon0VertexNode;
							else if(LPolygon0VertexNodeVertexID == LEdgeEnd) LPolygon0EdgeEndVertexNode = LPolygon0VertexNode;

							if(LPolygon1VertexNodeVertexID == LEdgeStart) LPolygon1EdgeStartVertexNode = LPolygon1VertexNode;
							else if(LPolygon1VertexNodeVertexID == LEdgeEnd) LPolygon1EdgeEndVertexNode = LPolygon1VertexNode;

							if(LPolygon0VertexNodeVertexID != LEdgeStart && LPolygon0VertexNodeVertexID != LEdgeEnd)
							{
								LVertex0O_Index = LPolygon0VertexNode->VertexID;LVertex0O = EStruct_ByIndex(LMeshVertices, LVertexSize, LPolygon0VertexNode->VertexID);
							}
							if(LPolygon1VertexNodeVertexID != LEdgeStart && LPolygon1VertexNodeVertexID != LEdgeEnd)
							{
								LVertex1O_Index = LPolygon1VertexNode->VertexID;LVertex1O = EStruct_ByIndex(LMeshVertices, LVertexSize, LPolygon1VertexNode->VertexID);
							}

							EStruct_Inc(LPolygon0VertexNode, LVertexNodeSize);
							EStruct_Inc(LPolygon1VertexNode, LVertexNodeSize);
						}
					}

// If any other Edges of the two triangles are already selected, bail
//
					const E3dEdge*  LEEdge0SO = _EdgeFind(LMeshEdges, LMeshEdges_Count,  LEdgeStart, LVertex0O_Index);
					const E3dEdge*  LEEdge0EO = _EdgeFind(LMeshEdges, LMeshEdges_Count,  LEdgeEnd,   LVertex0O_Index);
					const E3dEdge*  LEEdge1SO = _EdgeFind(LMeshEdges, LMeshEdges_Count,  LEdgeStart, LVertex1O_Index);
					const E3dEdge*  LEEdge1EO = _EdgeFind(LMeshEdges, LMeshEdges_Count,  LEdgeEnd,   LVertex1O_Index);

					if((LEEdge0SO && (LEEdge0SO->Flags & E3dEdgeSELECTED)) ||
					 (LEEdge0EO && (LEEdge0EO->Flags & E3dEdgeSELECTED)) ||
					 (LEEdge1SO && (LEEdge1SO->Flags & E3dEdgeSELECTED)) ||
					 (LEEdge0EO && (LEEdge1EO->Flags & E3dEdgeSELECTED))) return(LSelected);

					E3dCoord3  E_Sub3i_vv(LEdge0SO, LVertex0O->Position, LVertexEdgeStart->Position);E3d_Normalize3_v(LEdge0SO);
					E3dCoord3  E_Sub3i_vv(LEdge0EO, LVertex0O->Position, LVertexEdgeEnd->Position);E3d_Normalize3_v(LEdge0EO);

					E3dCoord3  E_Sub3i_vv(LEdge1SO, LVertex1O->Position, LVertexEdgeStart->Position);E3d_Normalize3_v(LEdge1SO);
					E3dCoord3  E_Sub3i_vv(LEdge1EO, LVertex1O->Position, LVertexEdgeEnd->Position);E3d_Normalize3_v(LEdge1EO);

					const E3dCoordinate  LAngle_0SO_SE = E3d_ACos( E_Dot3_vv(LEdge0SO, LEdgeSE)) * E3dRADIANS_TO_DEGREES;
					const E3dCoordinate  LAngle_0EO_ES = E3d_ACos(-E_Dot3_vv(LEdge0EO, LEdgeSE)) * E3dRADIANS_TO_DEGREES;
					const E3dCoordinate  LAngle_0OS_OE = 180.0 - (LAngle_0SO_SE + LAngle_0EO_ES);

					const E3dCoordinate  LAngle_1SO_SE = E3d_ACos( E_Dot3_vv(LEdge1SO, LEdgeSE)) * E3dRADIANS_TO_DEGREES;
					const E3dCoordinate  LAngle_1EO_ES = E3d_ACos(-E_Dot3_vv(LEdge1EO, LEdgeSE)) * E3dRADIANS_TO_DEGREES;
					const E3dCoordinate  LAngle_1OS_OE = 180.0 - (LAngle_1SO_SE + LAngle_1EO_ES);


/*
	if(LEdgeIndex == 2)
	{
	printf("%s() %s:%d  %f %f %f - %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LVertexEdgeStart->Position), XYZ_s(LVertexEdgeEnd->Position));fflush(stdout);
	printf("%s() %s:%d  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LVertex0O->Position));fflush(stdout);
	printf("%s() %s:%d  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__, XYZ_s(LVertex1O->Position));fflush(stdout);

	 printf("%s() %s:%d  %f %f %f  %f %f %f\n", __FUNCTION__, __FILE__, __LINE__,
//	LAngle_0SO_SE * E3dRADIANS_TO_DEGREES, LAngle_0EO_ES * E3dRADIANS_TO_DEGREES, LAngle_0OS_OE * E3dRADIANS_TO_DEGREES,
//	LAngle_1SO_SE * E3dRADIANS_TO_DEGREES, LAngle_1EO_ES * E3dRADIANS_TO_DEGREES, LAngle_1OS_OE * E3dRADIANS_TO_DEGREES);
	 LAngle_0SO_SE, LAngle_0EO_ES, LAngle_0OS_OE,
	 LAngle_1SO_SE, LAngle_1EO_ES, LAngle_1OS_OE);
	fflush(stdout);

	printf("%s() %s:%d   %f %f %d  %f %f %d   %f %f %d  %f %f %d\n", __FUNCTION__, __FILE__, __LINE__,
	 LAngle_0OS_OE, LAngle_0SO_SE, LAngle_0OS_OE > LAngle_0SO_SE,  LAngle_0OS_OE, LAngle_0EO_ES, LAngle_0OS_OE > LAngle_0EO_ES,
	 LAngle_1OS_OE, LAngle_1SO_SE, LAngle_1OS_OE > LAngle_1SO_SE,  LAngle_1OS_OE, LAngle_1EO_ES, LAngle_1OS_OE > LAngle_1EO_ES);

	fflush(stdout);
	}
*/

					if((LAngle_0OS_OE > LAngle_0SO_SE && LAngle_0OS_OE > LAngle_0EO_ES) ||
						 (LAngle_1OS_OE > LAngle_1SO_SE && LAngle_1OS_OE > LAngle_1EO_ES))
					{
// Make sure removing this Edge won't cause a tear in texture-space etc.
//
						if(E3dAttributes_CompareValues(LResourceList, LPolygon0EdgeStartVertexNode, LPolygon1EdgeStartVertexNode, LVertexAttributeEpsilon) && E3dAttributes_CompareValues(LResourceList, LPolygon0EdgeEndVertexNode, LPolygon1EdgeEndVertexNode, LVertexAttributeEpsilon))
						{
							PEdge->Flags |= E3dEdgeSELECTED;
							LSelected++;
						}
					}
				}
			}
		}
	}

	return(LSelected);
}

const ETime  _PutUpProgressbarAfter_ms = 200;

//========================================
// Un-triangulate a Geometry
//========================================
static int _GeometryUntriangulate(E3dGeometry* PGeometry, const EBool PSelectionOnly)
{
	EIndex  LEdgesSelected_Count = 0;

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

		if(LMesh->PolyGroups.Count == 0) return(E_NO_DATA);

		if(LMesh->WingedEdges == NULL) E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);

		E3dEdge*              LMeshEdges = LMesh->Edges.Items;
		const E3dWingedEdge*  LMeshWingedEdges = LMesh->WingedEdges;
		E3dPolyGroup**        LPolyGroups = LMesh->PolyGroups.Items;

		const ETime  LStartTime = E_GetMilliseconds();EBool  LProgressBarOn = FALSE, LProgressBarFirstUpdate = FALSE;

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

			if(PGeometry->Selection == E3dObjectSelPOLYGROUP)
			{
				if(!LPolyGroup->Selected) continue;
			}

// Examine PolyGroup Edges and mark the ones to be deleted
//
			if(PGeometry->Selection == E3dObjectSelPOLYGROUP && LPolyGroup->Edges.Count)
			{
#if DEBUG
printf("%s() %s:%d  PG %d has Edges  %lld\n", __FUNCTION__, __FILE__, __LINE__, (int)LPGI, (long long)LPolyGroup->Edges.Count);fflush(stdout);
#endif
				EIndex  LEC = 0;const  EIndex  LEN = LPolyGroup->Edges.Count;const EIndex*  LEdgeIndexP = LPolyGroup->Edges.Items;for(;LEC < LEN;LEC++, LEdgeIndexP++)
				{
					const EIndex          LEdgeIndex = *LEdgeIndexP;
					E3dEdge*              LEdge = LMeshEdges + LEdgeIndex;
					const E3dWingedEdge*  LWingedEdge = LMeshWingedEdges + LEdgeIndex;

					LEdgesSelected_Count += _EdgeProcess(LMesh, LWingedEdge, LEdge);

					{
						if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("Untriangulate");LProgressBarOn = LProgressBarFirstUpdate = TRUE; } }
						if(LProgressBarOn && (LProgressBarFirstUpdate || (LEC & 0x1FF) == 0)) { E3dProgressIndicator_Set((float)(LEC + 1) / (float)LEN,  NULL, (EPointer)NULL);XeApp_Sync(Xe_App);LProgressBarFirstUpdate = FALSE; }
					}
				}
			}
			else
			{
printf("%s() %s:%d  PG %d has no Edges\n", __FUNCTION__, __FILE__, __LINE__, (int)LPGI);fflush(stdout);
				EIndex  LEC = 0;const EIndex  LEN = LMesh->Edges.Count;E3dEdge*  LEdge = LMesh->Edges.Items;const E3dWingedEdge*  LWingedEdge = LMeshWingedEdges;for(;LEC < LEN;LEC++, LWingedEdge++, LEdge++)
				{
//if(LEC & 1) { LEdge->Flags |= E3dEdgeSELECTED;printf("%s() %s:%d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LEC);fflush(stdout); }
					LEdgesSelected_Count += _EdgeProcess(LMesh, LWingedEdge, LEdge);

					{
						if(!LProgressBarOn) { const ETime  LNow = E_GetMilliseconds();if(LNow - LStartTime > _PutUpProgressbarAfter_ms) { E3dProgressIndicator_SetMessage("Untriangulate");LProgressBarOn = LProgressBarFirstUpdate = TRUE; } }
						if(LProgressBarOn && (LProgressBarFirstUpdate || (LEC & 0x1FF) == 0)) { E3dProgressIndicator_Set((float)(LEC + 1) / (float)LEN,  NULL, (EPointer)NULL);XeApp_Sync(Xe_App);LProgressBarFirstUpdate = FALSE; }
					}
				}

				break;  // Break from the PolyGroup for() loop, as we just processed all Edges of the Mesh
			}
		}
		E3dMesh_EdgeFlagsUpdate(LMesh);

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

	if(LEdgesSelected_Count) return(E_SUCCESS);
	else return(E_NOTHING_TO_DO);
}


//========================================
// Menu callback
//========================================
static void _MCB_Untriangulate(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	EIndex	   LDid = 0;
	EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedGeometries = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedGeometries > 0)
	{
		EIndex  LObjI = 0;for(;LObjI < LNSelectedGeometries;LObjI++)
		{
			E3dGeometry*  LGeometry = E_DynamicCast(LSelectedObjects.Items[LObjI], E3dGeometry);if(LGeometry)
			{
				if(_GeometryUntriangulate(LGeometry, TRUE) == E_SUCCESS) LDid++;

				if(LGeometry->LODs_Count)
				{
					EIndex  LLODC = 0;const EIndex  LLODN = LGeometry->LODs_Count;E3dGeometry*  LLOD;for(;LLODC < LLODN;LLODC++)
					{
						LLOD = LGeometry->LODs[LLODC];
						if(_GeometryUntriangulate(LLOD, TRUE) == E_SUCCESS) LDid++;
					}
				}
			}
		}

		EList_FreeA_s(LSelectedObjects);

		if(LDid) E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
		else E3dp_PrintMessage(ELogWARNING, 5000, "No mergeable triangles found");
	}
	else
	{
		E3dp_PrintMessage(ELogWARNING, 5000, "Select Node, Mesh or PolyGroup to triangulate");
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_SmoothNormals(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	EIndex     LDid = 0;
	E3dScene*  LScene = E3d_Scene;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
		{
			if(LNode->Selection != E3dNodeSelNONE)
			{
				E3dModel*	LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
				{
					EIndex       LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LObjects[LObjI];

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

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

									LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
									E3dMeshPolyGroup_RefreshNormals(LMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
									LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
									LDid++;
								}
							}
						}
					}
				}
			}
		}

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
	}
	if(LDid == 0) E3dp_PrintMessage(ELogWARNING, 5000, "Select Model, Mesh or PolyGroup");
}


static EBool _PolyGroup_SetPolyNormalsOnVertexNodes(E3dPolyGroup* PPolyGroup)
{
	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;
	const int                LNormalOffset = E3dVertexNodeAttribute_Offset(&PPolyGroup->VertexAttributes,  E3dVtxAttribute_Normal, 0);if(LNormalOffset >= 0)
	{
		EIndex  LPI = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++)
		{
			const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;if(LVertexNode)
			{
				const E3dCoord3  LPolyNormal = LPolygon->Normal;
				EIndex  LVI = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;E3dCoord3*  LVertexNodeNormal = (E3dCoord3*)((uint8_t*)LVertexNode + LNormalOffset);for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNode, LVertexNodeSize), EStruct_Inc(LVertexNodeNormal, LVertexNodeSize))
				{
					if(LVertexNode->VertexID > -1) *LVertexNodeNormal = LPolyNormal;
				}
			}
		}
		return(FALSE);
	}
	else return(FALSE);
}



//========================================
// Menu callback
//========================================
static void _MCB_RefreshPolygonNormals(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*      LScene = E3d_Scene;
	E3dModel*      LDModel = NULL;
	E3dGeometry*   LDGeometry = NULL;
	E3dPolyGroup*  LDPolyGroup = NULL;
	EIndex         LDid = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
		{
			if(LNode->Selection != E3dNodeSelNONE)
			{
				E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
				{
					const int  LNodeSelection = LNode->Selection;
					const EBool  LNodeSelected = E3dNode_IsSelected(LNode);
					LDModel = LModel;

					EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;for(;LObjI < LObjN;LObjI++)
					{
						E3dObject*  LObject = LModel->Objects.Items[LObjI];
						if(LNodeSelected || (LNodeSelection == E3dNodeSelOBJECT && LObject->Selection))
						{
							if(EObject_IsSubClass(LObject, E3dMesh))
							{
								E3dMesh*  LMesh = (E3dMesh*)LObject;if(LMesh->PolyGroups.Count > 0)
								{
									E3dPolyGroup**  LPolyGroups = LMesh->PolyGroups.Items;

									LDGeometry = (E3dGeometry*)LObject;

									EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
									{
										E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];
//printf("%s() %s:%d  %sGREEN%s  %d %d %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LNodeSelection, LObject->Selection, LPolyGroup->Selected);fflush(stdout);

										if(LNodeSelected || (LNodeSelection == E3dNodeSelOBJECT && (LObject->Selection == E3dObjectSelOBJECT || (LObject->Selection == E3dObjectSelPOLYGROUP && LPolyGroup->Selected))))
										{
											LDPolyGroup = LPolyGroup;
											E3dMeshPolyGroup_RefreshPolygonNormals(LMesh, LPolyGroup);

// Set the Polygon normal on each Vertex of the Polygons if the PolyGroup is faceted
//
											if(LPolyGroup->VertexNormalType == 0 && LPolyGroup->Polygons.Items)
											{
												if(_PolyGroup_SetPolyNormalsOnVertexNodes(LPolyGroup)) LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
											}
											LDid++;
										}
									}
								}
							}
						}
					}
				}
			}
		}

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
	}

	switch(LDid)
	{
		case 0:   E3dp_PrintMessage(ELogWARNING, 5000, "Select Model, Mesh or PolyGroup");break;
		case 1:   E3dp_PrintMessage(ELogWARNING, 5000, "Recomputed polygon normals on %s/%s/%s", LDModel->Name ? LDModel->Name : "-", LDGeometry->Name ? LDGeometry->Name : "-", LDPolyGroup->Name ? LDPolyGroup->Name : "-");break;
		default:  E3dp_PrintMessage(ELogWARNING, 5000, "Recomputed polygon normals on %lld PolyGroups", LDid);break;
	}
}


//========================================
// Menu callback
//========================================
static void _MCB_RefreshNormals(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
// Count PolyGroups
//
	EIndex     LPolyGroupNum = 0;

	E3dNode**  LRootNodes = LScene->RootNodes.Items;
	const EIndex  LRN = LScene->RootNodes.Count;

	{
		EIndex  LRI = 0;for(;LRI < LRN;LRI++)
		{
			E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
			{
				if(LNode->Selection != E3dNodeSelNONE)
				{
					E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
					{
						EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
						{
							E3dObject*  LObject = LObjects[LObjI];

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

								LPolyGroupNum += LMesh->PolyGroups.Count;
							}
						}
					}
				}
			}
		}
	}

	if(LPolyGroupNum)
	{
		E3dProgressIndicator_SetMessage("Recomputing normals");
		E3dProgressIndicator_Set(0.0, _CB_Abort, NULL);
	}

	EIndex  LDid = 0;
	EIndex  LPolyGroupCnt = 0;

	E3d_RefreshNormalsGoOn = TRUE;

	{
		EIndex  LRI = 0;for(;LRI < LRN;LRI++)
		{
			if(!E3d_RefreshNormalsGoOn) break;

			E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
			{
				if(!E3d_RefreshNormalsGoOn) break;

				if(LNode->Selection != E3dNodeSelNONE)
				{
					E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
					{
						EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LObjI < LObjN;LObjI++)
						{
							if(!E3d_RefreshNormalsGoOn) break;

							E3dObject*  LObject = LObjects[LObjI];

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

								if(LMesh->PolyGroups.Count > 0)
								{
									if(!E3d_RefreshNormalsGoOn) break;

									E3dPolyGroup**  LPolyGroups = LMesh->PolyGroups.Items;

									E3dMesh_RecomputePolygonNormals(LMesh);

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

										if(!E3d_RefreshNormalsGoOn) break;

										E3dMeshPolyGroup_RefreshNormals(LMesh, LPolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);

// Set the Polygon normal on each Vertex of the Polygons if the PolyGroup is faceted
//
										if(LPolyGroup->VertexNormalType == 0 && LPolyGroup->Polygons.Count > 0)
										{
											if(_PolyGroup_SetPolyNormalsOnVertexNodes(LPolyGroup)) LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
										}

										LPolyGroupCnt++;
										LDid++;
										E3dProgressIndicator_Set((float)LPolyGroupCnt / (float)LPolyGroupNum, _CB_Abort, NULL);
									}
								}
							}
						}
					}
				}
			}
		}
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
	}

	if(LDid == 0) E3dp_PrintMessage(ELogWARNING, 5000, "Select Model, Mesh or PolyGroup");
	else
	{
		E3dProgressIndicator_Set(-1.0, NULL, NULL);
	}
}


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

	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Tesselate",                 '\0', NULL, NULL, FALSE, NULL, _MCB_Tesselate,             (EPointer)0);
	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Triangulate",               '\0', NULL, NULL, FALSE, NULL, _MCB_Tesselate,             (EPointer)1);
	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Un-triangulate",            '\0', NULL, NULL, FALSE, NULL, _MCB_Untriangulate,         NULL);
	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Recompute Polygon normals", '\0', NULL, NULL, FALSE, NULL, _MCB_RefreshPolygonNormals, (EPointer)0);
	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "Recompute normals",         '\0', NULL, NULL, FALSE, NULL, _MCB_RefreshNormals,        (EPointer)0);
	_MenuButtons[_NumOfMenuButtons++] = XePushButton_Add(Xe_App, "Menu->Geometry", "'Smooth' normals",          '\0', NULL, NULL, FALSE, NULL, _MCB_SmoothNormals,         (EPointer)0);

	XePushButton_AddImg3_sp(_ToolBarButtons[_ToolBarButtons_Count++], Triangulate, "Tool->Geometry", "Triangulate",    '\0', NULL, NULL, FALSE, "Triangulate: Convert polygons of selected objects to triangles", _MCB_Tesselate,     (EPointer)1);
	XePushButton_AddImg3_sp(_ToolBarButtons[_ToolBarButtons_Count++], Triangulate, "Tool->Geometry", "Un-triangulate", '\0', NULL, NULL, FALSE, "Un-triangulate: Convert traingles of selected objects to quads.\nThis tool selects Mesh Edges for deletion. You can change this selection.\nThen, select the Mesh (not just a PolyGroup) and press Ctrl+Delete or Cmd+Delete (Mac) to remove the selected Edges.", _MCB_Untriangulate, NULL);

	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LI;

		for(LI = 0;LI < _NumOfMenuButtons;LI++) XeItemFree(_MenuButtons[LI]);
		_NumOfMenuButtons = 0;

		for(LI = 0;LI < _ToolBarButtons_Count;LI++) XeItemFree(_ToolBarButtons[LI]);
		_ToolBarButtons_Count = 0;
	}

	EpIconFree3(Triangulate);

	return(E_SUCCESS);
}
