//================================================================
// Interactive Skin tool
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Mar-12 19:14:50
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <math.h>
#include <stdio.h>

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

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

#include <E3D/Matrix.h>

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


static EPlugin*  _Plugin = NULL;


typedef struct
{
	XeWidget  TrianglesW, SmoothW, WeldClosedCrossSectionsW,
	          SplineFollowingProportionalW, SplineFollowingStrictW,
	          SidesW;
} E3dResourcesGUI;


static XeDWidget  _MenuButton = NULL, _ToolBarButton = NULL;

EpIconDef3I(static, Skin);


/*
static XeWidget  _SplineFollowingOMW = NULL;

static XeOptionMenuRec  _SplineFollowing[]=
{
	{ "Proportional",  CR_PROPORTIONAL,  NULL,  (EPointer)CR_PROPORTIONAL,  NULL },
	{ "Follow keys",   CR_EDGES_AT_CVs,  NULL,  (EPointer)CR_EDGES_AT_CVs,  NULL },
	{ "Strict",        CR_STRICT,        NULL,  (EPointer)CR_STRICT,        NULL }
};
*/


enum
{
	CR_SET_TRIANGLES = XeCR_CUSTOM0,
	CR_SET_SMOOTH,
	CR_SET_WELD_CLOSED,
	CR_SET_SIDES,
	CR_SET_CLOSE,
	CR_SORT_NONE,
	CR_SORT_BY_X,
	CR_SORT_BY_Y,
	CR_SORT_BY_Z,
	CR_PROPORTIONAL,    // Ignore where the Spline CVs are
	CR_EDGES_AT_CVs,    // Follow Spline segments (create 'corners' at CVs)
	CR_STRICT,          // Follow Spline segments and Spline tesselation
	CR_SIMPLE,
	CR_HIERARCHY,
	CR_ADD_REMOVE_CROSS_SECTIONS
};


typedef struct
{
	E3dObjectComponentCore;

// Resources
//
	int              Topology;                 // CR_SIMPLE or CR_HIERARCHY
	int              SplineFollowing;          // CR_PROPORTIONAL, CR_EDGES_AT_CVs, CR_STRICT
	int              Sides;
	EIndex           NumOfCrossSections;
	EBool            Close,
	                 WeldClosedCrossSections,  // Weld first and last Vertex on Closed cross-sections
	                 Triangles, Smooth;

// Internal variables
//
	EInterface       InputInterface;                // This is added to the cross-section Geometries as an output node

	E3dSpline**      CrossSections;
	E3dModel**       CrossSectionModels;  // Models that instantiate the cross-sections (this allows for instancing and easily transforming cross-sections)

	E3dCoordinate**  HullParameters;      // NumOfCrossSections*Sides: parameters on the cross-section Splines where each longitudinal edge intersects that cross-section
	int              SortCrossSections;   // Automatically sort cross-sections by X, Y or Z
} E3dInfo;



#define ROffset(field) (void*)(EPtrOffset(E3dInfo, field))
#define ROffsetI(field) (EPtrOffset(E3dInfo, field))
static EResource  _Resources[]=
{
	{ "CrossSections",       EResOBJECT|EResDYNAMIC,  ROffset(CrossSections),       .Ext.CountOffset= ROffsetI(NumOfCrossSections) },
	{ "CrossSectionModels",  EResOBJECT|EResDYNAMIC,  ROffset(CrossSectionModels),  .Ext.CountOffset= ROffsetI(NumOfCrossSections) },

	{ "SplineFollowing",     EResINT32,    ROffset(SplineFollowing) },
	{ "Sides",               EResINT32,    ROffset(Sides) },

	{ "WeldClXSectns",       EResBOOLEAN,  ROffset(WeldClosedCrossSections) },
	{ "Tris",                EResBOOLEAN,  ROffset(Triangles) },
};
#undef ROffset
#undef ROffsetI



static E3dObjectComponentClass*  _ObjectComponent_class = NULL;


//========================================
// Set vertex coordinates
//========================================
static void _SetVertices(E3dVertex* PVertices, const E3dVertexSize PVertexSize,  E3dInfo* PInfo, const EIndex PUPoints)
{
	E3dSpline**  LCrossSections = PInfo->CrossSections;
	const int    LNumOfCrossSections = PInfo->NumOfCrossSections;
	E3dVertex*   LVertex = PVertices;

	switch(PInfo->SplineFollowing)
	{
		case CR_PROPORTIONAL:
			{
				E3dCoordinate**  LHullParameters = PInfo->HullParameters;

				int  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
				{
					E3dSpline*      LCrossSection = LCrossSections[LVC];
					E3dCoordinate*  LHullParametersC = LHullParameters[LVC];

					if(PInfo->CrossSectionModels)
					{
						const E3dModel*       LModel = PInfo->CrossSectionModels[LVC];
						const E3dCoordinate*  LMatrix = LModel->LocalToWorldMatrix;

						int  LUC = 0;for(;LUC < PUPoints;LUC++)
						{
							E3dCoord3  L3DPosition;E3dSpline_Get3DPosition(LCrossSection, (double)(LHullParametersC[LUC]),  &L3DPosition, NULL, NULL);
							EMatrix_Transform3x4_v(LMatrix, L3DPosition);
							LVertex->Position = L3DPosition;

//printf("t: %f  v: %f,%f,%f\n", LHullParametersC[LUC], LVertex->X, LVertex->Y, LVertex->Z);fflush(stdout);

							EStruct_Inc(LVertex, PVertexSize);
						}
					}
					else
					{
						int  LUC = 0;for(;LUC < PUPoints;LUC++)
						{
							E3dCoord3  L3DPosition;E3dSpline_Get3DPosition(LCrossSection, (double)(LHullParametersC[LUC]),  &L3DPosition, NULL, NULL);
							LVertex->Position = L3DPosition;

//printf("t: %f  v: %f,%f,%f\n", LHullParametersC[LUC], LVertex->X, LVertex->Y, LVertex->Z);fflush(stdout);

							EStruct_Inc(LVertex, PVertexSize);
						}
					}
				}
			}
		break;

		case CR_STRICT:
			{
//printf("%s() %s:%d  LNumOfCrossSections %d\n", __FUNCTION__, __FILE__, __LINE__, LNumOfCrossSections);fflush(stdout);
				int  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
				{
					E3dSpline*  LCrossSection = LCrossSections[LVC];

					E3dCoordinate*  LLinSegment = LCrossSection->SLinearSegments;
					const int       LDimensions = LCrossSection->Dimensions;


					if(PInfo->CrossSectionModels)
					{
						const E3dModel*       LModel = PInfo->CrossSectionModels[LVC];
						const E3dCoordinate*  LMatrix = LModel->LocalToWorldMatrix;

//printf("%s() %s:%d [%s]/<%s>\n", __FUNCTION__, __FILE__, __LINE__, LModel->Name, LCrossSection->Name);fflush(stdout);
						int  LUC = 0;for(;LUC < PUPoints;LUC++)
						{
							LVertex->Position.X = LLinSegment[0];
							LVertex->Position.Y = LLinSegment[1];
							LVertex->Position.Z = LLinSegment[2];
							LLinSegment += LDimensions;

							EMatrix_Transform3x4_v(LMatrix, LVertex->Position);

//printf("t: %f  v: %f,%f,%f\n", LHullParametersC[LUC], LVertex->X, LVertex->Y, LVertex->Z);fflush(stdout);

							EStruct_Inc(LVertex, PVertexSize);
						}
					}
					else
					{
//printf("%s() %s:%d <%s>\n", __FUNCTION__, __FILE__, __LINE__, LCrossSection->Name);fflush(stdout);
						int  LUC = 0;for(;LUC < PUPoints;LUC++)
						{
							LVertex->Position.X = LLinSegment[0];
							LVertex->Position.Y = LLinSegment[1];
							LVertex->Position.Z = LLinSegment[2];
							LLinSegment += LDimensions;

//printf("t: %f  v: %f,%f,%f\n", LHullParametersC[LUC], LVertex->X, LVertex->Y, LVertex->Z);fflush(stdout);

							EStruct_Inc(LVertex, PVertexSize);
						}
					}
				}
			}
		break;
	}
}


//========================================
// Update cross-section parameter list
//========================================
static void _InitHullParameters(E3dInfo* PInfo, const EBool PReallocate)
{
//	E3dSpline**     LCrossSections = PInfo->CrossSections;
	E3dCoordinate*  LParameters, LParameter, LParamStep;
	const int       LNumOfCrossSections = PInfo->NumOfCrossSections;
	const int       LSides = PInfo->Sides;


	if(PReallocate)
	{
		if(PInfo->HullParameters)
		{
			EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
			{
				EFree(PInfo->HullParameters[LVC]);
			}

			EFree(PInfo->HullParameters);
		}

		ENewN(PInfo->NumOfCrossSections, PInfo->HullParameters);
		if(PInfo->HullParameters)
		{
			EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
			{
//				E3dSpline*  LCrossSection = LCrossSections[LVC];
				ENewN(LSides + 1, PInfo->HullParameters[LVC]);
			}
		}
	}

	switch(PInfo->SplineFollowing)
	{
		case CR_PROPORTIONAL:  // Ignore segment boundaries and CVs
			{
				EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
				{
					LParameters = PInfo->HullParameters[LVC];
					LParameter = 0.0;LParamStep = 1.0/((E3dCoordinate)LSides);

					EIndex  LUC;for(LUC = 0;LUC < LSides;LUC++, LParameter += LParamStep)
					{
						LParameters[LUC] = LParameter;
					}
					LParameters[LSides] = 1.0;
				}
			}
		break;

		case CR_EDGES_AT_CVs:  // Make sure that there are edges at the Spline CVs
		{
				EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
				{
//					E3dSpline*  LCrossSection = LCrossSections[LVC];

					LParameters = PInfo->HullParameters[LVC];
					LParameter = 0.0;LParamStep = 1.0 / ((E3dCoordinate)LSides);

					EIndex  LUC = 0;for(;LUC < LSides;LUC++, LParameter += LParamStep)
					{
						LParameters[LUC] = LParameter;
					}
					LParameters[LSides] = 1.0;
				}
			}
		break;

		case CR_STRICT:  // Strictly follow Spline tesselation (Spline with fewest linear segments will determine # of edges)
		{
				EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
				{
//					E3dSpline*  LCrossSection = LCrossSections[LVC];

					LParameters = PInfo->HullParameters[LVC];
					LParameter = 0.0;LParamStep = 1.0 / ((E3dCoordinate)LSides);

					EIndex  LUC = 0;for(;LUC < LSides;LUC++, LParameter += LParamStep)
					{
						LParameters[LUC] = LParameter;
					}
					LParameters[LSides] = 1.0;
				}
			}
		break;
	}
}


#define _M_Add2Tris(pV0, pV1, pV2, pV3)\
{\
	E3dVertexNode*  lVertexNode;\
	E3dCoord2*      lUV;\
\
	E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, pV0, pV1, pV2);\
	lVertexNode = LPolygon->VertexNodes;\
	lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	lUV->X = LU;         lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU + LUStep;lUV->Y = LV;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU + LUStep;lUV->Y = LV + LVStep;\
	LPolygon++;\
	E3dPolygon_SetAsTriangle(LPolygon, LVertexNodeSize, pV2, pV3, pV0);\
	lVertexNode = LPolygon->VertexNodes;\
	lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	lUV->X = LU + LUStep;lUV->Y = LV+LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU;         lUV->Y = LV+LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU;         lUV->Y = LV;\
	LPolygon++;\
}


#define _M_AddQuad(pV0, pV1, pV2, pV3)\
{\
	E3dVertexNode*  lVertexNode;\
	E3dCoord2*      lUV;\
	E3dITriangle*   lITriangle;\
\
	E3dPolygon_SetVa(LPolygon, LVertexNodeSize, 4, pV0, pV1, pV2, pV3);\
	ENewN(2, LPolygon->ITriangles);lITriangle = LPolygon->ITriangles;\
	LPolygon->ITriangles_Count = 2;\
	lITriangle->VertexNodes[0] = 0;lITriangle->VertexNodes[1] = 1;lITriangle->VertexNodes[2] = 2;lITriangle++;\
	lITriangle->VertexNodes[0] = 2;lITriangle->VertexNodes[1] = 3;lITriangle->VertexNodes[2] = 0;\
	lVertexNode = LPolygon->VertexNodes;\
	lUV = (E3dCoord2*)EOffsetToPtr(lVertexNode, LUVOffsetInVtxNode);\
	lUV->X = LU;lUV->Y = LV+LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU + LUStep;lUV->Y = LV + LVStep;EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU + LUStep;lUV->Y = LV;         EStruct_Inc(lUV, LVertexNodeSize);\
	lUV->X = LU;lUV->Y = LV;\
	LPolygon++;\
}


//========================================
// Dialog box callback handlers
//========================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dObject*               LObject = LPanel->Object;
	E3dInfo*                 LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);
	XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, _ObjectComponent_class);
	E3dResourcesGUI*         LGUI = (E3dResourcesGUI*)LDrawer->ResourcesGUI;
	EChangeFlags             LChanged = 0;

	if(PW == LGUI->SplineFollowingProportionalW)
	{
		LComponent->SplineFollowing = CR_PROPORTIONAL;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SplineFollowingStrictW)
	{
		LComponent->SplineFollowing = CR_STRICT;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SmoothW)
	{
		E3dMesh*       LMesh = (E3dMesh*)LObject;
		E3dPolyGroup*  LPolyGroup = LMesh->PolyGroups.Items[0];

		LComponent->Smooth = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		if(LComponent->Smooth)
		{
			LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
			E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
			E3dMesh_RecomputeNormals(LMesh, FALSE, FALSE);
			LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
		}
		else
		{
			LPolyGroup->VertexNormalType = 0;
			E3dMesh_RecomputeNormals(LMesh, FALSE, FALSE);
			E3dPolyGroup_VertexAttributeDel(LPolyGroup,  E3dVtxAttribute_Normal, 0);
			LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
		}

		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->TrianglesW)
	{
		LComponent->Triangles = ((XeToggleButtonCallbackStruct*)PCallData)->Set;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->WeldClosedCrossSectionsW)
	{
		LComponent->WeldClosedCrossSections = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}
	else if(PW == LGUI->SidesW)
	{
		LComponent->Sides = ((XeScaleCallbackStruct*)PCallData)->Value.i32;
		LChanged |= E3dObject_GetChangeFlagsByClass(LObject);
		LComponent->AttributeChangeCount++;E3dObject_CustomUpdate(LObject, LComponent,  E3dCallALL,  &LChanged);
	}

	E3dScene*  LScene = E3d_Scene;
	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


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

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

				XeToggleButton_SetState(LResourcesGUI->SplineFollowingProportionalW, LComponent->SplineFollowing == CR_PROPORTIONAL, FALSE);
				XeToggleButton_SetState(LResourcesGUI->SplineFollowingStrictW, LComponent->SplineFollowing == CR_STRICT, FALSE);

				XeToggleButton_SetState(LResourcesGUI->TrianglesW, LComponent->Triangles, FALSE);
				XeToggleButton_SetState(LResourcesGUI->SmoothW, LComponent->Smooth, FALSE);
				XeToggleButton_SetState(LResourcesGUI->WeldClosedCrossSectionsW, LComponent->WeldClosedCrossSections, FALSE);

				XeScale_SetValue(LResourcesGUI->SidesW, LComponent->Sides, FALSE);
			}
			else
			{
				XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
				XeWidget  LIndentWs[7];int    LIndentN;      // For indenting the ScaleWidgets
				XeWidget  LTraverseWs[7];int  LTraverseN;    // For TextField traversal
				EArg      Args[256];int  ArgCnt;

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

				{
					EXeStart;
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSpacingX, 7);EXeSetArg(XeNSpacingRight, 8);
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNRadioAlwaysOne, TRUE);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget  LMatrixW = XeWidget_ManagedNew("SplineFollowingMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNMarginTop, 4);
					EXeSetArg(XeNMarginSizeY, 1);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeWidget_ManagedNew("Spline following", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNSet, (EBool)(LComponent->SplineFollowing == CR_PROPORTIONAL));
					XeToolTip8("Create Edges at even distances on each Spline.");
					LResourcesGUI->SplineFollowingProportionalW = LW = XeWidget_ManagedNew("Proportional", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
					EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					EXeSetArg(XeNSelectColorPtr, &Xe_HighlightRGBAiColor);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNSet, (EBool)(LComponent->SplineFollowing == CR_STRICT));
					XeToolTip8("Create Edges at the visible subdivision points of each Spline.\nThe Spline with the fewest points will determine the number of Edges.");
					LResourcesGUI->SplineFollowingStrictW = LW = XeWidget_ManagedNew("Strict", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);
				}

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LComponent->Triangles);
				EXeSetArg(XeNResizableY, FALSE);
				LResourcesGUI->TrianglesW = LW = XeWidget_ManagedNew("Triangles", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LComponent->Smooth);
				EXeSetArg(XeNResizableY, FALSE);
				LResourcesGUI->SmoothW = LW = XeWidget_ManagedNew("Smooth", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSet, LComponent->WeldClosedCrossSections);
				EXeSetArg(XeNResizableY, FALSE);
				LResourcesGUI->WeldClosedCrossSectionsW = LW = XeWidget_ManagedNew("Weld closed cross-section ends", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);


/*
				EXeStart;
				EXeSetArg(XeNBackgroundColorPtr, &Xe_BackgroundRGBAiColor);
				EXeSetArg(XeNButtonColorPtr, &Xe_ButtonRGBAiColor);
				EXeSetArg(XeNForegroundColorPtr, &Xe_ForegroundRGBAiColor);
				EXeSetArg(XeNFontList, XeMenu_FontList);
				EXeSetArg(XeNLabelFontList, Xe_LabelFontList);
				EXeSetArg(XeNMenuPaneBackgroundColor, &XeOptionMenu_RGBAiColor);
				LResourcesGUI->SplineFollowingOMW = XeOptionMenu_CreateAndInitManaged(LTopMatrixW, NULL, _SplineFollowing, sizeof(_SplineFollowing) / sizeof(XeOptionMenuRec), LComponent->SplineFollowing, "Spline following:", _CBSetSplineFollowing,  Args, ArgCnt);
*/


				LTraverseN = 0;
				LIndentN = 0;
				EXeStart;
				EXeSetArg(XeNMarginSizeX, 3);
				EXeSetArg(XeNTextFieldDigits, 3);
				EXeSetArg(XeNMinimum, 3);EXeSetArg(XeNMaximum, 200);
				EXeSetArg(XeNScaleMaximum, 50);
				EXeSetArg(XeNValue, LComponent->Sides);
				EXeSetArg(XeNResizableY, FALSE);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LResourcesGUI->SidesW = LW = XeWidget_ManagedNew("Sides", XeScaleWidget_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)LPanel);

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 4);
			}
		break;
	}
}


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

	LComponent->InputChangeCount++;

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

	return(LChanged);
}


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

	EListAI_s(, E3dNodePtr, LSelectedNodes);const EIndex  LNumOfSelectedNodes = E3dScene_GetSelectedNodes(LScene, E3dNodeSelANY, FALSE,  &LSelectedNodes);if(LNumOfSelectedNodes > 0)
	{
		E3dInfo  LTInfo;
		EBool    LSuccess = FALSE;

		E3dObjectComponent_InitStatic(&LTInfo, _ObjectComponent_class);

		EIndex  LMI = 0;for(;LMI < LNumOfSelectedNodes;LMI++)
		{
			E3dNode*   LNode  = LSelectedNodes.Items[LMI];
			E3dModel*  LModel = E_DynamicCast(LNode, E3dModel);if(LModel)
			{
				EIndex  LObjI = 0;const EIndex  LObjN = LModel->Objects.Count;for(;LObjI < LObjN;LObjI++)
				{
					E3dObject*  LObject = LModel->Objects.Items[LObjI];

					if(EObject_IsSubClass(LObject, E3dSpline))
					{
						EBool  LRet;ELst_ObjectAdd_r(&LTInfo.CrossSections, &LTInfo.NumOfCrossSections, LObject, LRet);if(LRet)
						{
							LTInfo.NumOfCrossSections -= 1;ELst_ObjectAdd(&LTInfo.CrossSectionModels, &LTInfo.NumOfCrossSections, LNode);
						}
					}
				}
			}
		}


		{
			E3dObject*  LObject;
			E3dModel*   LModel = E3dModel_CustomObjectNewInit(LScene, &LTInfo, &LObject, NULL, (E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME) != 0);if(LModel)
			{
				{ E3dGeometry*  LGeometry = E_DynamicCast(LObject, E3dGeometry);if(LGeometry) LGeometry->AutoLOD = FALSE; }

				E3dInfo*  LComponent = (E3dInfo*)E3dObjectComponent_ByClass(LObject, _ObjectComponent_class, FALSE);

// Add outputs to the cross-section Splines
//
				{
					EIndex  LC = 0;const EIndex  LN = LComponent->NumOfCrossSections;for(;LC < LN;LC++)
					{
						E3dBaseObject_AddOutput((E3dBaseObject*)LComponent->CrossSections[LC], &LComponent->InputInterface);
					}

					LSuccess = E3dScene_NodeHrcAdd(LScene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
					E3dScene_LayoutOnSchematics(LScene, TRUE);
				}
			}

			EIndex  LN = LTInfo.NumOfCrossSections;
			ELst_ObjectsFree(&LTInfo.CrossSections,      &LTInfo.NumOfCrossSections);
			ELst_ObjectsFree(&LTInfo.CrossSectionModels, &LN);

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

				E3dWindows_RedrawByChangeFlags(LScene, E3dChgMeshSHAPE|E3dChgHIERARCHY);
			}
		}

	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "Select at least two Splines for cross-sections");

	EList_FreeAChk_s(LSelectedNodes);
}


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


	LThis->SortCrossSections = CR_SORT_BY_Y;
	LThis->Sides = 24;
	LThis->SplineFollowing = CR_PROPORTIONAL;
	LThis->Topology = CR_HIERARCHY;
	LThis->Close = FALSE;
	LThis->WeldClosedCrossSections = TRUE;
	LThis->Triangles = FALSE;
	LThis->Smooth = TRUE;


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

	LThis->NumOfCrossSections = 0;
	LThis->CrossSections = NULL;
	LThis->CrossSectionModels = NULL;
	LThis->HullParameters = NULL;

	return(E_SUCCESS);
}


//========================================
// Copy-constructor
//========================================
static void _Copy(const E3dObject* PSrcObject, const E3dObjectComponent* PSrcInfo,  E3dObject* PDstObject, E3dObjectComponent* PDstInfo)
{
	const E3dInfo*  LSInfo = (E3dInfo*)PSrcInfo;
	E3dInfo*        LDInfo = (E3dInfo*)PDstInfo;

	_Init(PDstObject, PDstInfo);

#define _CopyField(f)  LDInfo->f = LSInfo->f
	_CopyField(SortCrossSections);
	_CopyField(Sides);
	_CopyField(SplineFollowing);
	_CopyField(Topology);
	_CopyField(Close);
	_CopyField(WeldClosedCrossSections);
	_CopyField(Triangles);
	_CopyField(Smooth);
#undef _CopyField 

	if(LSInfo->NumOfCrossSections)
	{
		ENewNClr(LSInfo->NumOfCrossSections,  LDInfo->CrossSections);
		ENewNClr(LSInfo->NumOfCrossSections,  LDInfo->CrossSectionModels);

		EIndex  LC = 0;for(;LC < LSInfo->NumOfCrossSections;LC++)
		{
			E_AssignToMember(LSInfo->CrossSections[LC], LDInfo,CrossSections[LC]);
			E3dBaseObject_AddOutput((E3dBaseObject*)LDInfo->CrossSections[LC], &LDInfo->InputInterface);

			E_AssignToMember(LSInfo->CrossSectionModels[LC], LDInfo,CrossSectionModels[LC]);
//			E3dBaseObject_AddOutput((E3dBaseObject*)(LDInfo->CrossSectionModels[LC]), &LDInfo->InputInterface);
		}

		LDInfo->NumOfCrossSections = LSInfo->NumOfCrossSections;
	}

	if(LDInfo->SplineFollowing == CR_PROPORTIONAL) _InitHullParameters(LDInfo, TRUE);
}


//================================================
// Destructor
//================================================
static int _Free(E3dObject* PObject, E3dObjectComponent* PInfo)
{
	E3dInfo*   LComponent = (E3dInfo*)PInfo;
	E3dGeometry**  LCrossSections      = (E3dGeometry**)LComponent->CrossSections;
	E3dModel**     LCrossSectionModels = LComponent->CrossSectionModels;

	EIndex  LC = 0;const EIndex  LN = LComponent->NumOfCrossSections;for(;LC < LN;LC++)
	{
		E3dGeometry*  LCrossSection      = LCrossSections[LC];
		E3dModel*     LCrossSectionModel = LCrossSectionModels ? LCrossSectionModels[LC] : NULL;

		if(LCrossSection)
		{
			E3dBaseObject_RemoveOutput((E3dBaseObject*)LCrossSection, &LComponent->InputInterface);
			E_Delete(LCrossSections[LC]);
		}

		if(LCrossSectionModel)
		{
//			E3dNode_RemoveOutput(LCrossSectionModel, &LComponent->InputInterface);
			E_Delete(LCrossSectionModels[LC]);
		}
	}
	if(LCrossSections)
	{
		EFree(LCrossSections);LComponent->CrossSections = NULL;
	}
	if(LCrossSectionModels)
	{
		EFree(LCrossSectionModels);LComponent->CrossSectionModels = NULL;
	}
	LComponent->NumOfCrossSections = 0;

	EFreeChkClr(LComponent->HullParameters);

	EInterface_Clear(&LComponent->InputInterface);

	return(E_SUCCESS);
}


//========================================
// Rebuild() (create/change) method
//========================================
static E3dObject* _Update1(E3dObject* PObject, E3dObjectComponent* PComponentTemplate,  EUpdateFlags* PTriggeredUpdateFlags, EChangeFlags* PTriggeredSceneChangeFlags,  const EBool PCheckOnly)
{
	E3dInfo*       LComponent;
	E3dMesh*       LMesh;
	E3dPolyGroup*  LPolyGroup;

	if(PObject == NULL)
	{
		E_New(E3dMesh, LMesh);
		PObject = (E3dObject*)LMesh;
		if((LPolyGroup = E3dMesh_PolyGroupAdd(LMesh)) == NULL)
		{
			E_Delete(LMesh);return(NULL);
		}

		LComponent = (E3dInfo*)E3dObjectComponent_Add((E3dObject*)LMesh, _ObjectComponent_class);
		if(PComponentTemplate && PComponentTemplate->Class == _ObjectComponent_class) _Copy(PObject, PComponentTemplate, PObject, (E3dObjectComponent*)LComponent);
	}
	else
	{
		if(!EObject_IsSubClass(PObject, E3dMesh)) return(NULL);

		LMesh = (E3dMesh*)PObject;
		LComponent = (E3dInfo*)E3dObjectComponent_ByClass((E3dObject*)LMesh, _ObjectComponent_class, FALSE);
		if(!PCheckOnly && LComponent->AttributeChangeCount == 0 && LComponent->InputChangeCount == 0) return(NULL);    // If no attributes or inputs changed, bail
		LPolyGroup = LMesh->PolyGroups.Items[0];

// Add outputs to the cross-section Splines
//
		if(PCheckOnly)
		{
			EIndex  LC = 0;const EIndex  LN = LComponent->NumOfCrossSections;for(;LC < LN;LC++)
			{
				if(LComponent->CrossSections[LC]) E3dBaseObject_AddOutput((E3dBaseObject*)LComponent->CrossSections[LC], &LComponent->InputInterface);
			}
		}
	}

	if(PCheckOnly || LComponent->CrossSections == NULL) return(NULL);

	if(LComponent->SplineFollowing == CR_PROPORTIONAL) _InitHullParameters(LComponent, TRUE);

	if(LMesh && LComponent->CrossSections)
	{
		E3dMesh_FreeCaches(LMesh);
		E3dMesh_EdgesFree(LMesh);

		E3dSpline**   LCrossSections = LComponent->CrossSections;
		const EIndex  LNumOfCrossSections = LComponent->NumOfCrossSections;


// Check cross-sections
//
		{
			EIndex  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
			{
				if(LCrossSections[LVC] == NULL)
				{
printf("%s() %s:%d <%s> %smissing cross-section%s %d\n", __FUNCTION__, __FILE__, __LINE__, PObject->Name, ECodeYellow, ECodeNormal, (int)LVC);fflush(stdout);
					return(NULL);
				}
			}
		}

		EIndex  LSides   = 0;
		EIndex  LUPoints = 0;

		switch(LComponent->SplineFollowing)
		{
			default:
			case CR_PROPORTIONAL:
				{
					LSides = LComponent->Sides;
					LUPoints = LSides + 1;
				}
			break;

			case CR_STRICT:
				{
// The number of sides is the smallest segment count of the cross-sections
//
					int  LVC = 0;for(;LVC < LNumOfCrossSections;LVC++)
					{
						E3dSpline*  LCrossSection = LCrossSections[LVC];
//						E3dSpline_CreateLinearSegments(LCrossSection);

						int  LTSides;
						int  LTUPoints = LCrossSection->SNumOfLinearSegments;
						if(LCrossSection->Closed && (LComponent->WeldClosedCrossSections)) LTSides = LCrossSection->SNumOfLinearSegments;
						else LTSides = LCrossSection->SNumOfLinearSegments - 1;

						if(LVC == 0)
						{
							LUPoints = LTUPoints;LSides = LTSides;
						}
						else
						{
							if(LSides   > LTSides)   LSides   = LTSides;
							if(LUPoints > LTUPoints) LUPoints = LTUPoints;
						}
					}
				}
			break;
		}


		E3dSpline*     LNextCrossSection;
		E3dPolygon*    LPolygons;
		E3dCoordinate  LU, LV, LUStep, LVStep;
		EIndex         LVPoints = 0,
		               LNumOfVertices = 0,
		               LNumOfPolygons,
		               LColumnA, LNextColumnA, LLastV, LNextLastV;


// Make sure that the VertexNodes have UV
//
		int  LUVOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_UV, 0);
		if(LUVOffsetInVtxNode < 0)
		{
			E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_UV, 0,  NULL);
			LUVOffsetInVtxNode = E3dPolyGroup_VertexAttributeOffset(LPolyGroup, E3dVtxAttribute_UV, 0);
		}
		const E3dVertexNodeSize  LVertexNodeSize = LPolyGroup->VertexNodeSize;


		LNumOfPolygons = LNumOfCrossSections * LSides;
		LNumOfPolygons = LSides * (LNumOfCrossSections - 1);

		if(LComponent->Triangles) LNumOfPolygons *= 2;

		LNumOfVertices = LUPoints * LNumOfCrossSections;


// Allocate Vertices for the Mesh
//
		if(LNumOfVertices != LMesh->Vertices.Count)
		{
			E3dMesh_VerticesFree(LMesh);
			E3dMesh_VerticesAdd(LMesh, LNumOfVertices);
		}


// Allocate Polygons for the Mesh
//
		if(LPolyGroup->Polygons.CountAllocated >= LNumOfPolygons)
		{
			LPolygons = LPolyGroup->Polygons.Items;
			LPolyGroup->Polygons.Count = LNumOfPolygons;
		}
		else
		{
			if(LPolyGroup->Polygons.Items) E3dPolyGroup_FreePolygons(LPolyGroup);

			LPolygons = E3dPolygons_New(LNumOfPolygons);if(LPolygons)
			{
				LPolyGroup->Polygons.Items = LPolygons;
				LPolyGroup->Polygons.Count = LPolyGroup->Polygons.CountAllocated = LNumOfPolygons;
			}
		}

		E3dPolygon*  LPolygon = LPolygons;

		_SetVertices(LMesh->Vertices.Items, LMesh->VertexSize,  LComponent, LUPoints);

// Build the Polygons
//
		LPolygon = LPolyGroup->Polygons.Items;

		LColumnA = 0;LNextColumnA = LVPoints;

		LUStep =  1.0 / (E3dCoordinate)LSides;
		LVStep = -1.0 / (E3dCoordinate)(LNumOfCrossSections - 1);

		LV = 1.0;
		if(LComponent->Triangles)
		{
			EIndex  LVC = 0;for(;LVC < (LNumOfCrossSections - 1);LVC++, LV += LVStep)
			{
				E3dSpline*	LCrossSection = LCrossSections[LVC];

				LNextCrossSection = LCrossSections[LVC + 1];
				LNextColumnA = LColumnA + LUPoints;

				LU = 0.0;
				EIndex  LUC = 0;for(;LUC < (LSides - 1);LUC++, LU += LUStep)
				{
					_M_Add2Tris(LNextColumnA + LUC, LNextColumnA + LUC + 1, LColumnA + LUC + 1, LColumnA + LUC);
				}
				if(LCrossSection->Closed && LComponent->WeldClosedCrossSections) LLastV = 0;
				else LLastV = LSides;
				if(LNextCrossSection->Closed && LComponent->WeldClosedCrossSections) LNextLastV = 0;
				else LNextLastV = LSides;

				_M_Add2Tris(LNextColumnA + LUC, LNextColumnA + LNextLastV, LColumnA + LLastV, LColumnA + LUC);

				LColumnA = LNextColumnA;
			}
		}
		else
		{
			EIndex  LVC = 0;for(;LVC < (LNumOfCrossSections - 1);LVC++, LV += LVStep)
			{
				E3dSpline*	LCrossSection = LCrossSections[LVC];

				LNextCrossSection = LCrossSections[LVC + 1];

				LNextColumnA = LColumnA + LUPoints;

				LU = 0.0;
				EIndex  LUC = 0;for(;LUC < (LSides - 1);LUC++, LU += LUStep)
				{
					_M_AddQuad(LNextColumnA + LUC, LNextColumnA + LUC + 1, LColumnA + LUC + 1, LColumnA + LUC);
				}
				if(LCrossSection->Closed && LComponent->WeldClosedCrossSections) LLastV = 0;
				else LLastV = LSides;
				if(LNextCrossSection->Closed && LComponent->WeldClosedCrossSections) LNextLastV = 0;
				else LNextLastV = LSides;

				_M_AddQuad(LNextColumnA + LUC, LNextColumnA + LNextLastV, LColumnA + LLastV, LColumnA + LUC);

				LColumnA = LNextColumnA;
			}
		}

		if(LComponent->Smooth)
		{
			LPolyGroup->VertexNormalType = E3dNormalFROM_SMOOTHING_ANGLE;
			LPolyGroup->Flags |= E3dUSE_VERTEX_NORMALS;
			E3dPolyGroup_VertexAttributeAdd(LPolyGroup,  NULL, E3dVtxAttribute_Normal, 0,  NULL);
			E3dMesh_RecomputeNormals(LMesh, TRUE, TRUE);
		}
		else
		{
			LPolyGroup->VertexNormalType = 0;
			LPolyGroup->Flags &= ~E3dUSE_VERTEX_NORMALS;
			E3dPolyGroup_VertexAttributeDel(LPolyGroup,  E3dVtxAttribute_Normal, 0);
			E3dMesh_RecomputePolygonNormals(LMesh);
		}

		if(PTriggeredUpdateFlags) *PTriggeredUpdateFlags = E3dUpdateALL & ~E3dUpdateVERTEX_NORMAL;
	}

	return(PObject);
}


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

	{
		E3dObjectComponentClass  LClass =
		{
			.Name=        "Skin",
			.StructSize=  sizeof(E3dInfo),
			.Resources=   EListS_s(_Resources),

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

	if(_ObjectComponent_class)
	{
		EpM_CreateIconStates3_sp(Skin);E_Assign(_Image_Skin, _ObjectComponent_class->Icon);
		XePushButton_AddImg3(_ToolBarButton, Skin, "Tool->Surface", "Skin", '\0', NULL, NULL, TRUE, "Skin: create surface from cross-sections defined by splines\nRight button: dialog box for this tool", _MCB, (EPointer)0);

		_MenuButton = XePushButton_Add(Xe_App, "Menu->Surface", "Skin", '\0', NULL, NULL, TRUE, NULL, _MCB, (EPointer)0);
	}
	return(E_SUCCESS);
}


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

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

	EpIconFree3(Skin);

	return(E_SUCCESS);
}
