//================================================================
// EQUINOX-3D(TM) plugin
//
// - Edit Mesh
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jun-07 20:19:06
//
// Copyright (C) 1996-2026 By Gabor Nagy. All rights reserved.
//================================================================
#include <stdio.h>

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

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

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

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

#include <E3D/Raytracing.h>

#include "Edit.h"
#include "VertexOps.h"

static EPluginLock*  _PolyGroup_DialogLock = NULL;


// PolyGroup dialog-box
//
typedef struct
{
	E3dPolyGroup*  PolyGroup;

	uint32_t       FollowSelection : 1, ApplyChangesToAllSelected : 1;

	XeWidget       DialogW;
	XeWidget       NameW,
	               FollowSelectionW, ApplyChangesToAllSelectedW,
	               FacetedW, UseMeshVertexNormalsW, SmoothingAngleW,
	               CastsShadowsW, ReceivesShadowsBW,
	               DoubleSidedBW, WireframeW, SharpPolygonsW;

	XeDrawer       AdvancedDrawer;
	XeWidget       GetUVLabelW, GetUVFromSTMatrixW,
	               GetUVFromSTWs[E3dMAX_NUM_OF_2DTEXTUREMAPPERS],
	               PolyGroupTangentFromSTWs[E3dMAX_NUM_OF_2DTEXTUREMAPPERS],
	               PolyGroupTangentsFromTextureMatrixW, PolyGroupTangentFromTextureWs[E3dMAX_NUM_OF_2DTEXTURES],
	               PolyGroupNormalsFromTextureMatrixW, PolyGroupNormalFromTextureWs[E3dMAX_NUM_OF_2DTEXTURES],
	               MeshNormalsFromTextureMatrixW, GetMeshNormalsFromTextureWs[E3dMAX_NUM_OF_2DTEXTURES],
	               MeshTangentsFromTextureMatrixW, GetMeshTangentsFromTextureWs[E3dMAX_NUM_OF_2DTEXTURES],
	               MeshTangentsEdgeFromTextureLabelW, MeshTangentsEdgeFromTextureMatrixW, MeshTangentsEdgeFromTextureWs[E3dMAX_NUM_OF_2DTEXTURES],
	               TangentFromShapeW,
	               RemoveTriangleStripsW,
	               StatsLabelW, StatsValuesLabelW;

	const int      VAStartIndex;
	XeWidget       VectorDrawScaleW, OverlayOpacityW;
	XeWidget       VertexAttributesMatrixW;
	EList_s(, E3dVertexAttributeGUI, _PolyGroup_VertexAttributesGUI);

	XeWidget       RotateTangentsW, TangentFromTextureEdgesRadiusW, TangentFromTextureEdgesKernelSizeW;
} E3dPolyGroupPanel;

static E3dPolyGroupPanel  _PolyGroupPanel = { .PolyGroup= NULL, .FollowSelection= TRUE, .ApplyChangesToAllSelected= TRUE };

static E3dCoordinate  _PolyGroup_TangentFromTextureEdges_Radius = 0.5;
static EIndex         _PolyGroup_TangentFromTextureEdges_KernelSize = 31;

EListI_s(static, E3dVertexAttributeGUI, _PolyGroup_VertexAttributesGUI);



EBool                 _PolyGroupUI_SceneCallbackAdded = FALSE;


static E3dCoordinate  _PolygonPackingSpacing = 0.01;

static E3dLock  _PolyGroupMeshLock=
{
	"Edit PolyGroup",  // Name
	NULL               // LockFunc
};


static void  _PolyGroupUpdateStatsLabel(const E3dPolyGroup* PPolyGroup);


// Inertia tensor
//
typedef struct
{
	E3dCoordinate  XX, YY, ZZ, XY, YZ, XZ;
} E3dInertia;


#define _SubExpressions(w0, w1, w2,  f1, f2, f3,  g0, g1, g2) do\
{\
	E3dCoordinate  temp0 = w0 + w1;\
	f1 = temp0 + w2;\
	E3dCoordinate  temp1 = w0 * w0;\
	E3dCoordinate  temp2 = temp1 + w1 * temp0;\
	f2 = temp2 + w2 * f1;\
	f3 = w0 * temp1 + w1 * temp2 + w2 * f2;\
	g0 = f2 + w0 * (f1 + w0);g1 = f2 + w1 * (f1 + w1); g2 = f2 + w2 * (f1 + w2);\
} while(0)



#define _ProcessTriangle() do\
{\
	const E3dCoordinate  LX0 = LVertex0->Position.X, LY0 = LVertex0->Position.Y, LZ0 = LVertex0->Position.Z;\
	const E3dCoordinate  LX1 = LVertex1->Position.X, LY1 = LVertex1->Position.Y, LZ1 = LVertex1->Position.Z;\
	const E3dCoordinate  LX2 = LVertex2->Position.X, LY2 = LVertex2->Position.Y, LZ2 = LVertex2->Position.Z;\
\
/* Get edges and cross product of edges	*/\
	const E3dCoordinate  a1 = LX1 - LX0,  b1 = LY1 - LY0,  c1 = LZ1 - LZ0,  a2 = LX2 - LX0,  b2 = LY2 - LY0, c2 = LZ2 - LZ0;\
	const E3dCoordinate  d0 = b1 * c2 - b2 * c1,  d1 = a2 * c1 - a1 * c2, d2 = a1 * b2 - a2 * b1;\
\
/* Compute integral terms		*/\
	E3dCoordinate  f1x, f2x, f3x,  f1y, f2y, f3y,  f1z, f2z, f3z,   g0x, g1x, g2x,  g0y, g1y, g2y,  g0z, g1z, g2z;\
\
	_SubExpressions(LX0, LX1, LX2,  f1x, f2x, f3x,  g0x, g1x, g2x);\
	_SubExpressions(LY0, LY1, LY2,  f1y, f2y, f3y,  g0y, g1y, g2y);\
	_SubExpressions(LZ0, LZ1, LZ2,  f1z, f2z, f3z,  g0z, g1z, g2z);\
\
/* Update integrals  */\
	LIntg[0] += d0 * f1x;\
	LIntg[1] += d0 * f2x; LIntg[2] += d1 * f2y;\
	LIntg[4] += d0 * f3x; LIntg[5] += d1 * f3y;\
	LIntg[7] += d0 * (LY0 * g0x + LY1 * g1x + LY2 * g2x);\
	LIntg[8] += d1 * (LZ0 * g0y + LZ1 * g1y + LZ2 * g2y);\
	LIntg[9] += d2 * (LX0 * g0z + LX1 * g1z + LX2 * g2z);\
	LIntg[3] += d2 * f2z;\
	LIntg[6] += d2 * f3z;\
} while(0)


static int _E3dMesh_VolumeProperties(const E3dMesh* PMesh, E3dCoordinate* PVolume, E3dCoord3* PCenterOfMass, E3dInertia* PInertia)
{
	const E3dCoordinate  LMult[10] = { 1.0/6.0, 1.0/24.0, 1.0/24.0, 1.0/24.0, 1.0/60.0, 1.0/60.0, 1.0/60.0, 1.0/120.0, 1.0/120.0, 1.0/120.0 };
	E3dCoordinate        LIntg[10] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ,0.0 };  // order: 1, x, y, z, x^2, y^2, z^2, xy, yz, zx

	*PVolume = 0.0;
	E3dPolyGroup* const*  LPolyGroups = PMesh->PolyGroups.Items;
	const E3dVertex*      LVertices = PMesh->Vertices.Items;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;
	EIndex  LPGI = 0;const EIndex  LPGN = PMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
	{
		const E3dPolyGroup*  LPolyGroup = LPolyGroups[LPGI];const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;

		EIndex  LPI = 0;const EIndex  LPN = LPolyGroup->Polygons.Count;const E3dPolygon*  LPolygon = LPolyGroup->Polygons.Items;for(LPI = 0;LPI < LPN;LPI++, LPolygon++)
		{
			const E3dVertexNode*  LVertexNodes = LPolygon->VertexNodes;if(LVertexNodes)
			{
				if(LPolygon->NumOfVertices == 3)
				{
					const E3dVertex*  LVertex0;const E3dVertex*  LVertex1;const E3dVertex*  LVertex2;
					{ const E3dVertexNode*  LVertexNode = LVertexNodes;LVertex0 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID);EStruct_Inc(LVertexNode, LVertexNodeSize); }
					{ const E3dVertexNode*  LVertexNode = LVertexNodes;LVertex1 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID);EStruct_Inc(LVertexNode, LVertexNodeSize); }
					{ const E3dVertexNode*  LVertexNode = LVertexNodes;LVertex2 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID); }
					_ProcessTriangle();
				}
				else if(LPolygon->ITriangles_Count > 0)
				{
					const E3dITriangle*  LITriangle = LPolygon->ITriangles;if(LITriangle)
					{
						EIndex  LTI = 0;const EIndex  LTN = LPolygon->ITriangles_Count;for(;LTI < LTN;LTI++, LITriangle++)
						{
							const E3dVertexNodeIndex*  LVertexNodeIndices = LITriangle->VertexNodes;
							const E3dVertex*           LVertex0;const E3dVertex*  LVertex1;const E3dVertex*  LVertex2;

							{ const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndices[0]);LVertex0 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID); }
							{ const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndices[1]);LVertex1 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID); }
							{ const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndices[2]);LVertex2 = EStruct_ByIndex(LVertices, LMeshVertexSize, LVertexNode->VertexID); }
							_ProcessTriangle();
						}
					}
				}
			}
			else
			{
printf("%s() %s:%d  <%s%s%s> PolyGroup %3lld <<%s>>  Polygon %lld has no VertexNodes\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, PMesh->Name, ECodeNormal,
 (long long)LPGI, LPolyGroup->Name, (long long)LPI);fflush(stdout);
			}
		}
	}

	{ int  LI = 0;for(;LI < 10;LI++) LIntg[LI] *= LMult[LI]; }

	const E3dCoordinate  LMass = LIntg[0];

	if(LMass <= 0.0) return(E_NO_DATA);

	*PVolume = LMass;   // Assuming density = 1.0

	E3dCoord3  LCenterOfMass;
	LCenterOfMass.X = LIntg[1] / LMass;
	LCenterOfMass.Y = LIntg[2] / LMass;
	LCenterOfMass.Z = LIntg[3] / LMass;

// Center of mass
//
	if(PCenterOfMass)
	{
		*PCenterOfMass = LCenterOfMass;
	}

// Inertia tensor relative to center of mass
//
	if(PInertia)
	{
		PInertia->XX = LIntg[5] + LIntg[6] - LMass * (LCenterOfMass.Y * LCenterOfMass.Y + LCenterOfMass.Z * LCenterOfMass.Z);
		PInertia->YY = LIntg[4] + LIntg[6] - LMass * (LCenterOfMass.Z * LCenterOfMass.Z + LCenterOfMass.X * LCenterOfMass.X);
		PInertia->ZZ = LIntg[4] + LIntg[5] - LMass * (LCenterOfMass.X * LCenterOfMass.X + LCenterOfMass.Y * LCenterOfMass.Y);
		PInertia->XY = -(LIntg[7] - LMass * LCenterOfMass.X * LCenterOfMass.Y);
		PInertia->YZ = -(LIntg[8] - LMass * LCenterOfMass.Y * LCenterOfMass.Z);
		PInertia->XZ = -(LIntg[9] - LMass * LCenterOfMass.Z * LCenterOfMass.X);
	}

	return(E_SUCCESS);
}


//========================================
// Update Mesh stats label
//========================================
static void _Mesh_UpdateStatsLabel(const E3dMesh* PMesh, XeWidget PStatsValLW)
{
	E3dCoordinate  LVolume = 0.0;
	const EIndex   LVN = PMesh->Vertices.Count;

	if(LVN)
	{
		if(_E3dMesh_VolumeProperties(PMesh, &LVolume, NULL, NULL) == E_SUCCESS)
		{
//printf("%s() %s:%d Volume = %f\n", __FUNCTION__, __FILE__, __LINE__, LVolume);fflush(stdout);
		}
	}

	E3dPolyGroup**  LPolyGroups = PMesh->PolyGroups.Items;
	char            LTmpStr[256];
	float           LAverageStripLength = 0.0;
	EIndex          LNumOfPolygons, LNumOfTriangles, LNumOfTriangleStrips;

	E3dCoordinate  LSurfaceArea = 0.0;

	EIndex  LMaxVertexNum = 0;

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

		LNumOfPolygons += LPolyGroup->Polygons.Count;
		LNumOfTriangles += E3dPolyGroup_CountTriangles(LPolyGroup, NULL);

		if(LVN > 0) LSurfaceArea += E3dMeshPolyGroup_Area(PMesh, LPolyGroup);

		LNumOfTriangleStrips += LPolyGroup->TriangleStrips.Count;

		{
			EIndex  LI = 0;const EIndex  LNumOfStrips = LPolyGroup->TriangleStrips.Count;E3dTriangleStrip*  LTriangleStrip = LPolyGroup->TriangleStrips.Items;
			for(;LI < LNumOfStrips;LI++, LTriangleStrip++)
			{
				if(LMaxVertexNum < LTriangleStrip->NumOfVertices) LMaxVertexNum = LTriangleStrip->NumOfVertices;
				LAverageStripLength += (float)(LTriangleStrip->NumOfVertices);
			}
		}
	}

	EIndex  LNumOfSelectedVertices = 0;

	{
		EIndex  LVI = 0;E3dVertex*  LVertex = PMesh->Vertices.Items;const E3dVertexSize  LMeshVertexSize = PMesh->VertexSize;for(;LVI < LVN;LVI++, EStruct_Inc(LVertex, LMeshVertexSize))
		{
			if(LVertex->Flags & E3dVtxSELECTED) LNumOfSelectedVertices++;
		}
	}

	if(LNumOfTriangleStrips)
	{
		LAverageStripLength /= (float)LNumOfTriangleStrips;
		sprintf(LTmpStr, "%lld (%lld selected)\n%lld (%lld selected %lld visible)\n%lld\n%lld (%lld selected)\n%lld\n%lld\n%lld vertices\n%.2f vertices\n%.6f\n%.6f",
		       (long long)LVN, (long long)LNumOfSelectedVertices,
		       (long long)PMesh->Edges.Count, (long long)E3dMesh_NumOfSelectedEdges(PMesh), (long long)E3dMesh_NumOfVisibleEdges(PMesh),
		       (long long)LPGN,
		       (long long)LNumOfPolygons, (long long)E3dMesh_NumOfSelectedPolygons(PMesh),
		       (long long)LNumOfTriangles,
		       (long long)LNumOfTriangleStrips, (long long)LMaxVertexNum, LAverageStripLength,
		       LSurfaceArea, LVolume);
	}
	else
	{
		sprintf(LTmpStr, "%lld (%lld selected)\n%lld (%lld selected %lld visible)\n%lld\n%lld (%lld selected)\n%lld\n0\n-\n-\n%.6f\n%.6f",
		        (long long)LVN, (long long)LNumOfSelectedVertices,
		        (long long)PMesh->Edges.Count, (long long)E3dMesh_NumOfSelectedEdges(PMesh), (long long)E3dMesh_NumOfVisibleEdges(PMesh),
		        (long long)LPGN, (long long)LNumOfPolygons, (long long)E3dMesh_NumOfSelectedPolygons(PMesh), (long long)LNumOfTriangles, LSurfaceArea, LVolume);
	}

	XeString  LXeStr = XeString_NewVa(TRUE,  XeFONT, XeFontDEFAULT,  XeALIGNMENT, XeStringAlignRIGHT,  XeTEXT, LTmpStr,  0);if(LXeStr)
	{
		EArg  Args[2];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNLabelString, LXeStr);
		Xe_SetValues(PStatsValLW,  Args, ArgCnt);

		XeString_Free(LXeStr);
	}
}


typedef struct
{
	E3dVertexGeometryGUICore;

	XeWidget       StatsValuesLabelW,
	               WireframeW, ShowAllEdgesW, ShowAllVerticesW, VectorDrawScaleW, OverlayOpacityW,
	               GenerateNormalsW, GenerateNormalsVertexDistanceW,
	               GenerateTangentsW, RotateTangentsW,
	               EdgeHardnessW, UseCustomEdgeColorW;

	XeDrawer       ColorDrawer;
	XeWidget       ColorSwatchW;
	EcColorPanel*  ColorPanel;

	XeDrawer       AdvancedDrawer;
} E3dMeshResourcesGUI;


//========================================
// Mesh dialog callback handler
//========================================
static void _DCB_Mesh(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*        LScene = E3d_Scene;
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dMesh*         LMesh = (E3dMesh*)LPanel->Object;

	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dMesh_class);if(LDrawer)
	{
		E3dMeshResourcesGUI*  LGUI = (E3dMeshResourcesGUI*)LDrawer->ResourcesGUI;

		if(PW == LGUI->WireframeW)
		{
			const E3dDisplayFlags  LDisplayFlags = ((XeToggleButtonCallbackStruct*)PCallData)->Set ? E3dDM_Wireframe : 0;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
				{
					EIndex  LI = 0;for(;LI < LN;LI++)
					{
						E3dMesh*  LTMesh = (E3dMesh*)LSelectedObjects.Items[LI];LTMesh->DisplayFlags = LDisplayFlags;
					}
					EList_FreeA_s(LSelectedObjects);
				}
				else LMesh->DisplayFlags = LDisplayFlags;
			}
			else LMesh->DisplayFlags = LDisplayFlags;

			EBool  LMeshVisible = FALSE;

			if(LMesh->Visible)
			{
				EIndex  LI = 0;const EIndex  LN = LMesh->Models.Count;for(;LI < LN;LI++)
				{
					if(LMesh->Models.Items[LI]->Visible) { LMeshVisible = TRUE;break; }
				}
			}

//		E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
			if(LMeshVisible) E3dWindows_RedrawByChangeFlags(LScene, E3dChgHIDE_UNHIDE|E3dChgMeshSHAPE);
			else E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
		}
		else if(PW == LGUI->UseCustomEdgeColorW)
		{
			LMesh->UseCustomEdgeColor = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
		}
		else if(PW == LGUI->ShowAllVerticesW)
		{
			const EBool  LV = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
				{
					EIndex  LI = 0;for(;LI < LN;LI++)
					{
						E3dMesh*  LTMesh = (E3dMesh*)LSelectedObjects.Items[LI];LTMesh->ShowAllVertices = LV;
					}
					EList_FreeA_s(LSelectedObjects);
				}
				else LMesh->ShowAllVertices = LV;
			}
			else LMesh->ShowAllVertices = LV;

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
		}
		else if(PW == LGUI->ShowAllEdgesW)
		{
			const EBool  LV = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
				{
					EIndex  LI = 0;for(;LI < LN;LI++)
					{
						E3dMesh*  LTMesh = (E3dMesh*)LSelectedObjects.Items[LI];LTMesh->ShowAllEdges = LV;
					}
					EList_FreeA_s(LSelectedObjects);
				}
				else LMesh->ShowAllEdges = LV;
			}
			else LMesh->ShowAllEdges = LV;

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
		}
		else if(PW == LGUI->GenerateNormalsW)
		{
			E3dMesh_GenerateVertexNormals(LMesh);

			E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
			E3dVertexAttributesGUI_Update(&LMesh->VertexAttributes, E3dVertexGeometry_DefaultVertexAttributes.Count, LGUI->VertexAttributesMatrixW, &LGUI->VertexAttributesGUI,
			 _DCB_VertexAttribute, LPanel);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
		}
		else if(PW == LGUI->GenerateTangentsW)
		{
			if(E3dMesh_GenerateVertexTangents(LMesh) == E_SUCCESS)
			{
				E3dVertexAttributesGUI_Update(&LMesh->VertexAttributes, E3dVertexGeometry_DefaultVertexAttributes.Count, LGUI->VertexAttributesMatrixW, &LGUI->VertexAttributesGUI,
				 _DCB_VertexAttribute, LPanel);
			}

			E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
		}
		else if(PW == LGUI->GenerateNormalsVertexDistanceW)
		{
			LMesh->VertexSamplingRadius = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
			E3dMesh_GenerateVertexNormals(LMesh);

			E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_NORMAL);
		}
		else if(PW == LGUI->EdgeHardnessW)
		{
			const float  LEdgeHardness = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

			EIndex  LI = 0;const EIndex  LN = LMesh->Edges.Count;E3dEdge*  LEdge = LMesh->Edges.Items;for(;LI < LN;LI++, LEdge++)
			{
				if(LEdge->Flags & E3dEdgeSELECTED) LEdge->Hardness = LEdgeHardness;
			}

			E3dMesh_EdgeFlagsUpdate(LMesh);

			EChangeFlags  LChanged = E3dObject_CallOutputs((E3dBaseObject*)LMesh, E3dChgMeshSHAPE);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME|LChanged);
		}
		else if(PW == LGUI->VectorDrawScaleW)
		{
			EChangeFlags  LChanged = 0;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
				{
					EIndex  LI = 0;for(;LI < LN;LI++)
					{
						E3dMesh*  LTMesh = (E3dMesh*)LSelectedObjects.Items[LI];
						LTMesh->VectorDrawScale = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

						{
							E3dVertexGeometryGPUData*  LGPUData = LTMesh->VertexGeometryGPUData;if(LGPUData)
							{
printf("%s() %s:%d  %s%s%s  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LTMesh->Name, LTMesh->VectorDrawScale);fflush(stdout);
								E3dVertexAttributeVisualization_Free(&LGPUData->VertexAttributeVisualization, FALSE);
							}
						}

						LChanged |= E3dChgSELECTION;
					}

					EList_FreeA_s(LSelectedObjects);
				}
			}
			else
			{
				LMesh->VectorDrawScale = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

				{
					E3dVertexGeometryGPUData*  LGPUData = LMesh->VertexGeometryGPUData;if(LGPUData)
					{
						E3dVertexAttributeVisualization_Free(&LGPUData->VertexAttributeVisualization, FALSE);
					}
				}

				LChanged |= E3dChgSELECTION;
			}

			if(LChanged)
			{
				E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			}
		}
		else if(PW == LGUI->OverlayOpacityW)
		{
			EChangeFlags  LChanged = 0;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
				{
					EIndex  LI = 0;for(;LI < LN;LI++)
					{
						E3dMesh*  LTMesh = (E3dMesh*)LSelectedObjects.Items[LI];
						LTMesh->OverlayOpacity = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

						{
							E3dVertexGeometryGPUData*  LGPUData = LTMesh->VertexGeometryGPUData;if(LGPUData)
							{
printf("%s() %s:%d  %s%s%s  %f\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LTMesh->Name, LTMesh->VectorDrawScale);fflush(stdout);
								E3dVertexAttributeVisualization_Free(&LGPUData->VertexAttributeVisualization, FALSE);
							}
						}

						LChanged |= E3dChgSELECTION;
					}

					EList_FreeA_s(LSelectedObjects);
				}
			}
			else
			{
				LMesh->OverlayOpacity = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;

				{
					E3dVertexGeometryGPUData*  LGPUData = LMesh->VertexGeometryGPUData;if(LGPUData)
					{
						E3dVertexAttributeVisualization_Free(&LGPUData->VertexAttributeVisualization, FALSE);
					}
				}

				LChanged |= E3dChgSELECTION;
			}

			if(LChanged)
			{
				E3dWindows_RedrawByChangeFlags(LScene, LChanged);
			}
		}
		else if(PW == LGUI->RotateTangentsW)
		{
			const float  LV = ((XeScaleCallbackStruct*)PCallData)->Value.f32;if(LV != 0.0)
			{
				_E3dMesh_RotateTangents(LMesh, LV);

				E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
				E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
				XeScale_SetValue_f32(LGUI->RotateTangentsW, 0.0, FALSE);
			}
		}
	}
}


//================================================================
// Return hardness value of the first selected Edge of a Mesh
//================================================================
static float _MeshGetEdgeHardness(E3dMesh* PMesh)
{
	EIndex  LI = 0;const EIndex  LN = PMesh->Edges.Count;const E3dEdge*  LEdge = PMesh->Edges.Items;for(;LI < LN;LI++, LEdge++)
	{
		if(LEdge->Flags & E3dEdgeSELECTED) return(LEdge->Hardness);
	}
	return(-1.0);  // No selected Edges found
}


//========================================
// Color ColorPanel callback
//========================================
static void _CB_ColorChanged(EcColorPanel* PColorPanel, EPointer PClientData, EPointer PCallData)
{
	EcColorPanelCallbackStruct*  LCPCBR = (EcColorPanelCallbackStruct*)PCallData;
	int                          LColorIndex;

	if(LCPCBR->NumOfColors > 0) LColorIndex = LCPCBR->ColorIDs[0];
	else return;

	E3dObjectPanel*  LObjectPanel = (E3dObjectPanel*)PClientData;
	E3dObject*       LObject = LObjectPanel->Object;E3dMesh*  LMesh = E_DynamicCast(LObject, E3dMesh);if(LMesh)
	{
		XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LObjectPanel, E3dMesh_class);if(LDrawer)
		{
			E3dMeshResourcesGUI*  LGUI = (E3dMeshResourcesGUI*)LDrawer->ResourcesGUI;
			E3dScene*             LScene = E3d_Scene;

			const EBool  LColorWasInactive = LMesh->EdgeRGBAfColor.A < 0.0 ? TRUE : FALSE;

			LMesh->EdgeRGBAfColor = PColorPanel->RGBAfColors[LColorIndex];

// If the user changed the Spline's color for the first time (it was "inactive" before), set it to "active"
//
			if(LColorWasInactive)
			{
//printf("%s() %s:%d %sACTIVATE%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
				LMesh->EdgeRGBAfColor.A = 1.0;
				EcColorPanel_SetRGBAfColor(LGUI->ColorPanel, 0, &LMesh->EdgeRGBAfColor);
			}

//printf("%s() %s:%d  <%s>  RGBAfColor % 7.2f % 7.2f % 7.2f % 7.2f\n", __FUNCTION__, __FILE__, __LINE__, LSpline->Name, RGBA_s(LMesh->EdgeRGBAfColor));fflush(stdout);

			EcRGBAiColor  LColor;Ec_RGBAf32ToRGBAi(&LMesh->EdgeRGBAfColor, &LColor);LColor.A = 0xFFFF;  // LMesh->EdgeRGBAfColor may have a 0 alpha, which would affect the drawing of this Widget
			Xe_SetValuesVa(LGUI->ColorSwatchW,  XeNBackgroundColor, &LColor,  NULL);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
		}
	}
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupVertexAttribute(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dPolyGroupPanel*  LPanel = (E3dPolyGroupPanel*)PClientData;
	E3dPolyGroup*       LPolyGroup = LPanel->PolyGroup;

	const int  LResult = E3dVertexAttributeGUI_Callback(PW, PClientData, PCallData, _DCB_PolyGroupVertexAttribute,  (EObject*)LPolyGroup,
	 &LPolyGroup->VertexAttributes, E3dPolyGroup_DefaultVertexAttributes_Count,  _PolyGroupPanel.VertexAttributesMatrixW, &_PolyGroup_VertexAttributesGUI,
	  &LPolyGroup->GPUData.VertexAttributeVisualization);
	(void)LResult;
//	if(LResult == E_SELECTED || LResult == E_UNSELECTED || LResult == E_REMOVED) _VertexAttribute_PaintOnOff((E3dObjectPanel*)PClientData);
}


//================================================================
// GUIFunc for E3dMesh
//================================================================
void _Mesh_GUI(ECoreObject* PObject, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dMeshResourcesGUI*     LGUI;
	E3dMesh*                 LMesh = (E3dMesh*)PObject;

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

	switch(PAction)
	{
		case E_UPDATE:
			if(LDrawer->ResourcesGUI)
			{
				LGUI = (E3dMeshResourcesGUI*)LDrawer->ResourcesGUI;
				_Mesh_UpdateStatsLabel(LMesh, LGUI->StatsValuesLabelW);
				XeToggleButton_SetState(LGUI->WireframeW, LMesh->DisplayFlags == E3dDM_Wireframe, FALSE);
				XeToggleButton_SetState(LGUI->ShowAllVerticesW, LMesh->ShowAllVertices, FALSE);
				XeToggleButton_SetState(LGUI->ShowAllEdgesW, LMesh->ShowAllEdges, FALSE);

				XeScale_SetValue_Coord(LGUI->VectorDrawScaleW, LMesh->VectorDrawScale, FALSE);
				XeScale_SetValue_Coord(LGUI->OverlayOpacityW, LMesh->OverlayOpacity, FALSE);

				XeScale_SetValue_Coord(LGUI->GenerateNormalsVertexDistanceW, LMesh->VertexSamplingRadius, FALSE);

				{
					const float  LEdgeHardness = _MeshGetEdgeHardness(LMesh);
					XeWidget_SetSensitive(LGUI->EdgeHardnessW, LEdgeHardness >= 0.0);  // Composite Widgets need this called (rather than just SetValues)
			    XeScale_SetValue_f32(LGUI->EdgeHardnessW,  LEdgeHardness,  FALSE);
			  }

				EcRGBAiColor  LColor;Ec_RGBAf32ToRGBAi(&LMesh->EdgeRGBAfColor, &LColor);LColor.A = 0xFFFF;  // LMesh->EdgeRGBAfColor may have a 0 alpha, which would affect the drawing of this Widget
				Xe_SetValuesVa(LGUI->ColorSwatchW,  XeNBackgroundColor, &LColor,  NULL);

				EcColorPanel_SetRGBAfColor(LGUI->ColorPanel, LGUI->ColorPanel->DrawColor0, &LMesh->EdgeRGBAfColor);
				XeToggleButton_SetState(LGUI->UseCustomEdgeColorW, LMesh->UseCustomEdgeColor, FALSE);

				_VertexAttributesGUI((E3dVertexGeometryGUI*)LGUI, NULL, (E3dVertexGeometry*)PObject, FALSE, PPanel, PAction);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;

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

				const XeCoordinate  LSpacingLeft = XeDrawer_LeftIndent, LSpacingRight = 4;

				EArg  Args[32];int  ArgCnt;

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 0);
				Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNSet, LMesh->DisplayFlags == E3dDM_Wireframe);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Always draw this Mesh in wireframe mode, to make editing Objects inside of it easier.");
//				LGUI->WireframeW = LW = XeWidget_ManagedNew("Wireframe", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				LGUI->WireframeW = LW = XeWidget_ManagedNew("Wireframe", XeToggleButton_class, LDrawer->Drawer.ButtonMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginBottom, 4);
					EXeSetArg(XeNSpacingX, 12);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
//				EXeSetArg(XeNMarginRight, 8);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					XeWidget_ManagedNew("Show", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LMesh->ShowAllVertices);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Show all Vertices of the new Mesh, including hidden ones.");
					LGUI->ShowAllVerticesW = LW = XeWidget_ManagedNew("All Vertices", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LMesh->ShowAllEdges);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Show all Edges of the new Mesh, including hidden ones.");
					LGUI->ShowAllEdgesW = LW = XeWidget_ManagedNew("All Edges", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);
				}

				XeWidget  LTraverseWs[8];int  LTraverseN = 0;  // For TextField traversal
				XeWidget  LIndentWs[8];int  LIndentN = 0;  // For TextField traversal

// VertexAttributes
//
				{
					EXeStart;
					EXeSetArg(XeNSpacingTop, 4);
					EXeSetArg(XeNShadowThickness, 2);
					EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Scale the lenght of vectors such as normals and tangents with this value.");
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->VectorDrawScaleW = LW = XeScale_New_Coord(LTopMatrixW, "Vector scale",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LMesh->VectorDrawScale,  0.001, 10.0,  0.001, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Opacity of VertexAttribute overlay.");
					LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->OverlayOpacityW = LW = XeScale_New_Coord(LTopMatrixW, "Overlay opacity",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LMesh->OverlayOpacity,  0.0, 1.0,  0.0, 1.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);

					_VertexAttributesGUI((E3dVertexGeometryGUI*)LGUI, LTopMatrixW, (E3dVertexGeometry*)PObject, TRUE, PPanel, PAction);

					XeWidgets_IndentFromLeft(LIndentWs, LIndentN, LSpacingLeft);
				}

// Stats
//
				{
					EXeStart;
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNSpacingX, 8);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					char      LTmpStr[256];sprintf(LTmpStr, "Vertices:\nEdges:\nPolyGroups:\nPolygons:\nTriangles:\nTriangle-strips:\nLongest strip:\nAverage strip:\nSurface area:\nVolume:");
					XeString  LXeStr = XeString_NewVa(TRUE,  XeFONT, XeFontDEFAULT,  XeALIGNMENT, XeStringAlignLEFT,  XeTEXT, LTmpStr,  0);
					if(LXeStr) EXeSetArg(XeNLabelString, LXeStr);

					XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);
					if(LXeStr) XeString_Free(LXeStr);


					EXeStart;
					EXeSetArg(XeNSpacingRight, 6);
					LGUI->StatsValuesLabelW = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					_Mesh_UpdateStatsLabel((E3dMesh*)PObject, LGUI->StatsValuesLabelW);
				}

// Selected Edge info
//
				{
					const float  LEdgeHardness = _MeshGetEdgeHardness(LMesh);
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNSpacingTop, 4);
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNSensitive, LEdgeHardness >= 0.0);
					XeToolTip8("Set 'hardness' value for selected Edges.\nThis is used for subdivision surfaces to keep some\nEdges creased and for bevelling / filleting Edges.");
			    LGUI->EdgeHardnessW = LW = XeScale_New_Coord(LTopMatrixW, "Edge hardness",  Args, ArgCnt);
			    XeScale_SetValueAndRanges_f32(LW,  LEdgeHardness,  0.0, 1.0,  0.0, 1.0,  FALSE);
			    XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);
				}

				EXeStart;
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

// Color Drawer
//
				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, XeDrawer_LeftIndent);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNSpacingTop, 4);EXeSetArg(XeNSpacingBottom, 2);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingX, 4);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("ColorMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					XeDrawer*  LColorDrawer = &LGUI->ColorDrawer;XeDrawer_NewVa(LMatrixW, LColorDrawer, "Edge color", 0, 0,  NULL);

					LGUI->ColorSwatchW = EcColorSwatch_NewRGBAf32(NULL, LMatrixW, NULL, &LMesh->EdgeRGBAfColor, XeDrawerCB_OpenClose, (EPointer)LColorDrawer);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSpacingLeft, 4);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSet, LMesh->UseCustomEdgeColor);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Use the color specified here, for the Edges of this Mesh.\nWhen this is OFF, use the default wireframe color.");
					LGUI->UseCustomEdgeColorW = LW = XeWidget_ManagedNew("Enable", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);

					EcColorPanel*  LColorPanel = EcColorPanel_NewVa(LTopMatrixW, "Color",  XeNColorChangedCallback, _CB_ColorChanged,  XeNColorChangedCallbackClientData, PPanel,
					 XeNAlphaOn, TRUE,
					 XeNSpacingLeft, LSpacingLeft,
					 XeNColorComponentType, EResFLOAT32,
					 XeNRGBAfColor, &LMesh->EdgeRGBAfColor,
					 XeNIntensityOn, TRUE,  XeNCreateColorWheel, TRUE,  NULL);

					LGUI->ColorPanel = LColorPanel;
					LColorDrawer->DrawerW = LColorPanel->TopMatrixW;
				}

// Advanced drawer
//
				{
					XeDrawer*  LLDrawer = &LGUI->AdvancedDrawer;XeWidget  LDrawerW = XeDrawer_NewVa(LTopMatrixW, LLDrawer, "Advanced", 0, XeDrawerCREATE_SEPARATOR|XeDrawerCREATE_TOP_MATRIX,  NULL);

					Xe_SetValuesVa(LDrawerW,  XeNResizableY, FALSE,  XeNSpacingLeft, LSpacingLeft,  NULL);

					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);
					EXeSetArg(XeNOrientation, XeVERTICAL);
					EXeSetArg(XeNSpacingY, 0);
//	EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LDrawerPaneW = XeWidget_New("AdvancedMatrixW", XeMatrixWidget_class, LDrawerW,  Args, ArgCnt);
					LLDrawer->DrawerW = LDrawerPaneW;

					{
						EXeStart;
//EXeSetArg(XeNBackgroundColor, &Xe_CyanRGBAiColor);
						EXeSetArg(XeNSpacingTop, 4);
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNSpacingX, 4);EXeSetArg(XeNSpacingY, -1);
						EXeSetArg(XeNResizableY, FALSE);
						XeWidget  LMatrixW = XeWidget_ManagedNew("NormalGenerationMatrixW", XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNSpacingTop, 2);
						EXeSetArg(XeNMarginSizeX, 4);EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Generate normals at the Mesh Vertices.");
						LGUI->GenerateNormalsW = LW = XeWidget_ManagedNew("Normals", XePushButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _DCB_Mesh, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
						EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
						EXeSetArg(XeNIncrement, 10);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8(
"When generating normals, consider Vertices within this distance as one Vertex during normal\n'smoothing'. This allows for smooth normals even across duplicated ('split') Vertices.");
						LTraverseWs[LTraverseN++] = LGUI->GenerateNormalsVertexDistanceW = LW = XeScale_New_Coord(LMatrixW, "Distance",  Args, ArgCnt);
						XeScale_SetValueAndRanges_Coord(LW,  LMesh->VertexSamplingRadius,  0.0001, 1000.0,  0.0001, 100.0,  FALSE);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB_Mesh, (EPointer)PPanel);
					}

					EXeStart;
					EXeSetArg(XeNSpacingTop, 4);
					EXeSetArg(XeNMarginSizeX, 4);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Generate tangents at the Mesh Vertices, using the normals in an X-axis aligned direction.");
					LGUI->GenerateTangentsW = LW = XeWidget_ManagedNew("Tangents", XePushButton_class, LDrawerPaneW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_Mesh, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNSpacingTop, 2);EXeSetArg(XeNSpacingBottom, 2);
					EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNTextFieldDigits, 7);EXeSetArg(XeNDecimalPoints, 4);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					EXeSetArg(XeNActivateOnLeave, FALSE);
					LGUI->RotateTangentsW = LW = XeScale_ManagedNew_f32(LDrawerPaneW, "Rotate tangents",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  0.0,  -180.0, 180.0,  -180.0, 180.0,  FALSE);
					XeAddCallback(LW, XeNActivateCallback, _DCB_Mesh, (EPointer)PPanel);
				}

				XeTraversal_SetRing(LTraverseWs, LTraverseN);
			}
		break;

		case E_FREE:
			{
				LGUI = (E3dMeshResourcesGUI*)LDrawer->ResourcesGUI;
				EList_Free_s(LGUI->VertexAttributesGUI);

				if(LGUI->ColorPanel) { EcColorPanel_Free(LGUI->ColorPanel);LGUI->ColorPanel = NULL; }
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
			}
		break;
	}
}


//========================================
// Update PolyGroup stats label
//========================================
static void _PolyGroupUpdateStatsLabel(const E3dPolyGroup* PPolyGroup)
{
	char            LAreaStr[256];
	EIndex          LNumOfEdges;
	const EIndex    LNumOfTriangles = E3dPolyGroup_CountTriangles(PPolyGroup, NULL);
	const E3dMesh*  LMesh = (E3dMesh*)PPolyGroup->VertexGeometry;

	if(LMesh->PolyGroups.Count > 1) LNumOfEdges = PPolyGroup->Edges.Count;
	else LNumOfEdges = LMesh->Edges.Count;

	EStr_DoubleToString(E3dMeshPolyGroup_Area(LMesh, PPolyGroup), FALSE, LAreaStr);

	char            LTmpStr[256 + 256];

	if(PPolyGroup->TriangleStrips.Count > 0)
	{ 
		sprintf(LTmpStr, "Edges\nPolygons:\nTriangles:\nTriangle-strips:\nLongest strip:\nAverage strip:\nSurface area:");
		XeString  LXeStr = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT, XeALIGNMENT, XeStringAlignLEFT,
		                                    XeTEXT, LTmpStr,
                                        0);
		EArg  Args[16];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNLabelString, LXeStr);
		Xe_SetValues(_PolyGroupPanel.StatsLabelW,  Args, ArgCnt);
		XeString_Free(LXeStr);

		EIndex         LMaxVertexNum = 0;
		E3dCoordinate  LAverageStripLength = 0.0;

		EIndex  LI = 0;const EIndex  LPN = PPolyGroup->TriangleStrips.Count;const E3dTriangleStrip*  LTriangleStrip = PPolyGroup->TriangleStrips.Items;for(;LI < LPN;LI++, LTriangleStrip++)
		{
			if(LMaxVertexNum < LTriangleStrip->NumOfVertices) LMaxVertexNum = LTriangleStrip->NumOfVertices;
			LAverageStripLength += (E3dCoordinate)LTriangleStrip->NumOfVertices;
		}

		LAverageStripLength /= (float)LPN;

		sprintf(LTmpStr, "%lld\n%lld (%lld selected)\n%6lld (%lld selected)\n%6lld\n%lld vertices\n%.2f vertices\n%s",
		 (long long)LNumOfEdges,
		 (long long)E3dMeshPolyGroup_NumOfSelectedEdges(LMesh, PPolyGroup),
		 (long long)(PPolyGroup->Polygons.Count),
		 (long long)E3dPolyGroup_NumOfSelectedPolygons(PPolyGroup),
		 (long long)LNumOfTriangles,
		 (long long)(PPolyGroup->TriangleStrips.Count),
		 (long long)LMaxVertexNum,
		 LAverageStripLength,
		 LAreaStr);
	}
	else
	{
		sprintf(LTmpStr, "Edges\nPolygons:\nTriangles:\nSurface area:");
		XeString  LXeStr = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT, XeALIGNMENT, XeStringAlignLEFT,
		                                    XeTEXT, LTmpStr,
		                                    0);

		EArg  Args[2];int  ArgCnt;

		EXeStart;
		EXeSetArg(XeNLabelString, LXeStr);
		Xe_SetValues(_PolyGroupPanel.StatsLabelW,  Args, ArgCnt);
		XeString_Free(LXeStr);

		sprintf(LTmpStr, "%lld (%lld selected)\n%lld (%lld selected)\n%lld\n%s",
		 (long long)LNumOfEdges,
		 (long long)E3dMeshPolyGroup_NumOfSelectedEdges(LMesh, PPolyGroup),
		 (long long)(PPolyGroup->Polygons.Count),
		 (long long)E3dPolyGroup_NumOfSelectedPolygons(PPolyGroup),
		 (long long)LNumOfTriangles,
		 LAreaStr);
	}

	{
		XeString  LXeStr = XeString_NewVa(TRUE, XeFONT, XeFontDEFAULT, XeALIGNMENT, XeStringAlignRIGHT,  XeTEXT, LTmpStr,  0);if(LXeStr)
		{
			EArg  Args[2];int  ArgCnt;

			EXeStart;
			EXeSetArg(XeNLabelString, LXeStr);
			Xe_SetValues(_PolyGroupPanel.StatsValuesLabelW,  Args, ArgCnt);

			XeString_Free(LXeStr);
		}
	}
}


//========================================
// Scene changed callback
//========================================
void _EditCB_ScenePolyGroup(ECoreObject* PScn, EPointer PClientData, ECallbackStruct* PCallData)
{
	E3dPolyGroupPanel*  LPanel = &_PolyGroupPanel;

	switch((PCallData)->Reason)
	{
		case E3dCR_GEOMETRY_CHANGED:
			{
				E3dSceneObjectCallbackStruct*  LCBS = (E3dSceneObjectCallbackStruct*)PCallData;

				if(LCBS->SubObject == NULL || LCBS->SubObject == (EObject*)LPanel->PolyGroup)
				{
					_PolyGroupPanel_Update(LPanel->PolyGroup);
				}
			}
		break;

		case E3dCR_SELECTION_CHANGED:
			if(LPanel->FollowSelection)
			{
				E3dItem  LSelectedItem;

				switch(E3dScene_GetSingleSelection(E3d_Scene, &LSelectedItem, E3dItemPOLYGROUP, 0))
				{
					case E3dItemPOLYGROUP:
						{
							E3dPolyItem*  LPolyItem = &LSelectedItem.PolyItem;

							_PolyGroupUI_PopupDialog(LPolyItem->PolyGroup, NULL, FALSE);
						}
					break;
				}
			}
		break;

		case E3dCR_POLYGON_SELECTION:
			if(LPanel->FollowSelection)
			{
				E3dItem  LSelectedItem;

				switch(E3dScene_GetSingleSelection(E3d_Scene, &LSelectedItem, E3dItemPOLYGROUP, 0))
				{
					case E3dItemPOLYGROUP:
						{
							E3dPolyItem*  LPolyItem = &LSelectedItem.PolyItem;

							_PolyGroupUI_PopupDialog(LPolyItem->PolyGroup, NULL, FALSE);
						}
					break;
				}
			}
		break;
	}
}


//================================================================
// Create geometric tangents for a PolyGroup
//================================================================
static void _PolyGroupCreateTangents(E3dPolyGroup* PPolyGroup)
{
	int  LOffset = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_Tangent, 0);if(LOffset < 0)
	{
		E3dPolyGroup_VertexAttributeAdd(PPolyGroup,  NULL, E3dVtxAttribute_Tangent, 0,  NULL);
		LOffset = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_Tangent, 0);
	}

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

			if((PPolyGroup->Flags & E3dUSE_VERTEX_NORMALS) && LNormalOffset >= 0)
			{
				EIndex  LVI = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
				const E3dCoord3*  LVertexNormal = (E3dCoord3*)((uint8_t*)LPolygon->VertexNodes + LNormalOffset);E3dCoord3*  LTangent = (E3dCoord3*)((uint8_t*)LVertexNode + LOffset);
				for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNormal, LVertexNodeSize), EStruct_Inc(LTangent, LVertexNodeSize))
				{
					_E3d_GenerateTangent_pp(LVertexNormal, LTangent);
				}
			}
			else
			{
				EIndex  LVI = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;E3dCoord3*  LTangent = (E3dCoord3*)((uint8_t*)LVertexNode + LOffset);
				for(;LVI < LVN;LVI++, EStruct_Inc(LVertexNode, LVertexNodeSize), EStruct_Inc(LTangent, LVertexNodeSize))
				{
					_E3d_GenerateTangent_vp(LPolygon->Normal, LTangent);
				}
			}
		}
	}
}


//================================================================
// Create geometric tangents from the way texture N is mapped
//================================================================
static EBool _PolyGroupCreateTangentsFromTextureMapping(E3dPolyGroup* PPolyGroup, const int PSTIndex)
{
	const int  LSTxOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, PSTIndex);if(LSTxOffsetInVtxNode >= 0)
	{
//printf("%s() %s:%d  %sGREEN%s  PSTIndex %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, PSTIndex);fflush(stdout);
		E3dPolyGroup_VertexAttributeAdd(PPolyGroup,  NULL, E3dVtxAttribute_Tangent, 0,  NULL);

		{
			const int                LTangentOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_Tangent, 0);
			const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;

			EIndex  LPI = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;for(;LPI < LPN;LPI++, LPolygon++)
			{
				E3dVertexNode*  LVertexNode = LPolygon->VertexNodes;
				const E3dST*    LST = (E3dST*)EOffsetToPtr(LVertexNode, LSTxOffsetInVtxNode);

				E3dCoord3*      LTangent = (E3dCoord3*)EOffsetToPtr(LVertexNode, LTangentOffsetInVtxNode);
				EIndex  LVI = 0;const EIndex  LVN = LPolygon->NumOfVertexNodes;for(LVI = 0;LVI < LVN;LVI++)
				{
					*LTangent = LST->Tangent;
					EStruct_Inc(LST, LVertexNodeSize);
					EStruct_Inc(LTangent, LVertexNodeSize);
				}
			}
		}
		return(TRUE);
	}
	else return(FALSE);
}



static void _PolyGroupDialogClose(void)
{
	XeWidget_Unmap(_PolyGroupPanel.DialogW);

	if(_PolyGroupUI_SceneCallbackAdded)
	{
		E3dScene_CallbackRemove(E3d_Scene, _EditCB_ScenePolyGroup, (EPointer)0);
		_PolyGroupUI_SceneCallbackAdded = FALSE;
	}

	if(_PolyGroupPanel.PolyGroup)
	{
		E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;
		E3dObject_LockDel((E3dObject*)LPolyGroup->VertexGeometry, &_PolyGroupMeshLock);

		E_DeleteChk(_PolyGroupPanel.PolyGroup);
	}
}


static int _PolyGroupDialogLockDel(EPlugin* PPlugin, EPluginLock* PLock)
{
	_PolyGroupDialogClose();

	return(E_SUCCESS);
}


//========================================================================================
// Return TRUE, if PPolyGroup's attributes are being edited (the dialog box is displayed)
//========================================================================================
EBool _PolyGroup_IsEdited(E3dPolyGroup* PPolyGroup)
{
	if(_PolyGroupPanel.PolyGroup == PPolyGroup) return(TRUE);
	return(FALSE);
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupClose(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeCallbackStruct*  LCBS = (XeCallbackStruct*)PCallData;
	if(LCBS->Reason == XeCR_PROTOCOLS)
	{
		_PolyGroupDialogClose();
		E3dWindows_RedrawByChangeFlags(E3d_Scene, E3dChgLOCK_UNLOCK);
	}
}


static inline void _PolyGroup_SmoothingAngle(E3dPolyGroup* PPolyGroup, const float PAngle, const EBool PUpdateUI)
{
	PPolyGroup->SmoothingAngle = PAngle;

// Turn on vertex normals automatically if the user changes the SmoothingAngle
//
	E3dPolyGroup_VertexAttributeAdd(PPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
	PPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
	PPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
	E3dMeshPolyGroup_RefreshNormals(E_DynamicCast(PPolyGroup->VertexGeometry, E3dMesh), PPolyGroup, PPolyGroup->VertexNormalType, PPolyGroup->SmoothingAngle);

	if(PUpdateUI) _PolyGroupUI_PopupDialog(PPolyGroup, NULL, FALSE);
}


static inline EIndex _PolyGroup_SharpPolygons(E3dPolyGroup* PPolyGroup, const EBool PSet)
{
	const EIndex  LNumOfSelectedPolygons = E3dPolyGroup_NumOfSelectedPolygons(PPolyGroup);

	if(LNumOfSelectedPolygons)
	{
		EIndex  LPI = 0;const EIndex  LPN = PPolyGroup->Polygons.Count;E3dPolygon*  LPolygon = PPolyGroup->Polygons.Items;
		if(PSet) for(;LPI < LPN;LPI++, LPolygon++) LPolygon->Flags |= E3dPolygonSHARP;
		else for(;LPI < LPN;LPI++, LPolygon++) LPolygon->Flags &= ~E3dPolygonSHARP;

		return(1);
	}
	return(0);
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroup(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dPolyGroupPanel*  LPanel = (E3dPolyGroupPanel*)PClientData;
	E3dScene*           LScene = E3d_Scene;

	if(PW == LPanel->NameW)
	{
		char*  LText = XeTextField_GetString(LPanel->NameW);

		EObject_SetName(LPanel->PolyGroup, LText, FALSE);
	}
	else if(PW == LPanel->FollowSelectionW)
	{
		LPanel->FollowSelection = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	}
	else if(PW == LPanel->ApplyChangesToAllSelectedW)
	{
		LPanel->ApplyChangesToAllSelected = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
	}
	else if(PW == LPanel->FacetedW)
	{
		EChangeFlags  LChanged = 0;
		const EBool   LSet = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

//printf("%s() %s:%d  %sGREEN%s  _PolyGroupApplyChangesToAllSelected %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LPanel->ApplyChangesToAllSelected);fflush(stdout);

		E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;

		{
			E3dMesh*  LMesh = E_DynamicCast(LPolyGroup->VertexGeometry, E3dMesh);if(LMesh)
			{
//printf("%s() %s:%d  %sGREEN%s  LSet %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LSet);fflush(stdout);
				XeToggleButton_SetState(LPanel->UseMeshVertexNormalsW, FALSE, FALSE);

				if(LSet)
				{
					if(LPolyGroup->Flags & E3dUSE_VERTEX_NORMALS)
					{
//printf("%s() %s:%d  %sFaceted%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
						LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
						LChanged |= E3dChgVERTEX_NORMAL;
					}
					else if(LPolyGroup->Flags & E3dUSE_MESH_VERTEX_NORMALS)
					{
//printf("%s() %s:%d  %sFaceted%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
						LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
						LPolyGroup->Flags &= ~E3dUSE_MESH_VERTEX_NORMALS;
						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
						LChanged |= E3dChgVERTEX_NORMAL;
					}
				}
				else
				{
					LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;

					E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);  // In case the PolyGroup had no vertex normals before
					LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;

					E3dMeshPolyGroup_RefreshNormals(LMesh, LPanel->PolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
					LChanged |= E3dChgVERTEX_NORMAL;

//printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
					_PolyGroupUI_PopupDialog(LPolyGroup, NULL, FALSE);
				}
			}
		}

		if(LPanel->ApplyChangesToAllSelected)
		{
			EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
			{
				EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
				{
					E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
					EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LTPolyGroup = LMesh->PolyGroups.Items[LPGI];if(LTPolyGroup != LPolyGroup)
						{
							if(LMesh->Selection != E3dObjectSelPOLYGROUP || LTPolyGroup->Selected)
							{
								LTPolyGroup->Flags &= ~E3dUSE_MESH_VERTEX_NORMALS;
								if(LSet)
								{
									if(LTPolyGroup->Flags & E3dUSE_VERTEX_NORMALS)
									{
										LTPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
										E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
										LChanged |= E3dChgVERTEX_NORMAL;
									}
									else if(LTPolyGroup->Flags & E3dUSE_MESH_VERTEX_NORMALS)
									{
										LTPolyGroup->Flags &= ~E3dUSE_MESH_VERTEX_NORMALS;
										E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
										LChanged |= E3dChgVERTEX_NORMAL;
									}
								}
								else
								{
									LTPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;

									E3dPolyGroup_VertexAttributeAdd(LTPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);  // In case the PolyGroup had no vertex normals before
									LTPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;

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

									E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
									LChanged |= E3dChgVERTEX_NORMAL;
									if(LI == 0) _PolyGroupUI_PopupDialog(LTPolyGroup, NULL, FALSE);
								}
							}
						}
					}
				}

				EList_FreeA_s(LSelectedObjects);
			}
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else if(PW == LPanel->UseMeshVertexNormalsW)
	{
		EChangeFlags  LChanged = 0;
		const EBool   LSet = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

//printf("%s() %s:%d  %sGREEN%s  _PolyGroupApplyChangesToAllSelected %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LPanel->ApplyChangesToAllSelected);fflush(stdout);

		E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;

		{
			E3dMesh*  LMesh = E_DynamicCast(LPolyGroup->VertexGeometry, E3dMesh);if(LMesh)
			{
printf("%s() %s:%d  %sGREEN%s  LSet %d\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal, LSet);fflush(stdout);

				const EBool  LHaveMeshNormals = LMesh && EResource_Offset(&LMesh->VertexAttributes,  E3dVtxAttribute_Normal, NULL, 0,  NULL) >= 0 ? TRUE : FALSE;if(LHaveMeshNormals && LSet)
				{
					LPolyGroup->Flags |= E3dUSE_MESH_VERTEX_NORMALS;
					LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
					XeToggleButton_SetState(LPanel->FacetedW, FALSE, FALSE);

					E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
					LChanged |= E3dChgVERTEX_NORMAL;
				}
				else
				{
					LPolyGroup->Flags &= ~E3dUSE_MESH_VERTEX_NORMALS;

					if((LPolyGroup->Flags &= E3dUSE_VERTEX_NORMALS) == 0)
					{
						LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;

						E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);  // In case the PolyGroup had no vertex normals before
						LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;

						E3dMeshPolyGroup_RefreshNormals(LMesh, LPanel->PolyGroup, LPolyGroup->VertexNormalType, LPolyGroup->SmoothingAngle);
						E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
						LChanged |= E3dChgVERTEX_NORMAL;

printf("%s() %s:%d  %sGREEN%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout);
						_PolyGroupUI_PopupDialog(LPolyGroup, NULL, FALSE);
					}
				}
				LChanged |= E3dChgVERTEX_NORMAL;
			}
		}

		if(LPanel->ApplyChangesToAllSelected)
		{
			EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
			{
				EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
				{
					E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
					EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LTPolyGroup = LMesh->PolyGroups.Items[LPGI];if(LTPolyGroup != LPolyGroup)
						{
							if(LMesh->Selection != E3dObjectSelPOLYGROUP || LTPolyGroup->Selected)
							{
								LTPolyGroup->Flags &= ~E3dUSE_MESH_VERTEX_NORMALS;

								const EBool  LHaveMeshNormals = LMesh && EResource_Offset(&LMesh->VertexAttributes,  E3dVtxAttribute_Normal, NULL, 0,  NULL) >= 0 ? TRUE : FALSE;if(LHaveMeshNormals && LSet)
								{
									LTPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
									E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
									LChanged |= E3dChgVERTEX_NORMAL;
								}
								else
								{
									LTPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;

									E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateVERTEX_NORMAL, 0,  LScene);
									LChanged |= E3dChgVERTEX_NORMAL;
								}
							}
						}
					}
				}

				EList_FreeA_s(LSelectedObjects);
			}
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else if(PW == LPanel->VectorDrawScaleW)
	{
		{
			const float   LV = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
			EChangeFlags  LChanged = 0;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
				{
					EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
					{
						E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
						EIndex  LPGI = 0;EChangeFlags  LMeshChange = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
						{
							E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
							if(LMesh->Selection != E3dObjectSelPOLYGROUP || LPolyGroup->Selected)
							{
								if(LPolyGroup->VectorDrawScale != LV)
								{
									LPolyGroup->VectorDrawScale = LV;
									E3dVertexAttributeVisualization_Free(&LPolyGroup->GPUData.VertexAttributeVisualization, FALSE);
									LMeshChange |= E3dChgSELECTION;
								}
							}
						}

						LChanged |= LMeshChange;
					}

					EList_FreeA_s(LSelectedObjects);
				}
			}
			else
			{
				E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;

				LPolyGroup->VectorDrawScale = LV;
				E3dVertexAttributeVisualization_Free(&LPolyGroup->GPUData.VertexAttributeVisualization, FALSE);
				LChanged |= E3dChgSELECTION;
			}

			if(LChanged) E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
	else if(PW == LPanel->OverlayOpacityW)
	{
		{
			const float   LV = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
			EChangeFlags  LChanged = 0;

			if(LPanel->ApplyChangesToAllSelected)
			{
				EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
				{
					EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
					{
						E3dMesh*      LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
						EIndex  LPGI = 0;EChangeFlags  LMeshChange = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
						{
							E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
							if(LMesh->Selection != E3dObjectSelPOLYGROUP || LPolyGroup->Selected)
							{
								if(LPolyGroup->OverlayOpacity != LV)
								{
									LPolyGroup->OverlayOpacity = LV;
									E3dVertexAttributeVisualization_Free(&LPolyGroup->GPUData.VertexAttributeVisualization, FALSE);
									LMeshChange |= E3dChgSELECTION;
								}
							}
						}

						if(LMeshChange) LChanged |= LMeshChange;
					}

					EList_FreeA_s(LSelectedObjects);
				}
			}
			else
			{
				E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;

				LPolyGroup->OverlayOpacity = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
				E3dVertexAttributeVisualization_Free(&LPolyGroup->GPUData.VertexAttributeVisualization, FALSE);
				LChanged |= E3dChgSELECTION;
			}

			if(LChanged) E3dWindows_RedrawByChangeFlags(LScene, LChanged);
		}
	}
	else if(PW == LPanel->SmoothingAngleW)
	{
		EChangeFlags  LChanged = 0;
		EListAI(, E3dObject*, LMeshes);
		const float   LAngle = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

		if(LPanel->ApplyChangesToAllSelected)
		{
			EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
			{
				EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
				{
					E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
					EIndex    LMeshChanged = 0;
					EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
						if(LMesh->Selection != E3dObjectSelPOLYGROUP || LPolyGroup->Selected)
						{
							_PolyGroup_SmoothingAngle(LPolyGroup, LAngle, LI == 0);
							LMeshChanged++;
						}
					}

					if(LMeshChanged)
					{
						EList_PointerAddAChk(LMeshes, 16, LMesh);
						LChanged |= E3dChgVERTEX_NORMAL;
					}
				}

				EList_FreeA_s(LSelectedObjects);
			}
		}

		{
			if(LPanel->PolyGroup)
			{
				E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;
				_PolyGroup_SmoothingAngle(LPanel->PolyGroup, LAngle, TRUE);
				EList_PointerAddAChk(LMeshes, 16, LPolyGroup->VertexGeometry);

				LChanged |= E3dChgVERTEX_NORMAL;
			}
		}

		if(LMeshes)
		{
			EIndex  LI = 0;for(;LI < LMeshes_Count;LI++)
			{
				E3dObject_UpdateForDisplay(LMeshes[LI],  E3dUpdateVERTEX_NORMAL, 0,  LScene);
			}
			EFree(LMeshes);
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else if(PW == LPanel->WireframeW)
	{
		EChangeFlags  LChanged = 0;
		const EBool   LSet = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

		if(LPanel->ApplyChangesToAllSelected)
		{
			EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects > 0)
			{
				EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
				{
					E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
					EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
						if(LMesh->Selection != E3dObjectSelPOLYGROUP || LPolyGroup->Selected)
						{
							if(LSet) LPolyGroup->DisplayFlags = E3dDM_Wireframe;
							else LPolyGroup->DisplayFlags = 0;
						}
					}
				}

				EList_FreeA_s(LSelectedObjects);
			}
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else if(PW == LPanel->SharpPolygonsW)
	{
		EChangeFlags  LChanged = 0;
		const EBool   LSet = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		EListAI(, E3dObject*, LMeshes);

		if(LPanel->ApplyChangesToAllSelected)
		{
			EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LNSelectedObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LSelectedObjects);if(LNSelectedObjects)
			{
				EIndex  LI = 0;for(;LI < LNSelectedObjects;LI++)
				{
					E3dMesh*  LMesh = (E3dMesh*)LSelectedObjects.Items[LI];
					EBool     LMeshChanged = FALSE;
					EIndex  LPGI = 0;const EIndex  LPGN = LMesh->PolyGroups.Count;for(;LPGI < LPGN;LPGI++)
					{
						E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[LPGI];
						if(LMesh->Selection != E3dObjectSelPOLYGROUP || LPolyGroup->Selected)
						{
							LMeshChanged += _PolyGroup_SharpPolygons(LPolyGroup, LSet);
						}
					}

					if(LMeshChanged)
					{
						EList_PointerAddAChk(LMeshes, 16, LMesh);
						LChanged |= E3dChgVERTEX_NORMAL;
					}
				}

				EList_FreeA_s(LSelectedObjects);
			}
		}
		else
		{
			if(LPanel->PolyGroup)
			{
				E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;
				if(_PolyGroup_SharpPolygons(LPolyGroup, LSet))
				{
					EList_PointerAddAChk(LMeshes, 16, LPolyGroup->VertexGeometry);

					LChanged |= E3dChgVERTEX_NORMAL;
				}
			}
		}

		if(LMeshes)
		{
			EIndex  LI = 0;for(;LI < LMeshes_Count;LI++)
			{
				E3dObject_UpdateForDisplay(LMeshes[LI],  E3dUpdateMeshSHAPE, 0,  LScene);
			}
			EFree(LMeshes);
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else if(PW == LPanel->CastsShadowsW)
	{
		if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LPanel->PolyGroup->CastsShadows = TRUE;
		else LPanel->PolyGroup->CastsShadows = FALSE;

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}
	else if(PW == LPanel->ReceivesShadowsBW)
	{
		if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LPanel->PolyGroup->ReceivesShadows = TRUE;
		else LPanel->PolyGroup->ReceivesShadows = FALSE;

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);  // E3dChgMeshSHAPE is overkill, but otherwise the ReceivesShadows flags won't be copied over to the RPolyGroups
//		E3dWindows_RedrawByChangeFlags(LScene, E3dChgLIGHT);
	}
	else if(PW == LPanel->DoubleSidedBW)
	{
		if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LPanel->PolyGroup->DoubleSided = TRUE;
		else LPanel->PolyGroup->DoubleSided = FALSE;

		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}
	else if(PW == LPanel->RemoveTriangleStripsW)
	{
		E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;
		E3dPolyGroup_FreeTriangleStrips(LPolyGroup);

		_PolyGroupUpdateStatsLabel(LPolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateREMOVE_STRIPS, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE);
	}
	else if(PW == LPanel->TangentFromShapeW)
	{
		E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;
		_PolyGroupCreateTangents(LPolyGroup);

		_PolyGroupUpdateStatsLabel(LPolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else if(PW == LPanel->TangentFromTextureEdgesRadiusW)
	{
		_PolyGroup_TangentFromTextureEdges_Radius = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
	}
	else if(PW == LPanel->TangentFromTextureEdgesKernelSizeW)
	{
		_PolyGroup_TangentFromTextureEdges_KernelSize = ((XeScaleCallbackStruct*)PCallData)->Value.i32;
	}
	else if(PW == LPanel->RotateTangentsW)
	{
		const float  LV = ((XeScaleCallbackStruct*)PCallData)->Value.f32;if(LV != 0.0)
		{
			E3dPolyGroup*  LPolyGroup = LPanel->PolyGroup;
			_E3dPolyGroup_RotateTangents(LPolyGroup, LV);

			E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
			E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
			XeScale_SetValue_f32(LPanel->RotateTangentsW, 0.0, FALSE);
		}
	}
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetUVFromSTx(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LSTIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;
	if(E3dPolyGroup_STXtoUV(LPolyGroup, LSTIndex) == E_NO_DATA)
	{
		E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup has no texture coordinate set (ST) %d", LSTIndex);
	}
	else
	{
		_PolyGroupUpdateStatsLabel(LPolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateREMOVE_STRIPS, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetNormalsFromTexture(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_E3d_NormalsOrTangentsFromTexture(NULL, LPolyGroup, LIndex,  0.0, 0,  E3dVtxAttribute_Normal))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dVertexGeometry*  LVertexGeometry = LPolyGroup->VertexGeometry;

printf("%s() %s:%d  %sUpdate Mesh dialog box, if it's active%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);

		E3dObject_UpdateForDisplay((E3dObject*)LVertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup's Material has no Texture %d", LIndex);
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetMeshNormalsFromTexture(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_E3d_NormalsOrTangentsFromTexture(LPolyGroup->VertexGeometry, LPolyGroup,  0.0, 0,  LIndex, E3dVtxAttribute_Normal))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup's Material has no Texture %d", LIndex);
}

//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetMeshTangentsFromTexture(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_E3d_NormalsOrTangentsFromTexture(LPolyGroup->VertexGeometry, LPolyGroup,  0.0, 0,  LIndex, E3dVtxAttribute_Tangent))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup's Material has no Texture %d", LIndex);
}

//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetMeshTangentsEdgeFromTexture(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_E3d_NormalsOrTangentsFromTexture(LPolyGroup->VertexGeometry, LPolyGroup, LIndex,  _PolyGroup_TangentFromTextureEdges_Radius, _PolyGroup_TangentFromTextureEdges_KernelSize,
	 E3dVtxAttribute_Tangent))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup's Material has no Texture %d", LIndex);
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetTangentsFromSTs(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_PolyGroupCreateTangentsFromTextureMapping(LPolyGroup, LIndex))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup has no texture coordinate set (ST) %d", LIndex);
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupGetTangentsFromTexture(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;
	const int  LIndex = (EPointerInt)PClientData;

	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;

	if(_E3d_NormalsOrTangentsFromTexture(NULL, LPolyGroup, LIndex,  0.0, 0,  E3dVtxAttribute_Tangent))
	{
		_PolyGroupUpdateStatsLabel(_PolyGroupPanel.PolyGroup);
		E3dObject_UpdateForDisplay((E3dObject*)LPolyGroup->VertexGeometry,  E3dUpdateVERTEX_ATTRIBUTE, 0,  LScene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgVERTEX_ATTRIBUTE);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "This PolyGroup's Material has no Texture %d", LIndex);
}


//========================================
// Update PolyGroup dialog
//========================================
void _PolyGroupPanel_Update(const E3dPolyGroup* PPolyGroup)
{
	E3dPolyGroupPanel*  LPanel = &_PolyGroupPanel;

	XeTextField_SetString(LPanel->NameW, PPolyGroup->Name, FALSE);

	_PolyGroupUpdateStatsLabel(PPolyGroup);

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

	if(PPolyGroup->TriangleStrips.Count > 0) XeWidget_Manage(LPanel->RemoveTriangleStripsW);
	else XeWidget_Unmanage(LPanel->RemoveTriangleStripsW);

	XeToggleButton_SetState(LPanel->FacetedW, ((PPolyGroup->Flags & (E3dUSE_VERTEX_NORMALS | E3dUSE_MESH_VERTEX_NORMALS)) == 0), FALSE);

	E3dMesh*  LMesh = E_DynamicCast(PPolyGroup->VertexGeometry, E3dMesh);const EBool  LHaveMeshNormals = LMesh &&
	 EResource_Offset(&LMesh->VertexAttributes,  E3dVtxAttribute_Normal, NULL, 0,  NULL) >= 0 ? TRUE : FALSE;

	XeWidget_SetSensitive(LPanel->UseMeshVertexNormalsW, LHaveMeshNormals);
	XeToggleButton_SetState(LPanel->UseMeshVertexNormalsW, ((PPolyGroup->Flags & E3dUSE_MESH_VERTEX_NORMALS) != 0) && LHaveMeshNormals, FALSE);

	XeScale_SetValue_f32(LPanel->SmoothingAngleW, PPolyGroup->SmoothingAngle, FALSE);

	XeScale_SetValue_Coord(LPanel->VectorDrawScaleW, PPolyGroup->VectorDrawScale, FALSE);
	XeScale_SetValue_Coord(LPanel->OverlayOpacityW, PPolyGroup->OverlayOpacity, FALSE);

	XeToggleButton_SetState(LPanel->CastsShadowsW, PPolyGroup->CastsShadows, FALSE);
	XeToggleButton_SetState(LPanel->ReceivesShadowsBW, PPolyGroup->ReceivesShadows, FALSE);
	XeToggleButton_SetState(LPanel->DoubleSidedBW, PPolyGroup->DoubleSided, FALSE);

	XeToggleButton_SetState(LPanel->WireframeW, PPolyGroup->DisplayFlags == E3dDM_Wireframe, FALSE);

	{
		EBool  LHasST = FALSE;

		{
			XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTUREMAPPERS];XeChangeManage*  LChild = LChildren;
			EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTUREMAPPERS;LI++)
			{
				const EBool  LHasThisST = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, LI) >= 0;
				LChild->W = LPanel->GetUVFromSTWs[LI];LChild->Manage = LHasThisST;LChild++;
				if(LHasThisST) LHasST = TRUE;
			}
			if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
		}

		{
			XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTUREMAPPERS];XeChangeManage*  LChild = LChildren;
			EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTUREMAPPERS;LI++)
			{
				const EBool  LHasThisST = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, LI) >= 0;
				LChild->W = LPanel->PolyGroupTangentFromSTWs[LI];LChild->Manage = LHasThisST;LChild++;
			}
			if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
		}

		{
			const E3dMaterial*  LMaterial = PPolyGroup->Material ? PPolyGroup->Material : E3dObject_GetDrawingMaterial(NULL, (E3dObject*)PPolyGroup->VertexGeometry, TRUE);

			{
				XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTURES];XeChangeManage*  LChild = LChildren;
				EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
				{
					const EBool  LHasThis = LMaterial->Textures.Count > LI;
					LChild->W = LPanel->PolyGroupTangentFromTextureWs[LI];LChild->Manage = LHasThis;LChild++;
				}
				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}

			{
				XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTURES];XeChangeManage*  LChild = LChildren;
				EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
				{
					const EBool  LHasThis = LMaterial->Textures.Count > LI;
					LChild->W = LPanel->PolyGroupNormalFromTextureWs[LI];LChild->Manage = LHasThis;LChild++;
				}
				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}

			{
				XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTURES];XeChangeManage*  LChild = LChildren;
				EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
				{
					const EBool  LHasThis = LMaterial->Textures.Count > LI;
					LChild->W = LPanel->GetMeshNormalsFromTextureWs[LI];LChild->Manage = LHasThis;LChild++;
				}
				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}

			{
				XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTURES];XeChangeManage*  LChild = LChildren;
				EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
				{
					const EBool  LHasThis = LMaterial->Textures.Count > LI;
					LChild->W = LPanel->GetMeshTangentsFromTextureWs[LI];LChild->Manage = LHasThis;LChild++;
				}
				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}

			{
				XeChangeManage  LChildren[E3dMAX_NUM_OF_2DTEXTURES];XeChangeManage*  LChild = LChildren;
				EIndex  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
				{
					const EBool  LHasThis = LMaterial->Textures.Count > LI;
					LChild->W = LPanel->MeshTangentsEdgeFromTextureWs[LI];LChild->Manage = LHasThis;LChild++;
				}
				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}

			{
				const EBool  LHaveTextures = LMaterial->Textures.Count > 0;

				XeChangeManage  LChildren[16];XeChangeManage*  LChild = LChildren;

				LChild->W = LPanel->PolyGroupTangentsFromTextureMatrixW;LChild->Manage = LHaveTextures;LChild++;
				LChild->W = LPanel->PolyGroupNormalsFromTextureMatrixW;LChild->Manage = LHaveTextures;LChild++;
				LChild->W = LPanel->MeshNormalsFromTextureMatrixW;LChild->Manage = LHaveTextures;LChild++;
				LChild->W = LPanel->MeshTangentsFromTextureMatrixW;LChild->Manage = LHaveTextures;LChild++;
				LChild->W = LPanel->MeshTangentsEdgeFromTextureLabelW;LChild->Manage = LHaveTextures;LChild++;
				LChild->W = LPanel->MeshTangentsEdgeFromTextureMatrixW;LChild->Manage = LHaveTextures;LChild++;

				if(LChild - LChildren > 0) XeWidgets_ChangeManage(LChildren, LChild - LChildren);
			}
		}

		{
			if(LHasST) XeWidget_Manage(LPanel->GetUVFromSTMatrixW);
			else XeWidget_Unmanage(LPanel->GetUVFromSTMatrixW);
		}
	}


	E3dVertexAttributesGUI_Update(&PPolyGroup->VertexAttributes, E3dPolyGroup_DefaultVertexAttributes_Count, _PolyGroupPanel.VertexAttributesMatrixW, &_PolyGroup_VertexAttributesGUI,
	 _DCB_PolyGroupVertexAttribute, &_PolyGroupPanel);
}


//========================================
// Lay out current texture
//========================================
static void _DCB_PolyGroupTextureLayout(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dPolyGroup*  LPolyGroup = _PolyGroupPanel.PolyGroup;
	E3dMesh*       LMesh = E_DynamicCast(LPolyGroup->VertexGeometry, E3dMesh);
	E3dScene*      LScene = E3d_Scene;

	const char*  LVASemantic = NULL;int  LVAIndex = 0;
	switch((EPointerInt)PClientData)
	{
		case 0:   LVASemantic = E3dVtxAttribute_ST;LVAIndex = 0;break;
		case 1:   LVASemantic = E3dVtxAttribute_ST;LVAIndex = 1;break;
		case 16:  LVASemantic = E3dVtxAttribute_UV;LVAIndex = 0;break;
	}

	const E3dCoordinate  LSpacing = _PolygonPackingSpacing;
	if(E3dMeshPolyGroup_TextureLayout(LMesh, LPolyGroup,  LVASemantic, LVAIndex,  LSpacing, LSpacing,  LSpacing, LSpacing,  NULL) == E_SUCCESS)
	{
		E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateTextureMAPPING, 0,  E3d_Scene);
//	E3dObject_UpdateForDisplay((E3dObject*)LMesh,  E3dUpdateALL, E3dCallALL,  E3d_Scene);
		E3dWindows_RedrawByChangeFlags(LScene, E3dChgTextureMAPPING);
		_PolyGroupPanel_Update(LPolyGroup);
	}
}


//========================================
// PolyGroup dialog callback handler
//========================================
static void _DCB_PolyGroupPackingSpacing(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	_PolygonPackingSpacing = ((XeScaleCallbackStruct*)PCallData)->Value.m_Coordinate;
}


static void _SampleTextureUI_New(const EIndex PTxN, const char* PTip, XeWidget PParentW,  XeCallbackFunc PCallback,  XeWidget* PWs)
{
	EArg  Args[32];int  ArgCnt;

	const XeCoordinate  LButtonMarginLeft = 0, LButtonMarginRight = 2;
	int  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTURES;LI++)
	{
		XeWidget  LW;
		EXeStart;
		EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
		EXeSetArg(XeNResizableY, FALSE);
		XeToolTip8(PTip);
		char  LName[32];sprintf(LName, "Tx%d", LI + 1);
		if(PTxN > LI) PWs[LI] = LW = XeWidget_ManagedNew(LName, XePushButton_class, PParentW,  Args, ArgCnt);
		else PWs[LI] = LW = XeWidget_New(LName, XePushButton_class, PParentW,  Args, ArgCnt);
		XeAddCallback(LW, XeNActivateCallback, PCallback, (EPointer)(EPointerInt)LI);
	}
}


//========================================
// Edit information on a PolyGroup
//========================================
void _PolyGroupUI_PopupDialog(const E3dPolyGroup* PPolyGroup, const E3dObjectComponentClass* POpenFolderForThisComponentClass, const EBool PRaiseDialogBox)
{
	E3dPolyGroupPanel*  LPanel = &_PolyGroupPanel;

// Is the PolyGroup-dialog already up?
//
	if(LPanel->PolyGroup)
	{
		if(LPanel->PolyGroup != PPolyGroup)
		{
			E3dObject_LockDel((E3dObject*)LPanel->PolyGroup->VertexGeometry, &_PolyGroupMeshLock);

			E3dObject_LockAdd((E3dObject*)PPolyGroup->VertexGeometry, &_PolyGroupMeshLock);

			E_Assign(PPolyGroup, LPanel->PolyGroup);
		}

		_PolyGroupPanel_Update(PPolyGroup);
		if(PRaiseDialogBox) XeShell_Raise(LPanel->DialogW);
		return;
	}

	E_Assign(PPolyGroup, LPanel->PolyGroup);

	if(_PolyGroup_DialogLock == NULL) _PolyGroup_DialogLock = EPluginLock_Add(_EditInfoPlugin, "EditPolyGroupInfo", _PolyGroupDialogLockDel);

	if(LPanel->DialogW == NULL)
	{
		XeWidget  LW;
		EArg      Args[32];int  ArgCnt;
		char      LTmpStr[256];
		XeWidget  LIndentWs[8];int  LIndentN;      // For indenting the ScaleWidgets
		XeWidget  LTraverseWs[8];int  LTraverseN;  // For TextField traversal

		const XeCoordinate  LButtonMarginLeft = 0, LButtonMarginRight = 2;

		E3dObject_LockAdd((E3dObject*)PPolyGroup->VertexGeometry, &_PolyGroupMeshLock);

		EXeStart;
		EXeSetArg(XeNAllowShellResize, TRUE);
		EXeSetArg(XeNButtonsStretch, FALSE);
		EXeSetArg(XeNWorkAreaType, XeMATRIX);
		EXeSetArg(XeNWMDecorations, XeWMDecorCLOSE|XeWMDecorBORDER|XeWMDecorRESIZEH|XeWMDecorTITLE);
		EXeSetArg(XeNTitle, "Edit PolyGroup parameters");EXeSetArg(XeNIconName, "PolyGroup");
		XeWidget  LDialogW = XeDialog_New(XeDIALOG_TEMPLATE, "PolyGroup", E3dp_TopShellW,  Args, ArgCnt);if(LDialogW == NULL) return;

		LPanel->DialogW = LDialogW;

		XeAddCallback(LDialogW, XeNWMCallback, _DCB_PolyGroupClose, (EPointer)0);

		XeWidget  LTopMatrixW = XeDialog_GetChild(LDialogW, XeDIALOG_WORK_AREA);

		EXeStart;
		EXeSetArg(XeNOrientation, XeVERTICAL);
		EXeSetArg(XeNMarginSizeX, 2);EXeSetArg(XeNMarginSizeY, 4);
		EXeSetArg(XeNSpacingY, 0);
		EXeSetArg(XeNResizableY, FALSE);
		Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

		const int  LLeftMargin = 6, LRightMargin = 6;

		{
			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingX, 2);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget_ManagedNew("Name", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNStringUTF8, PPolyGroup->Name);
			EXeSetArg(XeNResizableY, FALSE);
			LPanel->NameW = LW = XeWidget_ManagedNew("PolyGroupName", XeTextField_class, LMatrixW,  Args, ArgCnt);
			XeOverrideTranslations(LPanel->NameW, XeStd_TextWidgetTransTable);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);
		}

		{
			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingX, 8);
			EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableX, FALSE);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("FollowSelApplyToAllMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, LPanel->FollowSelection);
			EXeSetArg(XeNResizableY, FALSE);
			LPanel->FollowSelectionW = LW = XeWidget_ManagedNew("Follow selection", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNSet, LPanel->ApplyChangesToAllSelected);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Apply changes to all selected PolyGroups.");
			LPanel->ApplyChangesToAllSelectedW = LW = XeWidget_ManagedNew("Apply to all", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);
		}

		EXeStart;
		EXeSetArg(XeNSpacingTop, 2);
		EXeSetArg(XeNShadowThickness, 2);
		EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
		EXeSetArg(XeNResizableY, FALSE);
		XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

		{
			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingX, 8);
			EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableX, FALSE);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("FollowSelApplyToAllMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNSpacingTop, 2);
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNSet, ((PPolyGroup->Flags & (E3dUSE_VERTEX_NORMALS | E3dUSE_MESH_VERTEX_NORMALS)) == 0));
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Use the Polygon normals.");
			LPanel->FacetedW = LW = XeWidget_ManagedNew("Faceted", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			E3dMesh*  LMesh = E_DynamicCast(PPolyGroup->VertexGeometry, E3dMesh);const EBool  LHaveMeshNormals = LMesh && EResource_Offset(&LMesh->VertexAttributes, 
			 E3dVtxAttribute_Normal, NULL, 0,  NULL) >= 0 ? TRUE : FALSE;

			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNSpacingTop, 2);
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNSet, ((PPolyGroup->Flags & (E3dUSE_VERTEX_NORMALS | E3dUSE_MESH_VERTEX_NORMALS)) == E3dUSE_MESH_VERTEX_NORMALS));
			EXeSetArg(XeNSensitive, LHaveMeshNormals);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Use the normals from the Mesh Vertices.");
			LPanel->UseMeshVertexNormalsW = LW = XeWidget_ManagedNew("Mesh normals", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);
		}

		LTraverseN = 0;
		LIndentN = 0;
		EXeStart;
		EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
		EXeSetArg(XeNIncrement, 10);
		EXeSetArg(XeNResizableY, FALSE);
		LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LPanel->SmoothingAngleW = LW = XeScale_ManagedNew_f32(LTopMatrixW, "Smoothing angle",  Args, ArgCnt);
		XeScale_SetValueAndRange_f32(LW,  PPolyGroup->SmoothingAngle,  0.0, 180.0,  FALSE);
		XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

		XeWidgets_IndentFromLeft(LIndentWs, LIndentN, LLeftMargin);

		{
			EXeStart;
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNMarginSizeY, 5);
			EXeSetArg(XeNSpacingX, 12);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("PGShMatrix", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, PPolyGroup->CastsShadows);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			LPanel->CastsShadowsW = LW = XeWidget_ManagedNew("Casts shadows", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, PPolyGroup->ReceivesShadows);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			LPanel->ReceivesShadowsBW = LW = XeWidget_ManagedNew("Receives shadows", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);
		}


		{
			EXeStart;
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingLeft, LLeftMargin);EXeSetArg(XeNSpacingRight, LRightMargin);
			EXeSetArg(XeNSpacingX, 12);
			EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, PPolyGroup->DoubleSided);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			LPanel->DoubleSidedBW = LW = XeWidget_ManagedNew("Double-sided", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, PPolyGroup->DisplayFlags == E3dDM_Wireframe);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Always draw this PolyGroup in wireframe mode, to make editing Objects inside of it easier.");
			LPanel->WireframeW = LW = XeWidget_ManagedNew("Wireframe", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNShadowThickness, 0);
			EXeSetArg(XeNHighlightThickness, 0);
			EXeSetArg(XeNMarginSizeX, 0);
			EXeSetArg(XeNSet, PPolyGroup->DisplayFlags == E3dDM_Wireframe);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Mark selected Polygons as \"sharp\".\nSubdivision surfaces will only \"smooth\" the Edges of such Polygons.");
			LPanel->SharpPolygonsW = LW = XeWidget_ManagedNew("Sharp Polys", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);
		}


		EXeStart;
		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
		EXeSetArg(XeNResizableY, FALSE);
		LPanel->RemoveTriangleStripsW = LW = XeWidget_New("Remove triangle-strips", XePushButton_class, LTopMatrixW,  Args, ArgCnt);
		XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroup, (EPointer)LPanel);
		if(PPolyGroup->TriangleStrips.Count > 0) XeWidget_Manage(LPanel->RemoveTriangleStripsW);

// Stats
//
		{
			EXeStart;
			EXeSetArg(XeNSpacingTop, 6);
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 5);
			EXeSetArg(XeNSpacingX, 16);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("PGInfoMatrix",  XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			sprintf(LTmpStr, "Edges\nPolygons:\nTriangles:\nTriangle-strips:\nLongest strip:\nAverage strip:\nSurface area:");
			XeString  LXeStr = XeString_NewVa(TRUE,  XeFONT, XeFontDEFAULT,  XeALIGNMENT, XeStringAlignLEFT,  XeTEXT, LTmpStr,  0);if(LXeStr)
			{
				EXeSetArg(XeNLabelString, LXeStr);
			}

			LPanel->StatsLabelW = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);
			if(LXeStr) XeString_Free(LXeStr);

			EXeStart;
			LPanel->StatsValuesLabelW = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);
		}

		{
/*
			EXeStart;
			EXeSetArg(XeNMarginSizeY, 0);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8(
"Lay out Polygons of this PolyGroup in UV coordinate space, based on their surface area.\n\
This ensures a uniform pixel density on the surface of the Polygons, which is necessary\nfor rendering optimal light maps and other types of pre-computed textures.\n\
Select UV mapping for the light map texture.");
			LW = XeWidget_ManagedNew("Lay out UVs", XePushButton_class, LMatrixW,  Args, ArgCnt);
			XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupTextureLayout, (EPointer)0);
*/
		}


// VertexAttributes
//
		{
			LIndentN = 0;
			EXeStart;
			EXeSetArg(XeNSpacingTop, 4);
			EXeSetArg(XeNShadowThickness, 2);
			EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget_ManagedNew("", XeSeparatorWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
			EXeSetArg(XeNIncrement, 10);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Scale the lenght of vectors like normals and tangents with this value.");
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LPanel->VectorDrawScaleW = LW = XeScale_New_Coord(LTopMatrixW, "Vector scale",  Args, ArgCnt);
			XeScale_SetValueAndRanges_Coord(LW,  PPolyGroup->VectorDrawScale,  0.001, 1000.0,  0.001, 100.0,  FALSE);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNTextFieldDigits, 6);EXeSetArg(XeNDecimalPoints, 3);
			EXeSetArg(XeNIncrement, 10);
			EXeSetArg(XeNResizableY, FALSE);
			XeToolTip8("Opacity of VertexAttribute overlay.");
			LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LPanel->OverlayOpacityW = LW = XeScale_New_Coord(LTopMatrixW, "Overlay opacity",  Args, ArgCnt);
			XeScale_SetValueAndRanges_Coord(LW,  PPolyGroup->OverlayOpacity,  0.0, 1.0,  0.0, 1.0,  FALSE);
			XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroup, (EPointer)LPanel);

			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget_ManagedNew("Vertex attributes", XeLabelWidget_class, LTopMatrixW,  Args, ArgCnt);

			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNOrientation, XeHORIZONTAL);
			EXeSetArg(XeNMarginBottom, 4);
			EXeSetArg(XeNColumns, 4);
			EXeSetArg(XeNRadioBehavior, TRUE);EXeSetArg(XeNRadioAlwaysOne, FALSE);
			EXeSetArg(XeNSpacingX, 6);EXeSetArg(XeNSpacingY, 0);
			EXeSetArg(XeNSpacingRight, 6);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LMatrixW = XeWidget_ManagedNew("VertexAttributesMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);
			LPanel->VertexAttributesMatrixW = LMatrixW;

			XeWidgets_IndentFromLeft(LIndentWs, LIndentN, LLeftMargin);

			E3dVertexAttributesGUI_Update(&PPolyGroup->VertexAttributes, E3dPolyGroup_DefaultVertexAttributes_Count, LPanel->VertexAttributesMatrixW, &_PolyGroup_VertexAttributesGUI,
			 _DCB_PolyGroupVertexAttribute, &_PolyGroupPanel);
		}


// Advanced drawer
//
		{
			XeDrawer*  LDrawer = &LPanel->AdvancedDrawer;XeWidget  LDrawerW = XeDrawer_NewVa(LTopMatrixW, LDrawer, "Advanced", 0, XeDrawerCREATE_SEPARATOR|XeDrawerCREATE_TOP_MATRIX,  NULL);

			Xe_SetValuesVa(LDrawerW, XeNResizableY, FALSE, NULL);

			EXeStart;
			EXeSetArg(XeNOrientation, XeVERTICAL);
			EXeSetArg(XeNSpacingY, 0);
//		EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget  LDrawerPaneW = XeWidget_New("AdvancedMatrixW", XeMatrixWidget_class, LDrawerW,  Args, ArgCnt);
			LDrawer->DrawerW = LDrawerPaneW;

// Auto-layout STs or UVs
//
			{
				EXeStart;
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingLeft, LLeftMargin);
				EXeSetArg(XeNSpacingTop, 8);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNSpacingX, 0);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW = XeWidget_ManagedNew("PGPackingMatrix",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);

				EXeStart;
				XeWidget_ManagedNew("Lay out", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 4);
				EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8(
"Lay out Polygons of this PolyGroup in ST0 coordinate space, based on their surface area.\n\
This ensures a uniform pixel density on the surface of the Polygons, which is necessary\nfor rendering optimal light maps and other types of pre-computed textures.\n\
Select 'Freehand' mapping for the light map texture.\nIf there is no ST0 set on the PolyGroup, it will be added.");
				LW = XeWidget_ManagedNew("ST1", XePushButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupTextureLayout, (EPointer)0);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 4);
				EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8(
"Lay out Polygons of this PolyGroup in ST1 coordinate space, based on their surface area.\n\
This ensures a uniform pixel density on the surface of the Polygons, which is necessary\n\
for rendering optimal light maps and other types of pre-computed textures.\n\
Select 'Freehand' mapping for the light map texture.\nIf there is no ST1 set on the PolyGroup, it will be added.");
				LW = XeWidget_ManagedNew("ST2", XePushButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupTextureLayout, (EPointer)1);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, 4);
				EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8(
"Lay out Polygons of this PolyGroup in UV coordinate space, based on their surface area.\n\
This ensures a uniform pixel density on the surface of the Polygons, which is necessary\n\
for rendering optimal light maps and other types of pre-computed textures.\nSelect UV mapping for the light map texture.\nIf there is no UV set on the PolyGroup, it will be added.");
				LW = XeWidget_ManagedNew("UV", XePushButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupTextureLayout, (EPointer)16);

				EXeStart;
				EXeSetArg(XeNSpacingTop, 4);
				EXeSetArg(XeNSpacingLeft, 6);
				EXeSetArg(XeNTextFieldDigits, 7);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				LTraverseWs[LTraverseN++] = LW = XeScale_ManagedNew_f32(LDrawerPaneW, "Spacing",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  _PolygonPackingSpacing,  0.0001, 0.02,  0.001, 0.02,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB_PolyGroupPackingSpacing, (EPointer)0);
			}

// Get tangents
//
			EXeStart;
			EXeSetArg(XeNSpacingLeft, LLeftMargin);
			EXeSetArg(XeNSpacingTop, 12);
			EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
			EXeSetArg(XeNResizableY, FALSE);
			XeWidget_ManagedNew("Tangents", XeLabelWidget_class, LDrawerPaneW,  Args, ArgCnt);

			{
				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin + Xe_LeftIndent);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 4);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW = XeWidget_ManagedNew("TangentsFromSTsMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
				LW = XeWidget_ManagedNew("From", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

// Get tangents from the Mesh shape
//
				EXeStart;
				EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeToolTip8("Generate vertex tangents from the Mesh shape.");
				LPanel->TangentFromShapeW = LW = XeWidget_ManagedNew("Shape", XePushButton_class, LMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroup, (EPointer)LPanel);

// Get tangents from texture-space tangents
//
				{
					char  LName[32];
					int  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTUREMAPPERS;LI++)
					{
						EXeStart;
						EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Get vertex tangents from the texture-space tangents of this texcoord set.");
						sprintf(LName, "ST%d", LI + 1);
						if(E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, LI) >= 0)
						 LPanel->PolyGroupTangentFromSTWs[LI] = LW = XeWidget_ManagedNew(LName, XePushButton_class, LMatrixW,  Args, ArgCnt);
						else LPanel->PolyGroupTangentFromSTWs[LI] = LW = XeWidget_New(LName, XePushButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupGetTangentsFromSTs, (EPointer)(EPointerInt)LI);
					}
				}
			}

			E3dMaterial*  LMaterial = PPolyGroup->Material ? PPolyGroup->Material : E3dObject_GetDrawingMaterial(NULL, (E3dObject*)PPolyGroup->VertexGeometry, TRUE);
			const EBool   LHaveTextures = LMaterial->Textures.Count > 0;

// Sample tangents from a texture
//
			{
				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin + Xe_LeftIndent);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 6);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHaveTextures) LMatrixW = XeWidget_ManagedNew("PolyGroupTangentsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("PolyGroupTangentsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->PolyGroupTangentsFromTextureMatrixW = LMatrixW;

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("From", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				_SampleTextureUI_New(LMaterial ? LMaterial->Textures.Count : 0, "Get vertex tangents from sampling this texture of the Material.", LMatrixW,
				 _DCB_PolyGroupGetTangentsFromTexture, LPanel->PolyGroupTangentFromTextureWs);
			}

			EXeStart;
			EXeSetArg(XeNSpacingTop, 2);
			EXeSetArg(XeNSpacingLeft, LLeftMargin + Xe_LeftIndent);
			EXeSetArg(XeNTextFieldDigits, 7);EXeSetArg(XeNDecimalPoints, 4);
			EXeSetArg(XeNIncrement, 10);
			EXeSetArg(XeNResizableY, FALSE);
			EXeSetArg(XeNActivateOnLeave, FALSE);
			LPanel->RotateTangentsW = LTraverseWs[LTraverseN++] = LW = XeScale_ManagedNew_f32(LDrawerPaneW, "Rotate",  Args, ArgCnt);
			XeScale_SetValueAndRanges_Coord(LW,  0.0,  -180.0, 180.0,  -180.0, 180.0,  FALSE);
			XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroup, (EPointer)LPanel);

// Sample normal from a texture
//
			{
				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 6);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHaveTextures) LMatrixW = XeWidget_ManagedNew("PolyGroupNormalsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("PolyGroupNormalsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->PolyGroupNormalsFromTextureMatrixW = LMatrixW;

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
				LW = XeWidget_ManagedNew("Normals from", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				_SampleTextureUI_New(LMaterial ? LMaterial->Textures.Count : 0, "Get vertex normals from sampling this texture of the Material.", LMatrixW,
				 _DCB_PolyGroupGetNormalsFromTexture, LPanel->PolyGroupNormalFromTextureWs);
			}

// Sample Mesh-Vertex normal from a texture
//
			{
				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 6);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHaveTextures) LMatrixW = XeWidget_ManagedNew("MeshNormalsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("MeshNormalsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->MeshNormalsFromTextureMatrixW = LMatrixW;

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("Mesh normals from", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				_SampleTextureUI_New(LMaterial ? LMaterial->Textures.Count : 0,
				 "Get vertex normals from sampling this texture of the Material and store\nthem on the Mesh Vertices (vs. on the Polygon VertexNodes).",
				  LMatrixW, _DCB_PolyGroupGetMeshNormalsFromTexture, LPanel->GetMeshNormalsFromTextureWs);
			}

// Sample Mesh-Vertex tangent from a Texture
//
			{
				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 6);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHaveTextures) LMatrixW = XeWidget_ManagedNew("MeshTangentsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("MeshTangentsFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->MeshTangentsFromTextureMatrixW = LMatrixW;

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
				LW = XeWidget_ManagedNew("Mesh tangents from", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				_SampleTextureUI_New(LMaterial ? LMaterial->Textures.Count : 0,
				 "Get vertex tangents from sampling this texture of the Material and store\nthem on the Mesh Vertices (vs. on the Polygon VertexNodes).",
				  LMatrixW, _DCB_PolyGroupGetMeshTangentsFromTexture, LPanel->GetMeshTangentsFromTextureWs);
			}

// Sample Mesh-Vertex tangent from a texture with a kernel
//
			{
				LIndentN = 0;

				EXeStart;
				EXeSetArg(XeNSpacingTop, 4);
				EXeSetArg(XeNShadowThickness, 2);
				EXeSetArg(XeNShadowType, XeShadowETCHED_IN);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget_ManagedNew("", XeSeparatorWidget_class, LDrawerPaneW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin);
				EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
				if(LHaveTextures) LW = XeWidget_ManagedNew("Mesh tangents from Texture 'edges'", XeLabelWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LW = XeWidget_New("Mesh tangents from Texture 'edges'", XeLabelWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->MeshTangentsEdgeFromTextureLabelW = LW;

				EXeStart;
				EXeSetArg(XeNSpacingLeft, LLeftMargin);EXeSetArg(XeNSpacingRight, LRightMargin);
				EXeSetArg(XeNSpacingTop, 6);
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHaveTextures) LMatrixW = XeWidget_ManagedNew("MeshTangentsEdgeFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("MeshTangentsEdgeFromTextureMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->MeshTangentsEdgeFromTextureMatrixW = LMatrixW;

				_SampleTextureUI_New(LMaterial ? LMaterial->Textures.Count : 0,
"Compute tangents by sampling this texture of the Material using an edge-detection kernel around\n\
eacth Vertex. Then store the tangents on the Mesh Vertices (vs. on the Polygon VertexNodes).",
				  LMatrixW, _DCB_PolyGroupGetMeshTangentsEdgeFromTexture, LPanel->MeshTangentsEdgeFromTextureWs);

				EXeStart;
//				EXeSetArg(XeNSpacingTop, 2);EXeSetArg(XeNSpacingBottom, 2);
//				EXeSetArg(XeNSpacingRight, LSpacingRight);
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				EXeSetArg(XeNIncrement, 10);
				EXeSetArg(XeNResizableY, FALSE);
				EXeSetArg(XeNActivateOnLeave, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LPanel->TangentFromTextureEdgesRadiusW = LW = XeScale_ManagedNew_f32(LDrawerPaneW, "Radius",  Args, ArgCnt);
				XeScale_SetValueAndRanges_Coord(LW,  _PolyGroup_TangentFromTextureEdges_Radius,  0.01, 100.0,  0.01, 10.0,  FALSE);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroup, (EPointer)LPanel);


				EXeStart;
				EXeSetArg(XeNTextFieldDigits, 5);
				EXeSetArg(XeNMinimum, 1);EXeSetArg(XeNMaximum, 255);
				EXeSetArg(XeNScaleMaximum, 127);
				EXeSetArg(XeNIncrement, 2);
				EXeSetArg(XeNAlignTo, 2);EXeSetArg(XeNAlignOffset, 1);
				EXeSetArg(XeNValue, _PolyGroup_TangentFromTextureEdges_KernelSize);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LPanel->TangentFromTextureEdgesKernelSizeW = LW = XeWidget_ManagedNew("Kernel", XeScaleWidget_class, LDrawerPaneW,  Args, ArgCnt);
				XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroup, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, LLeftMargin);
			}


// Get UVs from STs
//
			{
				const EBool  LHasST = E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, 0) >= 0;

				EXeStart;
				EXeSetArg(XeNOrientation, XeHORIZONTAL);
				EXeSetArg(XeNSpacingLeft, LLeftMargin);
				EXeSetArg(XeNMarginTop, 12);
				EXeSetArg(XeNMarginSizeX, 0);
				EXeSetArg(XeNMarginSizeY, 0);
				EXeSetArg(XeNSpacingX, 4);
				EXeSetArg(XeNSelfResizePolicyX, XeGROW);EXeSetArg(XeNSelfResizePolicyY, XeGROW);
				EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
				EXeSetArg(XeNResizableY, FALSE);
				XeWidget  LMatrixW;
				if(LHasST) LMatrixW = XeWidget_ManagedNew("GrabUVMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				else LMatrixW = XeWidget_New("GrabUVMatrixW",  XeMatrixWidget_class, LDrawerPaneW,  Args, ArgCnt);
				LPanel->GetUVFromSTMatrixW = LMatrixW;

				EXeStart;
				EXeSetArg(XeNResizableX, FALSE);
				LPanel->GetUVLabelW = XeWidget_ManagedNew("Get UVs from", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

				{
					int  LI = 0;for(;LI < E3dMAX_NUM_OF_2DTEXTUREMAPPERS;LI++)
					{
						EXeStart;
						EXeSetArg(XeNMarginLeft, LButtonMarginLeft);EXeSetArg(XeNMarginRight, LButtonMarginRight);EXeSetArg(XeNMarginSizeY, 0);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						EXeSetArg(XeNResizableY, FALSE);
						char  LName[32];sprintf(LName, "ST%d", LI + 1);
						if(E3dPolyGroup_VertexAttributeOffset(PPolyGroup, E3dVtxAttribute_ST, LI) >= 0)
						 LPanel->GetUVFromSTWs[LI] = LW = XeWidget_ManagedNew(LName, XePushButton_class, LMatrixW,  Args, ArgCnt);
						else LPanel->GetUVFromSTWs[LI] = LW = XeWidget_New(LName, XePushButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNActivateCallback, _DCB_PolyGroupGetUVFromSTx, (EPointer)(EPointerInt)LI);
					}
				}
			}
		}

		XeTraversal_SetRing(LTraverseWs, LTraverseN);

		_PolyGroupUpdateStatsLabel(PPolyGroup);

		XeShell_Raise(LPanel->DialogW);
		XeShell_CenterToWidget(LPanel->DialogW, E3dp_TopShellW);
	}
	else
	{
		if(XeShell_DisplayStatus(LPanel->DialogW) != XeDISPLAYED)
		{
			E3dObject_LockAdd((E3dObject*)PPolyGroup->VertexGeometry, &_PolyGroupMeshLock);
			_PolyGroupPanel_Update(PPolyGroup);
			XeShell_Raise(LPanel->DialogW);
		}

		E3dVertexAttributesGUI_Update(&PPolyGroup->VertexAttributes, E3dPolyGroup_DefaultVertexAttributes_Count, LPanel->VertexAttributesMatrixW, &_PolyGroup_VertexAttributesGUI,
		 _DCB_PolyGroupVertexAttribute, &_PolyGroupPanel);
	}

	if(!_PolyGroupUI_SceneCallbackAdded)
	{
		E3dScene_CallbackAdd(E3d_Scene, E3dChgSELECTION|E3dChgMeshSHAPE, _EditCB_ScenePolyGroup, (EPointer)0);_PolyGroupUI_SceneCallbackAdded = TRUE;
	}
}


E3dPolyGroup* _PolyGroupUI_GetEdited(void)
{
	E3dPolyGroupPanel*  LPanel = &_PolyGroupPanel;
	return(LPanel->PolyGroup);
}
