//================================================================
// EQUINOX-3D(TM) plugin
//
// - Hierarchy walking
// - Select Objects, parts of Objects/Geometries (PolyGroup, Polygon, Edge, Vertex etc.)
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Apr-25 21:19:56
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <stdio.h>

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

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

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

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

char*	E_PluginInfo = "Advanced selection";
static EPlugin*      _Plugin = NULL;

static XeDWidget          _MenuButtons[32];static int  _MenuButtons_Count = 0;
static EScriptFunction*  _ScriptFunctions[32];static int  _ScriptFunctions_Count = 0;

static XeWidget      _SelectEdgesAtNormalCreasesDialogW = NULL;
static EPluginLock*  _SelectEdgesAtNormalCreasesDialogLock = NULL;



// Operations
//
enum
{
	E3dCrALL_HIERARCHIES = 1,
	E3dCrALL_NORMAL_MODELS,
	E3dCrEMPTY_MODELS,
	E3dCrHIDDEN_GEOMETRIES,
	E3dCrUNSELECT_EVERYTHING,
	E3dCrUNSELECT_ALL_MODELS,
	E3dCrSELECT_BY_NAME,
	E3dCrSELECT_EDGES,
	E3dCrSELECT_HULLS,
	E3dCrSELECT_TRIS,
	E3dCrSELECT_QUADS,

	E3dCrSELECT_EDGES_WITH_SELECTED_VERTICES,
	E3dCrSELECT_EDGES_OF_SELECTED_POLYGONS,
	E3dCrSELECT_EDGES_AT_NORMAL_CREASES,
	E3dCrUNSELECT_EDGES,
	E3dCrSELECT_BOUNDARY_EDGES,

	E3dCrPOLYGONS_WITH_ALL_VERTICES_SELECTED,
	E3dCrPOLYGONS_WITH_A_SELECTED_EDGE,
	E3dCrUNSELECT_ALL_POLYS,
	E3dCrINVERT_POLYGON_SELECTION,
	E3dCrINVERT_POLYGROUP_SELECTION,
	E3dCrGO_UP,
	E3dCrGO_DOWN,
	E3dCrGO_LEFT,
	E3dCrGO_RIGHT
};


enum
{
	E3dSELECT_POINTS_OF_SELECTION = 1,
	E3dUNSELECT_POINTS_OF_SELECTION,
	E3dSELECT_UNUSED_POINTS,
};


//========================================
// Node selection
//========================================
static void _NodeSelection(const int PAction)
{
	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)
		{
			switch(PAction)
			{
				case E3dCrALL_NORMAL_MODELS:
					LNode->Selection = E3dNodeSelNODE;
				break;

				case E3dCrEMPTY_MODELS:
					{
						E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
						{
							if(LModel->Objects.Count == 0) LNode->Selection = E3dNodeSelNODE;
						}
					}
				break;

				case E3dCrALL_HIERARCHIES:
					if(LNode->Parent == NULL) LNode->Selection = E3dNodeSelBRANCH_ROOT;
					else LNode->Selection = E3dNodeSelBRANCH;
				break;

				case E3dCrUNSELECT_EVERYTHING:
					LNode->Selection = E3dNodeSelNONE;
					if(EObject_IsSubClass(LNode, E3dModel)) E3dModel_ObjectsUnselect(LScene, (E3dModel*)LNode,  NULL, 0,  E3dSF_ALL, TRUE);
				break;

				case E3dCrUNSELECT_ALL_MODELS:
					LNode->Selection = E3dNodeSelNONE;
					if(EObject_IsSubClass(LNode, E3dModel)) E3dModel_ObjectsUnselect(LScene, (E3dModel*)LNode,  NULL, 0,  E3dSF_MODELS, TRUE);
				break;
			}
		}
	}
}


//========================================
// Node/Geometry
//========================================
static void _SelectHidden(const uint16_t PWhatToInclude)
{
	E3dScene*  LScene = E3d_Scene;

	E3dSel_SceneUnselectAll(LScene);

	if(PWhatToInclude & E3dItemGEOMETRY)
	{
		EListAI_s(, E3dObjectPtr, LObjects);EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemGEOMETRY, E3dVISIBLE|E3dHIDDEN, FALSE,  &LObjects);if(LNObjects)
		{
			EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
			{
				E3dObject*  LObject = LObjects.Items[LObjC];if(LObject->Visible)
				{
//printf("%s() %s:%d <%s>\n", __FUNCTION__, __FILE__, __LINE__, LObject->Name);fflush(stdout);
					EBool  LObjectHasVisibleInstance = FALSE;
					EIndex  LMI = 0;for(;LMI < LObject->Models.Count;LMI++)
					{
						if(LObject->Models.Items[LMI]->Visible) { LObjectHasVisibleInstance = TRUE;break; }
					}
					if(!LObjectHasVisibleInstance) E3dSel_ObjectSelect(LScene, LObjects.Items[LObjC], FALSE, FALSE);
				}
				else E3dSel_ObjectSelect(LScene, LObjects.Items[LObjC], FALSE, FALSE);
			}

			if(LNObjects == 1) E3dp_PrintMessage(ELogMESSAGE, 5000, "Selected <%s>", LObjects.Items[0]->Name ? LObjects.Items[0]->Name : "");
			else E3dp_PrintMessage(ELogMESSAGE, 5000, "Selected %lld Objects", (long long)LNObjects);

			EList_FreeA_s(LObjects);
		}
	}


	if(PWhatToInclude & E3dItemNODE)
	{
		EIndex  LRI = 0;const EIndex  LRN = E3d_Scene->RootNodes.Count;E3dNode**  LRootNodes = E3d_Scene->RootNodes.Items;for(;LRI < LRN;LRI++)
		{
			E3dNode*  LNode = LRootNodes[LRI];for(;LNode;LNode = LNode->Next)
			{
				if(!LNode->Visible) E3dSel_NodeSelect(LScene, LNode, FALSE, FALSE);
			}
		}
	}
}


static EIndex _TagVerticesOfSelectedPolygons(E3dPolyGroup* PPolyGroup, const EBool PSelectAllForPolyGroupsWithNoSelectedPolygons, E3dVertex* PVertices, const E3dVertexSize PVertexSize)
{
	EIndex  LPointSelectionChanged = 0;

	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;

	if(E3dPolyGroup_HasSelectedPolygons(PPolyGroup))
	{
		EIndex  LPC = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPC < LPN;LPC++, LPolygon++)
		{
			if(LPolygon->VertexNodes)
			{
				if(LPolygon->Flags & E3dPolygonSELECTED)
				{
					const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
					EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
					{
						const EIndex  LVIndex = LVertexNode->VertexID;if(LVIndex > -1)
						{
							E3dVertex*  LVertex = EStruct_ByIndex(PVertices, PVertexSize, LVIndex);
							if((LVertex->Flags & E3dVtxSELECTED) == 0) { LVertex->Flags |= E3dVtxSELECTED;LPointSelectionChanged++; }
						}
						EStruct_Inc(LVertexNode, LVertexNodeSize);
					}
				}
			}
		}
	}
	else if(PSelectAllForPolyGroupsWithNoSelectedPolygons)
	{
		EIndex  LPC = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPC < LPN;LPC++, LPolygon++)
		{
			if(LPolygon->VertexNodes)
			{
				const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
				EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
				{
					const EIndex  LVIndex = LVertexNode->VertexID;if(LVIndex > -1)
					{
						E3dVertex*  LVertex = EStruct_ByIndex(PVertices, PVertexSize, LVIndex);
						if((LVertex->Flags & E3dVtxSELECTED) == 0) { LVertex->Flags |= E3dVtxSELECTED;LPointSelectionChanged++; }
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}
		}
	}

	return(LPointSelectionChanged);
}


static EIndex _UnTagVerticesOfSelectedPolygons(E3dPolyGroup* PPolyGroup, const EBool PSelectAllForPolyGroupsWithNoSelectedPolygons, E3dVertex* PVertices, const E3dVertexSize PVertexSize)
{
	EIndex  LPointSelectionChanged = 0;

	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;

	if(E3dPolyGroup_HasSelectedPolygons(PPolyGroup))
	{
		EIndex  LPC = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPC < LPN;LPC++, LPolygon++)
		{
			if(LPolygon->VertexNodes)
			{
				if(LPolygon->Flags & E3dPolygonSELECTED)
				{
					const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
					EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
					{
						const EIndex  LVIndex = LVertexNode->VertexID;if(LVIndex > -1)
						{
							E3dVertex*  LVertex = EStruct_ByIndex(PVertices, PVertexSize, LVIndex);
							if(LVertex->Flags & E3dVtxSELECTED) { LVertex->Flags &= ~E3dVtxSELECTED;LPointSelectionChanged++; }
						}
						EStruct_Inc(LVertexNode, LVertexNodeSize);
					}
				}
			}
		}
	}
	else if(PSelectAllForPolyGroupsWithNoSelectedPolygons)
	{
		EIndex  LPC = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPC < LPN;LPC++, LPolygon++)
		{
			if(LPolygon->VertexNodes)
			{
				const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
				EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
				{
					const EIndex  LVIndex = LVertexNode->VertexID;if(LVIndex > -1)
					{
						E3dVertex*  LVertex = EStruct_ByIndex(PVertices, PVertexSize, LVIndex);
						if(LVertex->Flags & E3dVtxSELECTED) { LVertex->Flags &= ~E3dVtxSELECTED;LPointSelectionChanged++; }
					}
					EStruct_Inc(LVertexNode, LVertexNodeSize);
				}
			}
		}
	}

	return(LPointSelectionChanged);
}


static EIndex _SplineTagCVs(E3dSpline* PSpline)
{
	EIndex  LPointSelectionChanged = 0;

	EIndex  LI = 0;const EIndex  LN = PSpline->CVs.Count;E3dSplineCV*  LCV = PSpline->CVs.Items;for(;LI < LN;LI++, LCV++)
	{
		if((LCV->CVFlags & E3dCV_SELECTED) == 0)
		{
			LCV->CVFlags |= E3dCV_SELECTED;
			LPointSelectionChanged++;
		}
	}
	return(LPointSelectionChanged);
}


static EIndex _SplineUntagCVs(E3dSpline* PSpline)
{
	EIndex  LPointSelectionChanged = 0;

	EIndex  LI = 0;const EIndex  LN = PSpline->CVs.Count;E3dSplineCV*  LCV = PSpline->CVs.Items;for(;LI < LN;LI++, LCV++)
	{
		if(LCV->CVFlags & E3dCV_SELECTED)
		{
			LCV->CVFlags &= ~E3dCV_SELECTED;
			LPointSelectionChanged++;
		}
	}
	return(LPointSelectionChanged);
}


static EIndex _MeshSelectIntermediatePoints(E3dMesh* PMesh, const EBool PDoBoundaryEdges)
{
	E3dMesh_GetVertexLinksNew(PMesh, E3dVertexLinksEDGES);
	E3dMesh_CreateWingedEdgesFromEdges(PMesh, TRUE, FALSE);
	EIndex  LNVerticesSelected = 0;

	const E3dVertexLinkNew*  LVertexLinks = PMesh->VertexLinksNew;
	const EIndex           LVertexLinksStride = PMesh->VertexLinksStride;
	const E3dWingedEdge*   LMeshWingedEdges = PMesh->WingedEdges;

	EIndex  LMVC = 0;const EIndex  LMVN = PMesh->Vertices.Count;E3dVertex*  LVertex = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;const E3dVertexLinkNew*  LVertexLinksRec = LVertexLinks;for(;LMVC < LMVN;LMVC++, EStruct_Inc(LVertex, LVertexSize), EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
	{
		const EIndex  LVN = LVertexLinksRec->EdgeEnds.Count;if(LVN == 2)
		{
			EIndex  LVC = 0;const E3dEdgeEnd*  LEdgeEnd = LVertexLinksRec->EdgeEnds.Items;for(;LVC < LVN;LVC++, LEdgeEnd++)
			{
				if(!PDoBoundaryEdges)
				{
					const EIndex          LEIndex = LEdgeEnd->EdgeIndex;
					const E3dWingedEdge*  LTWingedEdge = LMeshWingedEdges + LEIndex;
					if(LTWingedEdge->Polygons_Count < 2) goto NextVertex;
				}
			}

			LVertex->Flags |= E3dVtxSELECTED;
			LNVerticesSelected++;
NextVertex:;
		}
	}
printf("%s() %s:%d %lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LNVerticesSelected);fflush(stdout);
	return(LNVerticesSelected);
}


static int _Sc_SelectIntermediatePoints(EPointer PParams)
{
	EIndex        LNTotalSelectedUnselected = 0;
	EChangeFlags  LSceneChangedFlags = 0;

	EListAI_s(, E3dObjectPtr, LObjects);EIndex  LNObjects = E3dScene_GetObjects(E3d_Scene, E3dItemALL, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];
			EIndex      LPointSelectedUnselected = 0;

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

// Select intermediate points on selected Objects
// - On Meshes: select Vertices that subdivide Edges (valence 2, non-boundary Vertices)
//
				LPointSelectedUnselected += _MeshSelectIntermediatePoints(LMesh, TRUE);
//printf("%s() %s:%d %d\n", __FUNCTION__, __FILE__, __LINE__, LPointSelectedUnselected);fflush(stdout);
			}
			else if(EObject_IsSubClass(LObject, E3dSpline))
			{
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
			}

			LNTotalSelectedUnselected += LPointSelectedUnselected;
		}

		EList_FreeA_s(LObjects);
	}

	if(LNTotalSelectedUnselected) LSceneChangedFlags |= E3dChgSELECTION;
	else E3dp_PrintMessage(ELogMESSAGE, 5000, "No points were selected");

//{ char  s[256];printf("%s() %s:%d  %d %s\n", __FUNCTION__, __FILE__, __LINE__, LNTotalSelectedUnselected, E3dSceneChangeFlags_ToString(LSceneChangedFlags, EfCOLOR,  s));fflush(stdout); }

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


static int _Sc_SelectPolygonsWithASelectedEdge(EPointer PParams)
{
	EIndex        LNTotalSelectedUnselected = 0;
	EChangeFlags  LSceneChangedFlags = 0;

	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(E3d_Scene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];

			if(EObject_IsSubClass(LObject, E3dMesh))
			{
				E3dMesh*  LMesh = (E3dMesh*)LObject;
				E3dMesh_CreateWingedEdgesFromEdges(LMesh, FALSE, FALSE);
				EIndex  LEC = 0;const EIndex  LEN = LMesh->Edges.Count;const E3dEdge*  LEdge = LMesh->Edges.Items;const E3dWingedEdge*  LWingedEdge = LMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
				{
					if(LEdge->Flags & E3dEdgeSELECTED)
					{
						EIndex  LPC = 0;const EIndex  LPN = LWingedEdge->Polygons_Count;E3dEdgePolygonInfo*  LPolygonInfo = LWingedEdge->Polygons;for(;LPC < LPN;LPC++, LPolygonInfo++)
						{
							E3dPolygon*  LPolygon = LPolygonInfo->Polygon;
							if((LPolygon->Flags & E3dPolygonSELECTED) == 0)
							{
								LPolygon->Flags |= E3dPolygonSELECTED;
								LNTotalSelectedUnselected++;
							}
						}
					}
				}
			}
		}

		EList_FreeA_s(LObjects);
	}

	if(LNTotalSelectedUnselected > 0) LSceneChangedFlags |= E3dChgSELECTION;

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


//========================================
// Point selecting / unselecting
//========================================
static EChangeFlags _PointSelection(E3dScene* PScene, const int PAction)
{
	EIndex  LNTotalSelectedUnselected = 0;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(PScene, E3dItemALL, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];
			EIndex      LPointSelectedUnselected = 0;

			if(EObject_IsSubClass(LObject, E3dMesh))
			{
				E3dMesh*    LMesh = (E3dMesh*)LObject;
				E3dVertex*  LVertices = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;

				switch(PAction)
				{
					case E3dSELECT_POINTS_OF_SELECTION:
					case E3dUNSELECT_POINTS_OF_SELECTION:
						{
							E3dPolyGroup**  LPolyGroups = LMesh->PolyGroups.Items;
							const EIndex    LPGN = LMesh->PolyGroups.Count;
							const EBool     LSelect = PAction == E3dSELECT_POINTS_OF_SELECTION;

// If the whole Mesh is selected, check for selected Polygons
//
							if(LObject->Selection != E3dObjectSelPOLYGROUP)
							{
								EBool  LHaveSelectedPolygons = FALSE;
								{
									EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
									{
//printf("%s() %s:%d <<%s>>\n", __FUNCTION__, __FILE__, __LINE__, LPolyGroups[LPGI]->Name);fflush(stdout);
									 if(E3dPolyGroup_HasSelectedPolygons(LPolyGroups[LPGI])) { LHaveSelectedPolygons = TRUE;break; }
									}
								}

								if(LHaveSelectedPolygons)
								{
//printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
									EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
									{
										if(LSelect) LPointSelectedUnselected += _TagVerticesOfSelectedPolygons(LPolyGroups[LPGI], FALSE, LVertices, LVertexSize);
										else LPointSelectedUnselected += _UnTagVerticesOfSelectedPolygons(LPolyGroups[LPGI], FALSE, LVertices, LVertexSize);
									}
								}
								else
								{
// No selected Polygons, select all Vertices of the Mesh
//
									if(LSelect)
									{
										EIndex  LVC = 0;const EIndex  LVN = LMesh->Vertices.Count;E3dVertex*  LVertex = LVertices;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize))
										{
											if((LVertex->Flags & E3dVtxSELECTED) == 0) { LVertex->Flags |= E3dVtxSELECTED;LPointSelectedUnselected++; }
										}
									}
									else
									{
										EIndex  LVC = 0;const EIndex  LVN = LMesh->Vertices.Count;E3dVertex*  LVertex = LVertices;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize))
										{
										 if(LVertex->Flags & E3dVtxSELECTED) { LVertex->Flags &= ~E3dVtxSELECTED;LPointSelectedUnselected++; }
										}
									}
								}
							}
							else
							{
								EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
								{
									E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];if(LPolyGroup->Selected)
									{
										if(LSelect) LPointSelectedUnselected += _TagVerticesOfSelectedPolygons(LPolyGroups[LPGI], TRUE, LVertices, LVertexSize);
										else LPointSelectedUnselected += _UnTagVerticesOfSelectedPolygons(LPolyGroups[LPGI], TRUE, LVertices, LVertexSize);
									}
								}
							}
						}
					break;

					case E3dSELECT_UNUSED_POINTS:
						{
							E3dMesh_GetVertexLinksNew(LMesh, E3dVertexLinksEDGES);

							EIndex  LVC = 0;const EIndex  LVN = LMesh->Vertices.Count, LVertexLinksStride = LMesh->VertexLinksStride;E3dVertex*  LVertex = LVertices;const E3dVertexLinkNew*  LVertexLinksRec = LMesh->VertexLinksNew;for(;LVC < LVN;LVC++, EStruct_Inc(LVertex, LVertexSize), EStruct_Inc(LVertexLinksRec, LVertexLinksStride))
							{
								if(LVertexLinksRec->EdgeEnds.Count == 0 && (LVertex->Flags & E3dVtxSELECTED) == 0)
								{
									LVertex->Flags |= E3dVtxSELECTED;
									LNTotalSelectedUnselected++;
								}
							}
						}
					break;
				}
			}
			else if(EObject_IsSubClass(LObject, E3dFace))
			{
				E3dFace*  LFace = (E3dFace*)LObject;

				switch(PAction)
				{
					case E3dSELECT_POINTS_OF_SELECTION:
					case E3dUNSELECT_POINTS_OF_SELECTION:
						{
							const EBool  LSelect = PAction == E3dSELECT_POINTS_OF_SELECTION;

							if(LSelect) LPointSelectedUnselected += _SplineTagCVs(LFace->Exterior);
							else LPointSelectedUnselected += _SplineUntagCVs(LFace->Exterior);
							EIndex  LHC = 0;const EIndex  LHN = LFace->Holes_Count;for(;LHC < LHN;LHC++)
							{
								if(LSelect) LPointSelectedUnselected += _SplineTagCVs(LFace->Holes[LHC]);
								else LPointSelectedUnselected += _SplineUntagCVs(LFace->Holes[LHC]);
							}
						}
					break;
				}
			}
			else if(EObject_IsSubClass(LObject, E3dSpline))
			{
				switch(PAction)
				{
					case E3dSELECT_POINTS_OF_SELECTION:
					case E3dUNSELECT_POINTS_OF_SELECTION:
						{
							const EBool  LSelect = PAction == E3dSELECT_POINTS_OF_SELECTION;
							if(LSelect) LPointSelectedUnselected += _SplineTagCVs((E3dSpline*)LObject);
							else LPointSelectedUnselected += _SplineUntagCVs((E3dSpline*)LObject);
						}
					break;
				}
			}
			else if(EObject_IsSubClass(LObject, E3dPointSet))
			{
printf("%s() %s:%d %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
			}

			if(LPointSelectedUnselected)
			{
				E3dSceneObjectCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, E3dSceneObjectCallbackStruct_class,  E3dCR_POINTS_SELECTION);
				LCBS.Object = LObject;
				LCBS.SubObject = NULL;
				E3d_CallCallbacks((ECoreObject*)PScene,  PScene->Callbacks, PScene->Callbacks_Count,  (ECallbackStruct*)&LCBS);
			}

			LNTotalSelectedUnselected += LPointSelectedUnselected;
		}

		EList_FreeA_s(LObjects);
	}

	if(LNTotalSelectedUnselected) return(E3dChgSELECTION);
	else return(0);
}


static int _Sc_UnselectPoints(EPointer PParams)
{
	E3dScene*           LScene = E3d_Scene;
	const EChangeFlags  LSceneChangeFlags = _PointSelection(LScene, E3dUNSELECT_POINTS_OF_SELECTION);

	if(LSceneChangeFlags) return(E_SUCCESS);
	else E3dp_PrintMessage(ELogMESSAGE, 5000, "No points were selected or unselected");
	return(E_NOTHING_TO_DO);
}


//========================================
//========================================
static void _MCB_SelectIntermediatePoints(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(_Sc_SelectIntermediatePoints(NULL) == E_SUCCESS) E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


//========================================
// Polygon selecting/unselecting
//========================================
static void _MCB_PointSelection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	const int           LAction = (EPointerInt)PClientData;
	E3dScene*           LScene = E3d_Scene;
	const EChangeFlags  LSceneChangeFlags = _PointSelection(LScene, LAction);

	if(LSceneChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LSceneChangeFlags);
	else E3dp_PrintMessage(ELogMESSAGE, 5000, "No points were selected or unselected");
}


static const uint8_t* _GetVertexAttribute(const E3dPolyGroup* PPolyGroup, const E3dPolygon* PPolygon, const char* PSemantic, const EIndex PMeshVertexIndex)
{
	const int  LVAOffset = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, PSemantic, 0);if(LVAOffset < 0) return(NULL);
	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;
	EIndex  LVC = 0;const EIndex  LVN = PPolygon->NumOfVertexNodes;const E3dVertexNode*  LVertexNode = PPolygon->VertexNodes;for(;LVC < LVN;LVC++, EStruct_Inc(LVertexNode, LVertexNodeSize))
	{
		if(LVertexNode->VertexID == PMeshVertexIndex)
		{
			return(((const uint8_t*)LVertexNode + LVAOffset));
		}
	}
	return(NULL);
}


//========================================
// Dialog box callback
//========================================
static void _DCB_Close(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		XeWidget_Unmap(_SelectEdgesAtNormalCreasesDialogW);
		EPluginLock_Del(_Plugin, &_SelectEdgesAtNormalCreasesDialogLock, FALSE);
	}
}


static int _DialogLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	XeWidget_Unmap(_SelectEdgesAtNormalCreasesDialogW);

	return(E_SUCCESS);
}


enum
{
	E3dSelectEdgesAtNormalCreasesVERTEX_NORMAL = 1,
	E3dSelectEdgesAtNormalCreasesPOLYGON_NORMAL,
};

static E3dCoordinate  _CreaseAngle = 10.0;
static int            _SelectEdgesAtNormalCreases_NormalMode = E3dSelectEdgesAtNormalCreasesVERTEX_NORMAL;


static void _SelectEdgesAtNormalCreases(const int PMode, const float PCreaseAngle)
{
	E3dScene*    LScene = E3d_Scene;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE, &LObjects);if(LNObjects > 0)
	{
		EIndex  LAllEdgeSelectionChanged = 0;

		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];

			if(EObject_IsSubClass(LObject, E3dMesh))
			{
				EIndex        LThisMeshEdgeSelectionChanged = 0;
				E3dMesh*      LMesh = (E3dMesh*)LObject;E3dEdge*  LMeshEdges = LMesh->Edges.Items;
				const EIndex  LEN = LMesh->Edges.Count;

				if(LMesh->WingedEdges == NULL) E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);
				if(LMesh->WingedEdges)
				{
					const float  LCreaseAngleCos = E3d_Cos(PCreaseAngle * E3dDEGREES_TO_RADIANS);

// Using the temporary flag E3dEdgeMARK0 allows us to track if we selected any Edges, while keeping the selection non-additive
//
					{ EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;for(;LEC < LEN;LEC++, LEdge++) LEdge->Flags &= ~E3dEdgeMARK0; }

					{
						EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;const E3dWingedEdge*  LWingedEdge = LMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
						{
							if(LWingedEdge->Polygons_Count > 0)
							{
								if(LWingedEdge->Polygons_Count > 1)
								{
									const E3dPolygon*  LPolygon0 = LWingedEdge->Polygons[0].Polygon;const E3dPolyGroup*  LPolyGroup0 = LMesh->PolyGroups.Items[LWingedEdge->Polygons[0].PolyGroupIndex];
									const E3dPolygon*  LPolygon1 = LWingedEdge->Polygons[1].Polygon;const E3dPolyGroup*  LPolyGroup1 = LMesh->PolyGroups.Items[LWingedEdge->Polygons[1].PolyGroupIndex];

									switch(_SelectEdgesAtNormalCreases_NormalMode)
									{
										case E3dSelectEdgesAtNormalCreasesPOLYGON_NORMAL:
											{
												if(E3d_Abs(E_Dot3_vv(LPolygon0->Normal, LPolygon1->Normal)) < LCreaseAngleCos)
												{
													if((LEdge->Flags & E3dEdgeMARK0) == 0)
													{
														LEdge->Flags |= E3dEdgeMARK0;
														LThisMeshEdgeSelectionChanged++;
													}
												}
											}
										break;

										case E3dSelectEdgesAtNormalCreasesVERTEX_NORMAL:
											{
												const EIndex       LEdgeStartVertexIndex = LEdge->Start, LEdgeEndVertexIndex = LEdge->End;
												const E3dCoord3*  L0SNormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup0, LPolygon0, E3dVtxAttribute_Normal, LEdgeStartVertexIndex);
												const E3dCoord3*  L0ENormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup0, LPolygon0, E3dVtxAttribute_Normal, LEdgeEndVertexIndex);
												const E3dCoord3*  L1SNormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup1, LPolygon1, E3dVtxAttribute_Normal, LEdgeStartVertexIndex);
												const E3dCoord3*  L1ENormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup1, LPolygon1, E3dVtxAttribute_Normal, LEdgeEndVertexIndex);

												if(L0SNormal && L0ENormal && L1SNormal && L1ENormal)
												{
													if(E3d_Abs(E_Dot3_pp(L0SNormal, L1SNormal)) < LCreaseAngleCos || E3d_Abs(E_Dot3_pp(L0ENormal, L1ENormal)) < LCreaseAngleCos)
													{
														if((LEdge->Flags & E3dEdgeMARK0) == 0)
														{
															LEdge->Flags |= E3dEdgeMARK0;
															LThisMeshEdgeSelectionChanged++;
														}
													}
												}
												else  // One or both Polygons are in a PolyGroup with no vertex normals
												{
													if((LEdge->Flags & E3dEdgeMARK0) == 0)
													{
														LEdge->Flags |= E3dEdgeMARK0;
														LThisMeshEdgeSelectionChanged++;
													}
												}
											}
										break;
									}
								}
								else  // Boundary Edge
								{
									if((LEdge->Flags & E3dEdgeMARK0) == 0)
									{
										LEdge->Flags |= E3dEdgeMARK0;
										LThisMeshEdgeSelectionChanged++;
									}
								}
							}
							else  // Bad WingedEdge. No Polygons.
							{
							}
						}
					}

					{
						EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;for(;LEC < LEN;LEC++, LEdge++)
						{
							if(LEdge->Flags & E3dEdgeMARK0)
							{
								if((LEdge->Flags & E3dEdgeSELECTED) == 0) { LEdge->Flags |= E3dEdgeSELECTED;LThisMeshEdgeSelectionChanged++; }
							}
							else
							{
								if(LEdge->Flags & E3dEdgeSELECTED) { LEdge->Flags &= ~E3dEdgeSELECTED;LThisMeshEdgeSelectionChanged++; }
							}
						}
					}
				}

				if(LThisMeshEdgeSelectionChanged)
				{
//printf("%s() %s:%d <%s> %d\n", __FUNCTION__, __FILE__, __LINE__, LObject->Name, (int)LThisMeshEdgeSelectionChanged);fflush(stdout);
					LMesh->EdgeFlags |= E3dSELECTED_EDGES;
					E3dMesh_EdgeFlagsUpdate(LMesh);
					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallOUTPUTS,  LScene);
					LAllEdgeSelectionChanged++;
				}
			}
		}

		EList_FreeA_s(LObjects);

		if(LAllEdgeSelectionChanged)
		{
			ECallbackStruct  LCBS;ECallbackStruct_Init(&LCBS, NULL,  E3dCR_SELECTION_CHANGED);
			E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  &LCBS);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
		}
	}
}


static void _DCB_SelectEdgesAtNormalCreases_Angle(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_CreaseAngle = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

	_SelectEdgesAtNormalCreases(_SelectEdgesAtNormalCreases_NormalMode, _CreaseAngle);
}


static void _DCB_SelectEdgesAtNormalCreases_Activate(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_SelectEdgesAtNormalCreases(_SelectEdgesAtNormalCreases_NormalMode, _CreaseAngle);
}


static void _DCB_SelectEdgesAtNormalCreases_NormalMode(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	const int  LMode = (EPointerInt)PClientData;

	if(_SelectEdgesAtNormalCreases_NormalMode != LMode)
	{
		_SelectEdgesAtNormalCreases_NormalMode = LMode;
		_SelectEdgesAtNormalCreases(_SelectEdgesAtNormalCreases_NormalMode, _CreaseAngle);
	}
}


//========================================================
// Pop up connect/disconnect settings dialog box
//========================================================
static void _SelectEdgesAtNormalCreasesPopupDialog(void)
{
	if(_SelectEdgesAtNormalCreasesDialogLock == NULL) _SelectEdgesAtNormalCreasesDialogLock = EPluginLock_Add(_Plugin, "SelectEdgesAtNormalCreasesDialog", _DialogLockDel);

	if(_SelectEdgesAtNormalCreasesDialogW == NULL)
	{
		XeWidget  LW;
		EArg      Args[256];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNCreateButtons, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Select Mesh Edges at creases");EXeSetArg(XeNIconName, "Edge selection");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "Edge selection", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;

		XeAddCallback(LDialogW, XeNWMCallback, _DCB_Close, (EPointer)XeCR_CANCEL);

		_SelectEdgesAtNormalCreasesDialogW = LDialogW;

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);
		EXeStart;
		EXeSetArg(XeNMarginSizeX, 6);EXeSetArg(XeNMarginSizeY, 4);
		Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

		EXeStart;
		EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
		LW = XeScale_New_Coord(LTopMatrixW, "Crease angle",  Args, ArgCnt);
		XeScale_SetValueAndRanges_f32(LW,  _CreaseAngle,  1.0, 90.0,  1.0, 90.0,  FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_SelectEdgesAtNormalCreases_Angle,    (EPointer)NULL);
		XeAddCallback(LW, XeNActivateCallback,     _DCB_SelectEdgesAtNormalCreases_Activate, (EPointer)NULL);

		{
			EXeStart;
			EXeSetArg(XeNRadioBehavior, TRUE);
			XeWidget LMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			XeWidget_ManagedNew("Normal source", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNMarginLeft, 8);
			EXeSetArg(XeNSet, _SelectEdgesAtNormalCreases_NormalMode == E3dSelectEdgesAtNormalCreasesVERTEX_NORMAL);
			LW = XeWidget_ManagedNew("Vertex normals", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_SelectEdgesAtNormalCreases_NormalMode, (EPointer)E3dSelectEdgesAtNormalCreasesVERTEX_NORMAL);

			EXeStart;
			EXeSetArg(XeNMarginLeft, 4);
			EXeSetArg(XeNSet, _SelectEdgesAtNormalCreases_NormalMode == E3dSelectEdgesAtNormalCreasesPOLYGON_NORMAL);
			LW = XeWidget_ManagedNew("Polygon normals", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_SelectEdgesAtNormalCreases_NormalMode, (EPointer)E3dSelectEdgesAtNormalCreasesPOLYGON_NORMAL);
		}

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

	_SelectEdgesAtNormalCreases(_SelectEdgesAtNormalCreases_NormalMode, _CreaseAngle);
}


//========================================
// Polygon selecting/unselecting
//========================================
static EBool _EdgeSelection(const int PAction)
{
	E3dScene*    LScene = E3d_Scene;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LAllEdgeSelectionChanged = 0;

		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];

			if(EObject_IsSubClass(LObject, E3dMesh))
			{
				EIndex        LThisMeshEdgeSelectionChanged = 0;
				E3dMesh*      LMesh = (E3dMesh*)LObject;E3dEdge*  LMeshEdges = LMesh->Edges.Items;
				const EIndex  LEN = LMesh->Edges.Count;

				switch(PAction)
				{
					case E3dCrSELECT_EDGES_WITH_SELECTED_VERTICES:
						{
							const E3dVertexSize  LVertexSize = LMesh->VertexSize;
							EIndex  LEC = 0;const E3dVertex*  LVertices = LMesh->Vertices.Items;E3dEdge*  LEdge = LMeshEdges;for(;LEC < LEN;LEC++, LEdge++)
							{
								const E3dVertex*  LStartVertex = EStruct_ByIndex(LVertices, LVertexSize, LEdge->Start);
								const E3dVertex*  LEndVertex   = EStruct_ByIndex(LVertices, LVertexSize, LEdge->End);
								if(LStartVertex->Flags & E3dVtxSELECTED && LEndVertex->Flags & E3dVtxSELECTED)
								{
									if((LEdge->Flags & E3dEdgeSELECTED) == 0)
									{
										LEdge->Flags |= E3dEdgeSELECTED;
										LThisMeshEdgeSelectionChanged++;
									}
								}
							}
						}
					break;

					case E3dCrSELECT_EDGES_OF_SELECTED_POLYGONS:
						{
							const EIndex  LPGN = LMesh->PolyGroups.Count;
							EBool         LRebuildWingedEdges = FALSE;

							{
								EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
								{
									E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
									if(E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_EdgeIndex, 0,  NULL) != E_NOTHING_TO_DO) LRebuildWingedEdges = TRUE;
								}
							}

							if(LRebuildWingedEdges) E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);
							if(LMesh->WingedEdges)
							{
								EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
								{
									const E3dPolyGroup*      LPolyGroup = LMesh->PolyGroups.Items[LPGI];
									const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
									const E3dVertexNodeSize  LEdgeIndexOffset = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_EdgeIndex, 0);

									if(LEdgeIndexOffset >= 0)
									{
										EIndex  LPC = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(;LPC < LPN;LPC++, LPolygon++)
										{
											if(LPolygon->Flags & E3dPolygonSELECTED)
											{
												EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;const E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;for(;LVC < LVN;LVC++, EStruct_Inc(LVertexNode, LVertexNodeSize))
												{
													if(LVertexNode->VertexID >= 0)
													{
														const E3dEdgeIndex  LEdgeIndexV = *((E3dEdgeIndex*)((const uint8_t*)LVertexNode + LEdgeIndexOffset)), LEdgeIndex = LEdgeIndexV & E3dVtxNodeFlagEDGE_INDEX_MASK;
														E3dEdge*            LEdge = LMeshEdges + LEdgeIndex;

														if((LEdge->Flags & E3dEdgeSELECTED) == 0)
														{
															LEdge->Flags |= E3dEdgeSELECTED;
															LThisMeshEdgeSelectionChanged++;
														}
													}
												}
											}
										}
									}
								}
							}
						}
					break;

					case E3dCrUNSELECT_EDGES:
						{
							EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;for(;LEC < LEN;LEC++, LEdge++)
							{
								if(LEdge->Flags & E3dEdgeSELECTED)
								{
									LEdge->Flags &= ~E3dEdgeSELECTED;
									LThisMeshEdgeSelectionChanged++;
								}
							}
						}
					break;

					case E3dCrSELECT_EDGES_AT_NORMAL_CREASES:
						_SelectEdgesAtNormalCreasesPopupDialog();

/*
						{
							if(LMesh->WingedEdges == NULL) E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);if(LMesh->WingedEdges)
							{
								const float  LCreaseAngle = 10.0, LCreaseAngleCos = E3d_Cos(LCreaseAngle * E3dDEGREES_TO_RADIANS);
								EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;const E3dWingedEdge*  LWingedEdge = LMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
								{
									if(LWingedEdge->Polygons_Count > 0)
									{
										if(LWingedEdge->Polygons_Count > 1)
										{
											const EIndex       LEdgeStartVertexIndex = LEdge->Start, LEdgeEndVertexIndex = LEdge->End;
											const E3dPolygon*  LPolygon0 = LWingedEdge->Polygons[0].Polygon;const E3dPolyGroup*  LPolyGroup0 = LMesh->PolyGroups[LWingedEdge->Polygons[0].PolyGroupIndex];
											const E3dPolygon*  LPolygon1 = LWingedEdge->Polygons[1].Polygon;const E3dPolyGroup*  LPolyGroup1 = LMesh->PolyGroups[LWingedEdge->Polygons[1].PolyGroupIndex];

											const E3dCoord3*  L0SNormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup0, LPolygon0, E3dVtxAttribute_Normal, LEdgeStartVertexIndex);
											const E3dCoord3*  L0ENormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup0, LPolygon0, E3dVtxAttribute_Normal, LEdgeEndVertexIndex);
											const E3dCoord3*  L1SNormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup1, LPolygon1, E3dVtxAttribute_Normal, LEdgeStartVertexIndex);
											const E3dCoord3*  L1ENormal = (const E3dCoord3*)_GetVertexAttribute(LPolyGroup1, LPolygon1, E3dVtxAttribute_Normal, LEdgeEndVertexIndex);
											if(L0SNormal && L0ENormal && L1SNormal && L1ENormal)
											{
												if(E3d_Abs(E_Dot3_pp(L0SNormal, L1SNormal)) < LCreaseAngleCos || E3d_Abs(E_Dot3_pp(L0ENormal, L1ENormal)) < LCreaseAngleCos)
												{
													if((LEdge->Flags & E3dEdgeSELECTED) == 0)
													{
														LEdge->Flags |= E3dEdgeSELECTED;
														LThisMeshEdgeSelectionChanged++;
													}
												}
											}
											else  // One or both Polygons are in a PolyGroup with no normals
											{
												if((LEdge->Flags & E3dEdgeSELECTED) == 0)
												{
													LEdge->Flags |= E3dEdgeSELECTED;
													LThisMeshEdgeSelectionChanged++;
												}
											}
										}
										else  // Boundary Edge
										{
											if((LEdge->Flags & E3dEdgeSELECTED) == 0)
											{
												LEdge->Flags |= E3dEdgeSELECTED;
												LThisMeshEdgeSelectionChanged++;
											}
										}
									}
									else  // Bad WingedEdge. No Polygons.
									{
									}
								}
							}
						}
*/
					break;

					case E3dCrSELECT_BOUNDARY_EDGES:
printf("%s() %s:%d  %sIMPLEMENT ME%s select boundary edges of selected Polygons, or the selected Mesh or PolyGroup\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
						{
							if(LMesh->WingedEdges == NULL) E3dMesh_CreateWingedEdgesFromEdges(LMesh, TRUE, FALSE);
							if(LMesh->WingedEdges)
							{
								EIndex  LEC = 0;E3dEdge*  LEdge = LMeshEdges;const E3dWingedEdge*  LWingedEdge = LMesh->WingedEdges;for(;LEC < LEN;LEC++, LEdge++, LWingedEdge++)
								{
									if(LWingedEdge->Polygons_Count > 0)
									{
										if(LWingedEdge->Polygons_Count > 1)
										{
											const E3dPolygon*  LPolygon0 = LWingedEdge->Polygons[0].Polygon;const E3dPolyGroup*  LPolyGroup0 = LMesh->PolyGroups.Items[LWingedEdge->Polygons[0].PolyGroupIndex];
											const E3dPolygon*  LPolygon1 = LWingedEdge->Polygons[1].Polygon;const E3dPolyGroup*  LPolyGroup1 = LMesh->PolyGroups.Items[LWingedEdge->Polygons[1].PolyGroupIndex];

											if((LPolygon0->Flags & E3dPolygonSELECTED) != (LPolygon1->Flags & E3dPolygonSELECTED) || LPolyGroup0 != LPolyGroup1)
											{
												if((LEdge->Flags & E3dEdgeSELECTED) == 0)
												{
													LEdge->Flags |= E3dEdgeSELECTED;
													LThisMeshEdgeSelectionChanged++;
												}
											}
										}
										else  // Boundary Edge
										{
											if((LEdge->Flags & E3dEdgeSELECTED) == 0)
											{
												LEdge->Flags |= E3dEdgeSELECTED;
												LThisMeshEdgeSelectionChanged++;
											}
										}
									}
									else  // Bad WingedEdge. No Polygons.
									{
									}
								}
							}
						}
					break;
				}

				if(LThisMeshEdgeSelectionChanged)
				{
					LMesh->EdgeFlags |= E3dSELECTED_EDGES;
					E3dMesh_EdgeFlagsUpdate(LMesh);
					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL & ~(E3dUpdateEDGES|E3dUpdateREMOVE_STRIPS|E3dUpdateTRIANGULATE), E3dCallALL & ~E3dCallOUTPUTS,  LScene);
				}

printf("%s() %s:%d <%s> %d\n", __FUNCTION__, __FILE__, __LINE__, LObject->Name, (int)LThisMeshEdgeSelectionChanged);fflush(stdout);
				if(LThisMeshEdgeSelectionChanged) LAllEdgeSelectionChanged += LThisMeshEdgeSelectionChanged;
			}
		}

		EList_FreeA_s(LObjects);

		if(LAllEdgeSelectionChanged)
		{
			ECallbackStruct  LCBS;ECallbackStruct_Init(&LCBS, NULL,  E3dCR_SELECTION_CHANGED);
			E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  &LCBS);

			return(TRUE);
		}
	}
	else
	{
		E3dp_PrintMessage(ELogMESSAGE, 5000, "Select a Mesh");return(FALSE);
	}
	return(FALSE);
}


//========================================
// Polygon selecting/unselecting
//========================================
static void _PolySelection(const int PAction)
{
	E3dScene*    LScene = E3d_Scene;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemALL, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		EIndex  LTotalPolyNum = 0;(void)LTotalPolyNum;

		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dObject*  LObject = LObjects.Items[LObjC];
			EBool       LPolygonSelectionChanged = FALSE, LPolyGroupSelectionChanged = FALSE, LPointSelectionChanged = FALSE;

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

				E3dPolyGroup**  LPolyGroups = LMesh->PolyGroups.Items;
				const EIndex    LPGN = LMesh->PolyGroups.Count;

				{
					E3dVertex*  LVertices = LMesh->Vertices.Items;const E3dVertexSize  LVertexSize = LMesh->VertexSize;

					EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];

						if(PAction == E3dCrINVERT_POLYGROUP_SELECTION)
						{
							if(LPolyGroup->Selected)
							{
								LPolyGroup->Selected = FALSE;
							}
							else LPolyGroup->Selected = TRUE;
							LPolyGroupSelectionChanged = TRUE;
						}
						else if(LPolyGroup->Selected || LObject->Selection != E3dObjectSelPOLYGROUP)
						{
							EBool  LPGPolygonSelectionChanged = FALSE;

							LTotalPolyNum += LPolyGroup->Polygons.Count;
							const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;
							EIndex  LPolyCnt;

							switch(PAction)
							{
								case E3dCrPOLYGONS_WITH_ALL_VERTICES_SELECTED:
									if((LPolyCnt = LPolyGroup->Polygons.Count) > 0)
									{
										E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;
										do
										{
											E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;if(LVertexNode)
											{
												EIndex  LNSelVs = 0;
												EIndex  LVC = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(;LVC < LVN;LVC++)
												{
													if(LVertexNode->VertexID > -1)
													{
														const E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexNode->VertexID);
														if(LVertex->Flags & E3dVtxSELECTED) LNSelVs++;
													}
													EStruct_Inc(LVertexNode, LVertexNodeSize);
												}
												if(LNSelVs == LPolygon->NumOfVertices && (LPolygon->Flags & E3dPolygonSELECTED) == 0)
												{
													LPolygon->Flags |= E3dPolygonSELECTED;
													LPGPolygonSelectionChanged = TRUE;
												}
											}
											LPolygon++;
										} while(--LPolyCnt);
										if(LPGPolygonSelectionChanged) LPolygonSelectionChanged = TRUE;
									}
								break;

								case E3dCrPOLYGONS_WITH_A_SELECTED_EDGE:
									if(_Sc_SelectPolygonsWithASelectedEdge(NULL) == E_SUCCESS) LPGPolygonSelectionChanged = TRUE;
								break;

								case E3dCrSELECT_TRIS:
									if((LPolyCnt = LPolyGroup->Polygons.Count) > 0)
									{
										E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;
										do
										{
											if(LPolygon->NumOfVertices == 3 && (LPolygon->Flags & E3dPolygonSELECTED) == 0)
											{
												LPolygon->Flags |= E3dPolygonSELECTED;
												LPGPolygonSelectionChanged = TRUE;
											}
											LPolygon++;
										} while(--LPolyCnt);
										if(LPGPolygonSelectionChanged) LPolygonSelectionChanged = TRUE;
									}
								break;

								case E3dCrSELECT_QUADS:
									if((LPolyCnt = LPolyGroup->Polygons.Count) > 0)
									{
										E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;
										do
										{
											if(LPolygon->NumOfVertices == 4 && (LPolygon->Flags & E3dPolygonSELECTED) == 0)
											{
												LPolygon->Flags |= E3dPolygonSELECTED;
												LPGPolygonSelectionChanged = TRUE;
											}
											LPolygon++;
										} while(--LPolyCnt);
										if(LPGPolygonSelectionChanged) LPolygonSelectionChanged = TRUE;
									}
								break;

								case E3dCrUNSELECT_ALL_POLYS:
									if((LPolyCnt = LPolyGroup->Polygons.Count) > 0)
									{
										E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;
										do
										{
											if(LPolygon->Flags & E3dPolygonSELECTED)
											{
												LPolygon->Flags &= ~E3dPolygonSELECTED;
												LPGPolygonSelectionChanged = TRUE;
											}
											LPolygon++;
										} while(--LPolyCnt);
										if(LPGPolygonSelectionChanged) LPolygonSelectionChanged = TRUE;
									}
								break;

								case E3dCrINVERT_POLYGON_SELECTION:
									if((LPolyCnt = LPolyGroup->Polygons.Count) > 0)
									{
										E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;
										do
										{
											LPolygon->Flags ^= E3dPolygonSELECTED;
											LPGPolygonSelectionChanged = TRUE;
											LPolygon++;
										} while(--LPolyCnt);
										LPolygonSelectionChanged = TRUE;
									}
								break;
							}

							if(LPGPolygonSelectionChanged)
							{
								E3dSceneObjectCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, E3dSceneObjectCallbackStruct_class,  E3dCR_POLYGON_SELECTION);
								LCBS.Object = LObject;
								LCBS.SubObject = (EObject*)LPolyGroup;
								E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  (ECallbackStruct*)&LCBS);
							}
						}
					}
				}

				if(LPolyGroupSelectionChanged)
				{
// Set the Mesh's Selection mode, based on whether any PolyGroups are selected
//
					EBool  LPolyGroupSelected = FALSE;
					EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];
						if(LPolyGroup->Selected) { LPolyGroupSelected = TRUE;break; }
					}
					if(LPolyGroupSelected)
					{
						LMesh->Selection = E3dObjectSelPOLYGROUP;
					}
					else LMesh->Selection = E3dObjectSelNONE;
				}
			}

			if(LPointSelectionChanged)
			{
				E3dSceneObjectCallbackStruct  LCBS;ECallbackStruct_Init((ECallbackStruct*)&LCBS, E3dSceneObjectCallbackStruct_class,  E3dCR_POINTS_SELECTION);
				LCBS.Object = LObject;
				LCBS.SubObject = NULL;
				E3d_CallCallbacks((ECoreObject*)LScene,  LScene->Callbacks, LScene->Callbacks_Count,  (ECallbackStruct*)&LCBS);
			}

			(void)LPolygonSelectionChanged;
		}

		EList_FreeA_s(LObjects);
	}
	else E3dp_PrintMessage(ELogMESSAGE, 5000, "Select something");
}


//========================================
// Menu callback
//========================================
static void _MCB_NodeSelection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_NodeSelection((EPointerInt)PClientData);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


//========================================
// Menu callback
//========================================
static void _MCB_SelectHidden(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_SelectHidden((EPointerInt)PClientData);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


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

	E3dSel_SceneUnselectAll(LScene);

	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, (EPointerInt)PClientData, 0, FALSE, &LObjects);if(LNObjects > 0)
	{
		EIndex  LObjC = 0;for(;LObjC < LNObjects;LObjC++)
		{
			E3dSel_ObjectSelect(LScene, LObjects.Items[LObjC], FALSE, FALSE);
		}

		if(LNObjects == 1) E3dp_PrintMessage(ELogMESSAGE, 5000, "Selected <%s>", LObjects.Items[0]->Name ? LObjects.Items[0]->Name : "");
		else E3dp_PrintMessage(ELogMESSAGE, 5000, "Selected %lld Objects", (long long)LNObjects);

		EList_FreeA_s(LObjects);
	}

	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
}


//========================================
// Menu callback
//========================================
static void _MCB_EdgeSelection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	if(_EdgeSelection((EPointerInt)PClientData)) E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


//========================================
// Menu callback
//========================================
static void _MCB_PolySelection(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_PolySelection((EPointerInt)PClientData);
	E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


//========================================
// Key callback	
//========================================
static void _KCB_Selection(EKeyCode LKey, unsigned int LModifiers, int LKeyEventType, EPointer PClientData)
{
	_NodeSelection((EPointerInt)PClientData);
	E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
	E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgSELECTION);
}


//========================================
// Key callback
//========================================
static void _KCB_HrcWalk(const EKeyCode PKey, const unsigned int PModifiers, const int PKeyEventType, EPointer PClientData)
{
	E3dScene*     LScene = E3d_Scene;
	EChangeFlags  LChanged = 0;

	EIndex  LRI = 0;const EIndex  LRN = LScene->RootNodes.Count;E3dNode**  LRootNodes = LScene->RootNodes.Items;for(;LRI < LRN;LRI++)
	{
		E3dNode*  LRootNode = LRootNodes[LRI];
		E3dNode*  LNode = LRootNode;for(;LNode;LNode = LNode->Next)
		{
			if(LNode->Selection != E3dNodeSelNONE)
			{
				switch((EPointerInt)PClientData)
				{
					case E3dCrGO_UP:
						if(LNode->Selection == E3dNodeSelOBJECT)
						{
// If an Object is selected and it's only instantiated once, we can switch to Node selection mode.
//
printf("%s() %s:%d %sUP%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);

							int  LNodesSelected = 0;

							E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
							{
								EIndex  LI = 0;const EIndex  LN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LI < LN;LI++)
								{
									const E3dObject*  LObject = LObjects[LI];if(LObject->Selection)
									{
										switch(LObject->Selection)
										{
											case E3dObjectSelOBJECT:
												if(LObject->Models.Count < 2)
												{
													E3dSel_NodeSelect(LScene, LNode, TRUE, TRUE);
													LChanged |= E3dChgSELECTION;
												}
												else
												{
// LObject instantiated by multiple Models. Select all of them.
//
													E3dSel_NodeSelect(LScene, LNode, TRUE, FALSE);
													EIndex  LIC = 0;for(;LIC < LObject->Models.Count;LIC++)
													{
														if(LObject->Models.Items[LIC] != (E3dModel*)LNode) { E3dSel_NodeSelect(LScene, LNode, FALSE, FALSE);LChanged |= E3dChgSELECTION; }
													}
													E3dp_PrintMessage(ELogMESSAGE, 2000, "Object \"%s\" is instantiated by multiple Nodes (it's not clear which one to select).", LObject->Name ? LObject->Name : "");
												}
												LNodesSelected = LObject->Models.Count;
											break;

											case E3dObjectSelPOLYGROUP:
												{
													E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
													{
// If a PolyGroup is selected going up means selecting the Mesh
//
														LMesh->Selection = 0;  // We must set this to 0, so E3dSel_ObjectSelect will work properly

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

														E3dSel_ObjectSelect(LScene, LObjects[LI], TRUE, TRUE);
														LChanged |= E3dChgSELECTION;
													}
												}
											break;
										}
										break;
									}
								}
							}

							if(LNodesSelected)
							{
								if(LNodesSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected Node: \"%s\"", LNode->Name ? LNode->Name : "");
								else E3dp_PrintMessage(ELogMESSAGE, 2000, "Multiple Nodes selected");
							}
						}
						else
						{
							E3dNode*  LTNode = LNode->Parent;if(LTNode)
							{
								int  LSelected = 0;
								switch(LNode->Selection)
								{
									case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
									case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectBranch(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
								}

								if(LSelected)
								{
									LChanged |= E3dChgSELECTION;
									if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected parent Node: \"%s\"", LTNode->Name ? LTNode->Name : "");
									else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected parent Branch: \"%s\"", LTNode->Name ? LTNode->Name : "");
								}
							}
							else E3dp_PrintMessage(ELogWARNING, 2000, "Node \"%s\" has no parent.", LNode->Name ? LNode->Name : "");
						}
					break;

					case E3dCrGO_DOWN:
						if(LNode->Selection == E3dNodeSelOBJECT)
						{
							E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
							{
								EIndex  LI = 0;const EIndex  LN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LI < LN;LI++)
								{
									E3dObject*  LObject = LObjects[LI];if(LObject->Selection)
									{
										switch(LObject->Selection)
										{
											case E3dObjectSelOBJECT:
												{
													E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
													{
														if(LMesh->PolyGroups.Count)
														{
															E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[0];

															E3dSel_MeshPolyGroupSelect(LScene, LMesh, LPolyGroup,  EBoolTOGGLE, TRUE, TRUE);
															LChanged |= E3dChgSELECTION;
														}
													}
												}
											break;
										}
									}
								}
							}
						}
						else
						{
							E3dNode*  LTNode = LNode->Child;if(LTNode)
							{
								int  LSelected = 0;
								switch(LNode->Selection)
								{
									case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
									case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectBranch(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
								}

								if(LSelected)
								{
									LChanged |= E3dChgSELECTION;
									if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected child Node:  \"%s\"", LTNode->Name ? LTNode->Name : "");
									else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected child Branch:  \"%s\"", LTNode->Name ? LTNode->Name : "");
								}
							}
							else
							{
								E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
								{
									if(LModel->Objects.Count > 0)
									{
										E3dSel_ObjectSelect(LScene, LModel->Objects.Items[0], TRUE, TRUE);
										LChanged |= E3dChgSELECTION;
										E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected Object:  \"%s\"", LModel->Objects.Items[0]->Name ? LModel->Objects.Items[0]->Name : "");
									}
								}
								else E3dp_PrintMessage(ELogWARNING, 2000, "Node \"%s\" has no child Node and no Objects.", LNode->Name ? LNode->Name : "");
							}
						}
					break;

					case E3dCrGO_LEFT:
						if(LNode->Selection == E3dNodeSelOBJECT)
						{
							E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
							{
								EIndex  LI = 0;const EIndex  LN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LI < LN;LI++)
								{
									E3dObject*  LObject = LObjects[LI];if(LObject->Selection)
									{
										switch(LObject->Selection)
										{
											case E3dObjectSelOBJECT:
												{
													if(LI > 0)
													{
														E3dSel_ObjectSelect(LScene, LObjects[LI - 1], TRUE, TRUE);
														LChanged |= E3dChgSELECTION;
													}
													else E3dp_PrintMessage(ELogWARNING, 2000, "Object \"%s\" has no next sibling.", LObject->Name ? LObject->Name : "");
												}
											break;

											case E3dObjectSelPOLYGROUP:
												{
													E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
													{
														const EIndex  LPGN = LMesh->PolyGroups.Count;if(LPGN > 1)
														{
															EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
															{
																if(LMesh->PolyGroups.Items[LPGI]->Selected) break;
															}
															if(LPGI == LPGN);  // No PolyGroup selected
															else if(LPGI == 0)
															{
																const E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
																E3dp_PrintMessage(ELogWARNING, 2000, "PolyGroup \"%s\" has no previous sibling.", LPolyGroup->Name ? LPolyGroup->Name : "");
															}
															else
															{
																E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI - 1];

																E3dSel_MeshPolyGroupSelect(LScene, LMesh, LPolyGroup,  EBoolTOGGLE, TRUE, TRUE);
																LChanged |= E3dChgSELECTION;
															}
														}
														else if(LPGN > 0) E3dp_PrintMessage(ELogWARNING, 2000, "PolyGroup \"%s\" has no next sibling.", LMesh->PolyGroups.Items[0]->Name ? LMesh->PolyGroups.Items[0]->Name : "");
														else E3dp_PrintMessage(ELogWARNING, 2000, "Mesh \"%s\" has no next PolyGroups.", LMesh->Name ? LMesh->Name : "");
													}
												}
											break;
										}
										break;
									}
								}
							}
						}
						else
						{
							E3dNode*  LTNode = LNode->PrevSibling;if(LTNode)
							{
								int  LSelected = 0;
								switch(LNode->Selection)
								{
									case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
									case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectBranch(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
								}

								if(LSelected)
								{
									LChanged |= E3dChgSELECTION;
									if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected prev sibling Node:  \"%s\"", LTNode->Name ? LTNode->Name : "");
									else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected prev sibling Branch:  \"%s\"", LTNode->Name ? LTNode->Name : "");
								}
							}
							else
							{
								if(LNode == LRootNode)
								{
									if(LRI > 0)
									{
										int  LSelected = 0;
										LTNode = LScene->RootNodes.Items[LRI - 1];
										switch(LNode->Selection)
										{
											case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
											case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectTree(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
										}

										if(LSelected)
										{
											LChanged |= E3dChgSELECTION;
											if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected previous hierarchy root Node:  \"%s\"", LTNode->Name ? LTNode->Name : "");
											else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected previous hierarchy:  \"%s\"", LTNode->Name ? LTNode->Name : "");
										}
									}
								}
								else E3dp_PrintMessage(ELogWARNING, 2000, "Node \"%s\" has no previous hierarchy root.", LNode->Name ? LNode->Name : "");
							}
						}
					break;

					case E3dCrGO_RIGHT:
						if(LNode->Selection == E3dNodeSelOBJECT)
						{
							E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
							{
								EIndex  LI = 0;const EIndex  LN = LModel->Objects.Count;E3dObject**  LObjects = LModel->Objects.Items;for(;LI < LN;LI++)
								{
									E3dObject*  LObject = LObjects[LI];if(LObject->Selection)
									{
										switch(LObject->Selection)
										{
											case E3dObjectSelOBJECT:
												if(LI < LN - 1)
												{
													E3dSel_ObjectSelect(LScene, LObjects[LI + 1], TRUE, TRUE);
													LChanged |= E3dChgSELECTION;
												}
												else E3dp_PrintMessage(ELogWARNING, 2000, "Object \"%s\" has no next sibling.", LObjects[LI]->Name);
											break;

											case E3dObjectSelPOLYGROUP:
												{
													E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
													{
														const EIndex  LPGN = LMesh->PolyGroups.Count;if(LPGN > 1)
														{
															EIndex  LPGI = 0;for(;LPGI < LPGN;LPGI++)
															{
																if(LMesh->PolyGroups.Items[LPGI]->Selected) break;
															}
															if(LPGI == LPGN);  // No PolyGroup selected
															else if(LPGI == LPGN - 1)
															{
																const E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
																E3dp_PrintMessage(ELogWARNING, 2000, "PolyGroup \"%s\" has no next sibling.", LPolyGroup->Name ? LPolyGroup->Name : "");
															}
															else
															{
																E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI + 1];

																E3dSel_MeshPolyGroupSelect(LScene, LMesh, LPolyGroup,  EBoolTOGGLE, TRUE, TRUE);
																LChanged |= E3dChgSELECTION;
															}
														}
														else if(LPGN > 0) E3dp_PrintMessage(ELogWARNING, 2000, "PolyGroup \"%s\" has no next sibling.", LMesh->PolyGroups.Items[0]->Name ? LMesh->PolyGroups.Items[0]->Name : "");
														else E3dp_PrintMessage(ELogWARNING, 2000, "Mesh \"%s\" has no next PolyGroups.", LMesh->Name ? LMesh->Name : "");
													}
												}
											break;
										}
										break;
									}
								}
							}
						}
						else
						{
							E3dNode*  LTNode = LNode->NextSibling;if(LTNode)
							{
								int  LSelected = 0;
								switch(LNode->Selection)
								{
									case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
									case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectBranch(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
								}

								if(LSelected)
								{
									LChanged |= E3dChgSELECTION;
									if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected next sibling Node:  \"%s\"", LTNode->Name ? LTNode->Name : "");
									else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected next sibling Branch:  \"%s\"", LTNode->Name ? LTNode->Name : "");
								}
							}
							else
							{
								if(LNode == LRootNode)
								{
									if(LRI < LRN - 1)
									{
										int  LSelected = 0;
										LTNode = LScene->RootNodes.Items[LRI + 1];

										switch(LNode->Selection)
										{
											case E3dNodeSelNODE:         E3dSel_NodeSelect(LScene, LTNode, TRUE, TRUE);LSelected = 1;break;
											case E3dNodeSelBRANCH_ROOT:  E3dSel_NodeSelectTree(LScene, LTNode, TRUE, TRUE);LSelected = 2;break;
										}

										if(LSelected)
										{
											LChanged |= E3dChgSELECTION;
											if(LSelected == 1) E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected next hierarchy root Node:  \"%s\"", LTNode->Name ? LTNode->Name : "");
											else E3dp_PrintMessage(ELogMESSAGE, 2000, "Selected next hierarchy:  \"%s\"", LTNode->Name);
										}
									}
									else E3dp_PrintMessage(ELogWARNING, 2000, "Node \"%s\" has no next sibling.", LNode->Name ? LNode->Name : "");
								}
								else E3dp_PrintMessage(ELogWARNING, 2000, "Node \"%s\" has no next sibling.", LNode->Name ? LNode->Name : "");
							}
						}
					break;
				}
				goto Done;
			}
		}
	}

Done:
	if(LChanged)
	{
		E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);
		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
}


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

//	EPluginLock_Add(_Plugin, NULL, NULL);  // Lock the Plugin

//	E3dp_SelectByNameMenuButton = XePushButton_Add(Xe_App, "Menu/Select", "By name", '\0', NULL, NULL, NULL, _MCB_NodeSelection, (EPointer)E3dCrSELECT_BY_NAME);

	const EKeyModifiers  LCtrlMask = Xe_CommandKeyForCtrl ? E_CommandModifierMask : EControlMask;const char*  LCtrlName = Xe_CommandKeyForCtrl ? "Cmd" : "Ctrl";char                 LAccText[256];

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All Models",                    '\0', NULL,                NULL,           FALSE, NULL, _MCB_NodeSelection,         (EPointer)E3dCrALL_NORMAL_MODELS);
	sprintf(LAccText, "Shift+%s+A", LCtrlName);_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All hierarchies",               '\0', "Shift Ctrl <Key>A", LAccText, FALSE, NULL, _MCB_NodeSelection,         (EPointer)E3dCrALL_HIERARCHIES);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Empty Models",                  '\0', NULL,                NULL,           FALSE, NULL, _MCB_NodeSelection,         (EPointer)E3dCrEMPTY_MODELS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All Splines",                   '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectObjects,         (EPointer)E3dItemSPLINE);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All Spline Faces",              '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectObjects,         (EPointer)E3dItemFACE);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All Meshes",                    '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectObjects,         (EPointer)E3dItemMESH);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "All Geometries",                '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectObjects,         (EPointer)E3dItemGEOMETRY);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Hidden Geometries",             '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectHidden,          (EPointer)E3dItemGEOMETRY);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Hidden Nodes and Geometries",   '\0', NULL,                NULL,           FALSE, NULL, _MCB_SelectHidden,          (EPointer)(E3dItemNODE|E3dItemGEOMETRY));

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Unselect everything",           '\0', NULL, NULL, FALSE, NULL, _MCB_NodeSelection, (EPointer)E3dCrUNSELECT_EVERYTHING);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Unselect all Nodes",            '\0', NULL, NULL, FALSE, NULL, _MCB_NodeSelection, (EPointer)E3dCrUNSELECT_ALL_MODELS);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Invert PolyGroup selection",    '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrINVERT_POLYGROUP_SELECTION);

//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Edges",      '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrSELECT_EDGES);
//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select", "Hulls",      '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrSELECT_HULLS);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Points", "Select points of selected Objects",    '\0', NULL, NULL, FALSE, NULL, _MCB_PointSelection,           (EPointer)E3dSELECT_POINTS_OF_SELECTION);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Points", "Un-select points of selected Objects", '\0', NULL, NULL, FALSE, NULL, _MCB_PointSelection,           (EPointer)E3dUNSELECT_POINTS_OF_SELECTION);
	E3dScriptFunction_Register("UnselectPoints", "Unselect points", "Unselect selected Vertices on selected Meshes, or selected CVs on selected Splines.",  _Sc_UnselectPoints, NULL,  _ScriptFunctions + _ScriptFunctions_Count++);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Points", "Select unused points",                 '\0', NULL, NULL, FALSE, NULL, _MCB_PointSelection,           (EPointer)E3dSELECT_UNUSED_POINTS);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Points", "Select Edge splitter points",          '\0', NULL, NULL, FALSE, NULL, _MCB_SelectIntermediatePoints, (EPointer)0);
	E3dScriptFunction_Register("SelectIntermediatePoints", "Select intermediate points", "Select Edge bisector Vertices on selected Meshes, or intermediate CVs on selected Splines.",  _Sc_SelectIntermediatePoints, NULL,  _ScriptFunctions + _ScriptFunctions_Count++);


	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Edges", "Select Edges with selected Vertices",   '\0', NULL, NULL, FALSE, NULL, _MCB_EdgeSelection, (EPointer)E3dCrSELECT_EDGES_WITH_SELECTED_VERTICES);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Edges", "Select Edges of selected Polygons",     '\0', NULL, NULL, FALSE, NULL, _MCB_EdgeSelection, (EPointer)E3dCrSELECT_EDGES_OF_SELECTED_POLYGONS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Edges", "Unselect Edges",                        '\0', NULL, NULL, FALSE, NULL, _MCB_EdgeSelection, (EPointer)E3dCrUNSELECT_EDGES);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Edges", "Select Edges at normal creases",        '\0', NULL, NULL, FALSE, NULL, _MCB_EdgeSelection, (EPointer)E3dCrSELECT_EDGES_AT_NORMAL_CREASES);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Edges", "Select boundary Edges",                 '\0', NULL, NULL, FALSE, "Select Edges at the boundaries between PolyGroups, between selected Polygons,\nand at the boundaries of Meshes (e.g. to find holes in a Mesh)", _MCB_EdgeSelection, (EPointer)E3dCrSELECT_BOUNDARY_EDGES);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Polygons with all Vertices selected",'\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrPOLYGONS_WITH_ALL_VERTICES_SELECTED);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Polygons with a selected Edge",      '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrPOLYGONS_WITH_A_SELECTED_EDGE);
	E3dScriptFunction_Register("SelectPolygonsWithASelectedEdge", "Select Polygons with a selected Edge", "Select Polygons that have at least one selected Edge.",  _Sc_SelectPolygonsWithASelectedEdge, NULL,  _ScriptFunctions + _ScriptFunctions_Count++);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Triangles",                          '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrSELECT_TRIS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Quads",                              '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrSELECT_QUADS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Unselect all Polygons",              '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrUNSELECT_ALL_POLYS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Select/Polygons", "Invert Polygon selection",           '\0', NULL, NULL, FALSE, NULL, _MCB_PolySelection, (EPointer)E3dCrINVERT_POLYGON_SELECTION);

//	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu/Delete", "Hidden Nodes and Objects",    '\0', NULL, NULL, FALSE, NULL, _MCB_DeleteHiddenNodesAndObjects, (EPointer)FALSE);

	E_KeySetCallback("Select all hierarchies", 'A', EShiftMask|LCtrlMask, EKeyDOWN, _KCB_Selection, (EPointer)E3dCrALL_HIERARCHIES);

	E_KeySetCallback("Go up",    EKeyUP,    0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_UP);
	E_KeySetCallback("Go down",  EKeyDOWN,  0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_DOWN);
	E_KeySetCallback("Go left",  EKeyLEFT,  0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_LEFT);
	E_KeySetCallback("Go right", EKeyRIGHT, 0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_RIGHT);

	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;  }

		E_KeyRemoveCallback(EKeyUP,    0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_UP);
		E_KeyRemoveCallback(EKeyDOWN,  0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_DOWN);
		E_KeyRemoveCallback(EKeyLEFT,  0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_LEFT);
		E_KeyRemoveCallback(EKeyRIGHT, 0, EKeyPRESS, _KCB_HrcWalk, (EPointer)E3dCrGO_RIGHT);
	}

//	{ int  LI = 0;for(;LI < _ScriptFunctions_Count;LI++) { E3dScriptFunction_Remove(_ScriptFunctions[LI]); } _ScriptFunctions_Count = 0; }

	return(E_SUCCESS);
}
