//================================================================
// EQUINOX-3D(TM) plugin
//
// - Create and edit Splines
// - Edges to Splines tool
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Feb-15 01:32:30
//
// 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 <E3D/E3D.h>
#include <E3D/Item.h>
#include <E3D/Matrix.h>
#include <E3D/Model.h>

#include <E3D/SplineOps.h>  // Undoable Spline operations

#include <E3D/UI/ModeStack.h>

#ifndef _E3D_Panel_h
#include <E3D/UI/Panel.h>
#endif

#ifndef _E3D_Panels_h
#include <E3D/UI/Panels.h>
#endif

#ifndef _E3D_StatusPanel_h
#include <E3D/UI/StatusPanel.h>  // For E3dp_PrintMessage
#endif

#include <E3D/GL/Draw.h>         // For E3dGL_GetFloatv

#include <Color/Panel.h>

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

// Icons
//
EpIconDef3I(static, SplSegmentLin);
EpIconDef3I(static, SplSegmentCrv);
EpIconDef3I(static, SplSegmentArc);
EpIconDef3I(static, TgC0);
EpIconDef3I(static, TgC1);
EpIconDef3I(static, TgG1);


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

static int		    _SplineType = E3dSplineNONE;  // Type of Spline being drawn

static E3dModel*  _Model = NULL;

// The Geometry the currently edited Spline belongs to
// If this the Spline itself, _Spline will be the same.
// If this is a Face, _Spline will point to a Contour of that Face
//
static E3dGeometry*  _Geometry = NULL;
static E3dSpline*    _Spline = NULL;



static EChangeFlags _EdgesToSplines_InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS);

#define E3dSnVERTEX  1
#define E3dSnEDGE    2

typedef struct
{
	E3dNodeComponentCore;

	EList(, E3dModel*,  SrcModels);            // ...Corresponding Models
	EList(, E3dObject*, SrcObjects);           // Source Objects and... (instances, really)
	EBool*  SrcActiveFlags;                    // ...Corresponding Active flags

	EBool    DoHiddenEdges, DoUnselectedEdges,
	         MergeContiguous;                  // Generate a single Spline for non-branching Edges
	uint8_t  SplineNormalsMode;                // 0: don't generate Spline normals, E3dSnVERTEX: from Vertex normals, E3dSnEDGE from Edge normals

// Internal variables
//
	EInterface          InputInterface;        // This is added to SrcObjects and SrcModels as an output node

	EListA(, E3dSpline*, Splines);             // We cache Splines here, so we don't have to create / destroy tons of Splines every time the source Meshes change
} E3dEdgesToSplines;

#define ROffsetP(field)  (void*)(EPtrOffset(E3dEdgesToSplines, field))
#define ROffsetI(field)  EPtrOffset(E3dEdgesToSplines, field)
static EResource  _EdgesToSplines_Resources[]=
{
	{ "SrcModels",          EResOBJECT|EResDYNAMIC,  ROffsetP(SrcModels),      .Ext.Count= ROffsetI(SrcModels_Count) },
	{ "SrcObjects",         EResOBJECT|EResDYNAMIC,  ROffsetP(SrcObjects),     .Ext.Count= ROffsetI(SrcObjects_Count) },
	{ "SrcActiveFlags",     EResBOOLEAN|EResDYNAMIC, ROffsetP(SrcActiveFlags), .Ext.Count= ROffsetI(SrcObjects_Count) },

	{ "DoHiddenEdges",      EResBOOLEAN,             ROffsetP(DoHiddenEdges) },
	{ "DoUnselectedEdges",  EResBOOLEAN,             ROffsetP(DoUnselectedEdges) },
	{ "MergeContiguous",    EResBOOLEAN,             ROffsetP(MergeContiguous) },
	{ "SplineNormalsMode",  EResUINT8,               ROffsetP(SplineNormalsMode) },
};
#undef ROffsetP
#undef ROffsetI

typedef struct
{
	XeWidget  DoHiddenEdgesW, DoUnselectedEdgesW,
	          SplineNormalsModeNoneW, SplineNormalsModeVertexW, SplineNormalsModeEdgeW,
	          MergeContiguousW;
} E3dResourcesGUI;

static E3dNodeComponentClass*  _EdgesToSplines_class = NULL;


enum
{
	CR_SPLINE_OPEN,
	CR_SPLINE_CLOSE,
	CR_SPLINE_DRAW,
	CR_SPLINE_EDIT_POINTS,
	CR_DELETE_POINTS
};


enum
{
 EditMode_ADD_ADD_ADD = 1,
 EditMode_DELETE
};

static int  _CVEditMode = EditMode_ADD_ADD_ADD;

#define E3dTRANSFORM_EDGES   0
#define E3dUSE_EDGE_NORMALS  1

#define EPSILON	0.00001


//========================================================
// Create compound message string for the given mode
//========================================================
static XeString _GetMessage(const int PType, const int PIndex)
{
	XeString  LXeString;

	const E3dpPrefs*  LPrefs = &E3dp_Prefs;
	const uint32_t    LLabelRGBAp8Color      = Xe_ForegroundRGBA8pColor;
	const uint32_t    LButtonNameRGBAp8Color = LPrefs->ButtonNameRGBAp8Color;

	switch(PType)
	{
		case EditMode_ADD_ADD_ADD:
			LXeString = XeString_NewVa(True, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,    XeTEXT,  "Edit Spline CVs   ",
			 E3dp_LMouseButtonS,  XeTEXT,  ": add CV after last    ",
			 E3dp_MMouseButtonS,  XeTEXT,  ": add CV between CVs    ",
			 E3dp_RMouseButtonS,  XeTEXT,  ": add CV before first    ",
			 XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",  XeFONT, XeFontDEFAULT,    XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT,  ": end mode",
			 0);
		return(LXeString);

		case EditMode_DELETE:
		 LXeString = XeString_NewVa(True, XeFONT, XeFontDEFAULT,  XeCOLOR_RGBA, LLabelRGBAp8Color,    XeTEXT,  "Edit Spline CVs   ",
			 E3dp_LMouseButtonS,
			 XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,       XeFTEXT, ": delete CV #%d  ", PIndex,
			 XeFONT, XeFontFIXEDWIDTH,  XeCOLOR_RGBA, LButtonNameRGBAp8Color,  XeTEXT, "Esc",  XeFONT, XeFontDEFAULT,     XeCOLOR_RGBA, LLabelRGBAp8Color,  XeTEXT, ": end mode",
			 0);
		return(LXeString);
	}
	return(NULL);
}


//========================================
// Open or close selected Splines
//========================================
static void _OpenCloseSplines(const int PAction)
{
	E3dScene*     LScene = E3d_Scene;
	EIndex        LPerformedCnt = 0;
	EChangeFlags  LChanged = 0;
	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNSelectedSplines = E3dScene_GetObjects(LScene, E3dItemSPLINE, E3dSELECTED, FALSE,  &LObjects);if(LNSelectedSplines > 0)
	{
		EListAI_s(, EOpNode, LOpNodes);

		EIndex  LI = 0;for(;LI < LNSelectedSplines;LI++)
		{
			E3dSpline*  LSpline = (E3dSpline*)LObjects.Items[LI];

			switch(PAction)
			{
				case CR_SPLINE_OPEN:
					if(LSpline->Closed)
					{
						LSpline->Closed = FALSE;
						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL & ~E3dCallOUTPUTS,  LScene) | E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;

						EOpNode*  LOpNode = EList_StructAddAClr_s(LOpNodes, 4);if(LOpNode)
						{
							LOpNode->Object  = (ECoreObject*)LSpline;
							LOpNode->Changed = E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;
						}

						LPerformedCnt++;
					}
				break;

				case CR_SPLINE_CLOSE:
					if(!LSpline->Closed)
					{
						LSpline->Closed = TRUE;
						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL & ~E3dCallOUTPUTS,  LScene) | E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;

						EOpNode*  LOpNode = EList_StructAddAClr_s(LOpNodes, 4);if(LOpNode)
						{
							LOpNode->Object  = (ECoreObject*)LSpline;
							LOpNode->Changed = E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY;
						}

						LPerformedCnt++;
					}
				break;
			}
		}

		switch(PAction)
		{
		 case CR_SPLINE_CLOSE:
			 if(LPerformedCnt > 0)
			 {
				 E3dp_PrintMessage(ELogMESSAGE, 5000, "%d Spline(s) closed", LPerformedCnt);
			 }
			 else E3dp_PrintMessage(ELogWARNING, 5000, "No open Spline selected");
		 break;

		 case CR_SPLINE_OPEN:
			 if(LPerformedCnt > 0)
			 {
				 E3dp_PrintMessage(ELogMESSAGE, 5000, "%d Spline(s) opened", LPerformedCnt);
			 }
			 else E3dp_PrintMessage(ELogWARNING, 5000, "Select at least 1 closed Spline");
		 break;
		}

		EList_FreeA_s(LObjects);

		if(LOpNodes.Items)
		{
			LChanged |= E3dObjects_CallOutputs(ECbINTERNAL|ECbUSER,  &LOpNodes,  NULL);
			EList_FreeA_s(LOpNodes);
		}

		E3dWindows_RedrawByChangeFlags(LScene, LChanged);
	}
	else E3dp_PrintMessage(ELogWARNING, 5000, "No Spline selected");
}


//================================================
// Start CV moving
//================================================
static void _CVStartMoving(E3dWindow* P3DWindow,  const int PPtrX, const int PPtrY,  E3dModel* PModel, E3dSpline* PSpline, E3dSplineCV* PCV, const int PSubCV, E3dCoord3* PPoint)
{
	E3dpPointMove*  LPointMove = &E3dp_PointMove;

	E3dp_PushPointerMode(E3dpPointerModeMOVE_POINT, XeString_SimpleNew("Moving Spline CV"));

	LPointMove->ActiveGeometry = _Geometry;
	LPointMove->ActiveSpline = PSpline;

	LPointMove->ActiveSplineCV = PCV;
	LPointMove->ActiveSubCV = PSubCV;
	LPointMove->OldPointFlags = PCV->CVFlags;

	PCV->CVFlags |= E3dCV_ACTIVE;

	E3dMatrix_Invert3x4(PModel->LocalToWorldMatrix, LPointMove->WorldToLocalMatrix);

	E3dp_PointMoveInit(P3DWindow, E3dp_InteractionObject3D,  PPtrX, PPtrY,  PPoint->X, PPoint->Y, PPoint->Z, FALSE);
}


//========================================================
// Add a CV to a Spline with and undo-able operator
//========================================================
static EChangeFlags _AddCV(E3dPickCallbackStruct* PPickStruct, E3dSpline* PSpline, const EIndex PCVIndex, const E3dSplineCV* PCV, const E3dSplineCV* PReferenceCV, const E3dCoord3* PLocalPosition, const E3dCoord3* PWorldPosition, const int PSubCV, E3dModel* PModel, EOps* POps)
{
	EChangeFlags  LChanged = 0;

	E3dp_PickStop();    // Must stop picking if we change a Geometry

// Initialize Operator, execute it and add it to the undo stack
//
	{
		E3dOperatorAddDelSplineCVs*  LOperator = (E3dOperatorAddDelSplineCVs*)EOperator_New(E3dOperator_AddSplineCVs_class);
		const EBool                  LHasTangents = (EBool)(PSpline->SplineType == E3dSplineBEZIER || PSpline->SplineType == E3dSplineHERMITE);

		{
			E3dSplineCV*  LCV;ENew(LCV);E3dSplineCV_Init(LCV, PSpline->SplineType);

			if(PCV) *LCV = *PCV;
			else
			{
				LCV->Position = *PLocalPosition;

//			if(LHasTangents)
				{
// With this part commented out, it's a neuron-drawer
// :)
					LCV->Previous = *PLocalPosition;

					LCV->Next = *PLocalPosition;
				}
			}

// Copy constraint, segment type and resolution from PReferenceCV, if it's given
//
			if(PReferenceCV)
			{
				LCV->Constraint = PReferenceCV->Constraint;
				LCV->Segment.Shape = PReferenceCV->Segment.Shape;
				LCV->Segment.NumOfLinSteps = PReferenceCV->Segment.NumOfLinSteps;
			}

			E_AssignToMember(_Geometry, LOperator,Object);
			E_AssignToMember(PSpline, LOperator,Spline);
			ENew(LOperator->CVs_Indices);LOperator->CVs_Indices[0] = PCVIndex;LOperator->CVs = LCV;LOperator->CVs_Count = 1;

			EList_ObjectAddA_s(POps->Operators, 1, LOperator);
			if(POps->Operators.Count)
			{
				LChanged |= EOps_Run(POps, EOpDO);    // Run OperatorList
			}
		}

		E3dSplineCV*  LCV = PSpline->CVs.Items + PCVIndex;

		switch(PSubCV)
		{
			case E3dCV_POSITION:
				_CVStartMoving(PPickStruct->E3DWindow,  PPickStruct->PtrX, PPickStruct->PtrY,  PModel, PSpline, LCV, LHasTangents ? E3dCV_OUT_TANGENT : E3dCV_POSITION, &LCV->Position);

				E3dp_PrintMessage(ELogMESSAGE, 0, "Point #%d: % .4f,% .4f,% .4f (local: % .4f,% .4f,% .4f)", PCVIndex,  XYZ_p(PWorldPosition),  XYZ_s(LCV->Position));
			break;

			case E3dCV_IN_TANGENT:
				_CVStartMoving(PPickStruct->E3DWindow,  PPickStruct->PtrX, PPickStruct->PtrY,  PModel, PSpline, LCV, LHasTangents ? E3dCV_OUT_TANGENT : E3dCV_POSITION, &LCV->Previous);

				E3dp_PrintMessage(ELogMESSAGE, 0, "Point #%d (prev): % .4f,% .4f,% .4f (local: % .4f,% .4f,% .4f)", PCVIndex,  XYZ_p(PWorldPosition),  XYZ_s(LCV->Previous));
			break;

			case E3dCV_OUT_TANGENT:
				_CVStartMoving(PPickStruct->E3DWindow,  PPickStruct->PtrX, PPickStruct->PtrY,  PModel, PSpline, LCV, LHasTangents ? E3dCV_OUT_TANGENT : E3dCV_POSITION, &LCV->Next);

				E3dp_PrintMessage(ELogMESSAGE, 0, "Point #%d (next): % .4f,% .4f,% .4f (local: % .4f,% .4f,% .4f)", PCVIndex,  XYZ_p(PWorldPosition),  XYZ_s(LCV->Next));
			break;
		}
	}

	E3dp_PickResume();

	return(LChanged);
}


static void _PickPosition(E3dPickCallbackStruct* PPickStruct, const E3dCoord3* PPositionIn)
{
	E3dScene*     LScene = E3d_Scene;
	E3dModel*     LModel = NULL;
	E3dSpline*    LSpline = _Spline;
	E3dModel*     LCameraModel = PPickStruct->CameraModel;
	E3dMatrix     LViewerToWorldMatrix;E3dMatrix_Copy(LCameraModel->LocalToWorldMatrix, LViewerToWorldMatrix);
	E3dMatrix     LWorldToViewerMatrix;E3dMatrix_Invert3x4(LViewerToWorldMatrix, LWorldToViewerMatrix);
	E3dMatrix     LMatrix;
	E3dRay        LRay;
	E3dCoord3     LRO = PPickStruct->RayOrigin, LRD = PPickStruct->RayDirection;
	EChangeFlags	LChanged = 0;
	EBool         LNewModel = FALSE;

	E3dCoord3     LPosition;

	if(PPositionIn) LPosition = *PPositionIn;
	else
	{
		LRay.Origin = PPickStruct->RayOrigin;
		LRay.Direction = PPickStruct->RayDirection;

// Intersect the ray cast by the pick algorithm with a plane that is perpendicular
// to the Camera's view direction and passes through a reference point
//
		{
			E3dCoord3      LDiff, LViewDirection;  // Camera Direction vector
			E3dCoordinate  LD;                     // t parameter
			E3dCoord3      LAimPoint, LAimDirection;E3dModel_GetAim(LScene, LCameraModel,  &LAimPoint, &LAimDirection, NULL, NULL, NULL);

			LViewDirection.X = -LAimDirection.X;
			LViewDirection.Y = -LAimDirection.Y;
			LViewDirection.Z = -LAimDirection.Z;

// Get D in the plane equation
//
			LModel = _Model;
			if(LModel && LSpline && LSpline->CVs.Items)
			{
				const unsigned int	LCVSize = sizeof(E3dSplineCV);

				switch(PPickStruct->Button)
				{
					case Button1:	// Add after last CV, use last CV as the reference point
						{
							const E3dSplineCV*  LReferenceCV = EStruct_ByIndex(LSpline->CVs.Items, LCVSize, LSpline->CVs.Count - 1);
							EMatrix_Transform3x4_vv(LModel->LocalToWorldMatrix,  LReferenceCV->Position,  LDiff);
							LDiff.X *= LViewDirection.X;
							LDiff.Y *= LViewDirection.Y;
							LDiff.Z *= LViewDirection.Z;
						}
					break;

					case Button3:	// Add before first CV, use first CV as the reference point
						{
							const E3dSplineCV*  LReferenceCV = (E3dSplineCV*)LSpline->CVs.Items;
							EMatrix_Transform3x4_vv(LModel->LocalToWorldMatrix,  LReferenceCV->Position,  LDiff);
							LDiff.X *= LViewDirection.X;
							LDiff.Y *= LViewDirection.Y;
							LDiff.Z *= LViewDirection.Z;
						}
					break;

					default:
						LDiff.X = LViewDirection.X * LAimPoint.X;
						LDiff.Y = LViewDirection.Y * LAimPoint.Y;
						LDiff.Z = LViewDirection.Z * LAimPoint.Z;
					break;
				}
			}
			else
			{
				LDiff.X = LViewDirection.X * LAimPoint.X;
				LDiff.Y = LViewDirection.Y * LAimPoint.Y;
				LDiff.Z = LViewDirection.Z * LAimPoint.Z;
			}

			LD = LDiff.X + LDiff.Y + LDiff.Z;

// t is the parameter for the ray where it intersects the plane
//
			const E3dCoordinate	LT = (LD - LViewDirection.X * LRO.X - LViewDirection.Y * LRO.Y - LViewDirection.Z * LRO.Z) / (LViewDirection.X * LRD.X + LViewDirection.Y * LRD.Y + LViewDirection.Z * LRD.Z);

			LPosition.X = LRO.X + LT * LRD.X;
			LPosition.Y = LRO.Y + LT * LRD.Y;
			LPosition.Z = LRO.Z + LT * LRD.Z;

			if(PPickStruct->SnapToX) LPosition.X = E3d_SnapCoordinateTo(LPosition.X, PPickStruct->SnapToXV);
			if(PPickStruct->SnapToY) LPosition.Y = E3d_SnapCoordinateTo(LPosition.Y, PPickStruct->SnapToYV);
			if(PPickStruct->SnapToZ) LPosition.Z = E3d_SnapCoordinateTo(LPosition.Z, PPickStruct->SnapToZV);
		}
	}

	switch(PPickStruct->Button)
	{
		case Button1:
		case Button3:
			if(_Model == NULL)
			{
				char*  LModelName = NULL;
				char*  LGeometryName = NULL;

				switch(_SplineType)
				{
					case E3dSplineBEZIER:
						LModelName    = "BezSplineMdl";
						LGeometryName = "BezSpline";
					break;

					case E3dSplineBSPLINE:
						LModelName    = "BSplineMdl";
						LGeometryName = "BSpline";
					break;

					case E3dSplineCARDINAL:
						LModelName    = "CardSplineMdl";
						LGeometryName = "CardSpline";
					break;

					case E3dSplineLINEAR:
						LModelName    = "LinSplineMdl";
						LGeometryName = "LinSpline";
					break;
				}

				E_New(E3dModel, LModel);if(LModel)
				{
					EObject_SetName(LModel, E3dScene_GetUniqueNodeName(LScene, LModelName), FALSE);

					E_New(E3dSpline, LSpline);if(LSpline)
					{
						EObject_SetName(LSpline, E3dScene_GetUniqueObjectName(LScene, LGeometryName), FALSE);
						LSpline->SplineType = _SplineType;
						E3dModel_ObjectAppend(LModel, (E3dObject*)LSpline, FALSE);

						E3dScene_NodeHrcAdd(E3d_Scene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
						E3dScene_ObjectsUpdateModelList(LScene);
						E3dScene_LayoutOnSchematics(LScene, TRUE);

						LModel->Selection = E3dNodeSelNODE;
						E3dTransformPanel_Update(NULL,  E3dDoALL, E3dDoALL, E3dDoALL,  E3dpDO_NOT_CHANGE,  E3dTrPanelALL);

						_Model = LModel;
						_Geometry = (E3dGeometry*)LSpline;
						_Spline = LSpline;
						LChanged |= E3dChgHIERARCHY;
						LNewModel = TRUE;
					}
				}
			}
		break;
	}

	if(LSpline && E3dOperator_AddSplineCVs_class)
	{
		EOps   LOps;EOps_Init(&LOps);
		EBool  LHasTangents = (EBool)(LSpline->SplineType == E3dSplineBEZIER);

//printf("%s() %s:%d [%s]\n", __FUNCTION__, __FILE__, __LINE__, LModel ? LModel->Name : "");fflush(stdout);
// Find the Model that 'makes' this Spline selected and use the
// inverse of its LocalToWorldMatrix to go to local space...
//
		if(LModel == NULL) LModel = E3dObject_GetSelectingModel((E3dObject*)_Geometry, TRUE);

		E3dCoord3  LWorldPosition = LPosition;

		if(LModel)
		{
			E3dMatrix_Invert3x4(LModel->LocalToWorldMatrix, LMatrix);
			EMatrix_Transform3x4_v(LMatrix,  LPosition);    
		}
		else
		{
// If there's no LModel, return
// Maybe we should exit CV editing if the Spline gets unselected?
//
			return;
//			E3dMatrix_LoadIdentity(LMatrix);
		}

		switch(PPickStruct->Button)
		{
// Add CV after the last one
//
			case Button1:
				LChanged |= _AddCV(PPickStruct, LSpline, LSpline->CVs.Count, NULL, LSpline->CVs.Count ? LSpline->CVs.Items + LSpline->CVs.Count - 1 : NULL, &LPosition, &LWorldPosition, LHasTangents ? E3dCV_OUT_TANGENT : E3dCV_POSITION, LModel, &LOps);
			break;

// Add CV between 2 existing ones
//
			case Button2:
				if(PPositionIn == NULL)
				{
					if(LSpline->CVs.Count >= 2)
					{
						E3dSplineCV  LTCV;EIndex  LCVIndex = E3dSpline_CVInsertBetween(LSpline, LMatrix,  &LPosition, &LRay, &LTCV);if(LCVIndex > -1)
						{
							LChanged |= _AddCV(PPickStruct, LSpline, LCVIndex,  &LTCV, NULL,  &LPosition, &LWorldPosition, (LHasTangents && LTCV.Segment.Shape != E3dSplineLINEAR) ? E3dCV_IN_TANGENT : E3dCV_POSITION, LModel, &LOps);
						}
					}
					else
					{
						E3dp_PrintMessage(ELogWARNING, 4000, "Spline must have at least 2 CVs to add between");
						return;
					}
				}
			break;

// Add CV to the beginning
//
			case Button3:
				LChanged |= _AddCV(PPickStruct, LSpline, 0,  NULL, LSpline->CVs.Items,  &LPosition, &LWorldPosition, LHasTangents ? E3dCV_IN_TANGENT : E3dCV_POSITION, LModel, &LOps);
			break;
		}

		if(LOps.Operators.Count)
		{
			if(!LNewModel) E3dpUndoStack_AddOps(&LOps, NULL);    // Add OperatorList to undo stack
			else EOps_FreeContents(&LOps);
		}

// Note that _Geometry may not be the same as LSpline (e.g. if it's a Face)
//
		if(LChanged) LChanged |= E3dObject_UpdateForDisplay((E3dObject*)_Geometry,  E3dUpdateALL, E3dCallALL,  LScene);
	}

	E3dWindows_RedrawByChangeFlags(LScene, LChanged);
}


static EBool _PolygonIntersect(const E3dGridRay* PRay, E3dModel* PModel, const E3dMesh* PMesh, const E3dPolyGroup* PPolyGroup, const E3dPolygon* PPolygon,  E3dCoordinate* PT)
{
	const E3dVertex*         LVertices = PMesh->Vertices.Items;const E3dVertexSize  LVertexSize = PMesh->VertexSize;
	const E3dVertexNodeSize  LVertexNodeSize = PPolyGroup->VertexNodeSize;
	const E3dCoordinate*     LVertexMatrix = PModel->LocalToWorldMatrix;

  E3dRTriangle  LRTriangle;

//printf("%s() %s:%d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)(PPolygon->NumOfVertices));fflush(stdout);
	if(PPolygon->NumOfVertices == 3)  // Is this Polygon a triangle?
	{
		const E3dVertexNode*  LVertexNode = PPolygon->VertexNodes;
 
		LRTriangle.Normal = PPolygon->Normal;
		int  LI = 0;for(;LI < 3;LI++, EStruct_Inc(LVertexNode, LVertexNodeSize))
		{
			const E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexNode->VertexID);EMatrix_Transform3x4_vv(LVertexMatrix, LVertex->Position, LRTriangle.Vertices[LI].Position);EStruct_Inc(LVertexNode, LVertexNodeSize);
		}

    if(E3dRTriangle_PrepForRayTracing(&LRTriangle) == E_SUCCESS)
    {
			if(E3dRay_IntersectTriangleFrontBack(PRay, (E3dRTriangleICore*)&LRTriangle, PT)) return(TRUE);
		}
	}
	else
	{
		const E3dITriangle*  LITriangle = PPolygon->ITriangles;if(LITriangle)
		{
			const E3dVertexNode*  LVertexNodes = PPolygon->VertexNodes;
 			const EIndex          LTN = PPolygon->ITriangles_Count;
	
			EIndex  LTC = 0;for(;LTC < LTN;LTC++, LITriangle++)
			{
				const E3dVertexNodeIndex*  LVertexNodeIndices = LITriangle->VertexNodes;

//printf("%s() %s:%d %d\n", __FUNCTION__, __FILE__, __LINE__, (int)LTC);fflush(stdout);
				LRTriangle.Normal = PPolygon->Normal;
				int  LI = 0;for(;LI < 3;LI++)
				{
					const E3dVertexNode*  LVertexNode = EStruct_ByIndex(LVertexNodes, LVertexNodeSize, LVertexNodeIndices[LI]);const E3dVertex*  LVertex = EStruct_ByIndex(LVertices, LVertexSize, LVertexNode->VertexID);EMatrix_Transform3x4_vv(LVertexMatrix, LVertex->Position, LRTriangle.Vertices[LI].Position);
				}

		    if(E3dRTriangle_PrepForRayTracing(&LRTriangle) == E_SUCCESS)
    		{
					if(E3dRay_IntersectTriangleFrontBack(PRay, (E3dRTriangleICore*)&LRTriangle, PT)) return(TRUE);
				}
				else return(FALSE);
			}
		}
	}

	return(FALSE);
}


//================================================================
// Add Spline CV pick-callback
//================================================================
static void _PickCB_EditCVs(E3dPickCallbackStruct* PPickStruct, EPointer PClientData,  const EIndex PNumOfItems, E3dItem* PPickedItems)
{
	if(PPickStruct->Reason == E3dCR_ABORT)
	{
		_SplineType = E3dSplineNONE;
		_Model = NULL;
		_Geometry = NULL;
		_Spline = NULL;
		return;
	}

/*
if(_Spline)
{
 E3dSpline*  LSpline = _Spline;
 EIndex  LI = 0;for(;LI < LSpline->CVs_Count;LI++)
{
printf(" %08x", LSpline->CVs[LI].Flags);fflush(stdout);
}
printf("\n");fflush(stdout);
}
*/

	if(PPickStruct->Button != 0)
	{
		if(PPickedItems)
		{
			E3dItem*  LItem = PPickedItems;

			switch(LItem->Item.Type)
			{
				case E3dItemMODEL:
				 {
					 E3dObjItem*  LTItem = (E3dObjItem*)PPickedItems;

// Model picked explicitly (e.g. on Schematics)
//
					 if(LTItem->Node && LTItem->Object == NULL)
					 {
						 E3dNode*         LNode = LTItem->Node;
						 const E3dCoord3  LPos = E3dCoord3_s(LNode->LocalToWorldMatrix[M30], LNode->LocalToWorldMatrix[M31], LNode->LocalToWorldMatrix[M32]);
						 _PickPosition(PPickStruct, &LPos);
					 }
				 }
				break;
			}
		}
	}

	const E3dViewClass*  LViewClass = PPickStruct->E3DWindow->ViewMode->Class;
	if(LViewClass == E3dSchematicsView_class) return;

// If PPickStruct->Button is 0, it's just pointer motion
//
//printf("%s() %s:%d  %d %d\n", __FUNCTION__, __FILE__, __LINE__, PPickStruct->Button, PPickStruct->HighlightedItemChanged);fflush(stdout);
	if(PPickStruct->Button == 0 && PPickStruct->HighlightedItemChanged)
	{
		if(PNumOfItems)
		{
			const E3dItem*  LCVItem = PPickedItems;

			if(LCVItem && LCVItem->Item.Type == E3dItemSPLINE_CV)
			{
				if(_CVEditMode != EditMode_DELETE)
				{
					const E3dSplineItem*  LSplineItem = (E3dSplineItem*)LCVItem;
					E3dp_ChangeCompoundMessage(_GetMessage(EditMode_DELETE, LSplineItem->PointIndex));
					_CVEditMode = EditMode_DELETE;
				}
			}
			else goto NoCV;
		}
		else
		{
NoCV:
			if(_CVEditMode != EditMode_ADD_ADD_ADD)
			{
				E3dp_ChangeCompoundMessage(_GetMessage(EditMode_ADD_ADD_ADD, 0));
				_CVEditMode = EditMode_ADD_ADD_ADD;
			}
		}
		return;
	}

	if(PPickStruct->Button != 0)
	{
		if(PPickedItems)
		{
			const E3dItem*  LClosestItem = PPickedItems;

			if(LClosestItem)
			{
//printf("%s() %s:%d  CV %d\n", __FUNCTION__, __FILE__, __LINE__, LClosestItem ? LClosestItem->ObjItem.ZNear : 0);fflush(stdout);
//printf("%s() %s:%d  CV %d  Surface %d\n", __FUNCTION__, __FILE__, __LINE__, LClosestItem ? LClosestItem->ObjItem.ZNear : 0, LSurfaceItem ? LSurfaceItem->ObjItem.ZNear : 0);fflush(stdout);
				switch(LClosestItem->Item.Type)
				{
// Surface was hit, place new CV on the surface
//
					case E3dItemPOLYGON:
						{
							E3dPolyItem*  LPolyItem = (E3dPolyItem*)LClosestItem;
							E3dGridRay    LRay;
							E3dModel*     LCameraModel;
							const int     LX = PPickStruct->PtrX, LY = PPickStruct->PtrY;
							E3dWindow*    L3DWindow = PPickStruct->E3DWindow;
							E3dCamera*    LCamera = E3dWindow_CastPrimaryRay(L3DWindow,  LX, LY,  (E3dRay*)&LRay, &LCameraModel);if(LCamera)
							{
								E3dCoord3            LPosition = E3dCoord3_s(0.0, 0.0, 0.0);
								const E3dPolyGroup*  LPolyGroup = LPolyItem->PolyGroup;

								if(0)
								{
// E3dIMPLEMENTME
// High-precision version
									if(LPolyItem->PolygonIndex >= 0)
									{
//printf("%s() %s:%d  <%s/%s/%s>\n", __FUNCTION__, __FILE__, __LINE__, LPolyItem->Node->Name, LPolyItem->Object->Name, LPolyGroup->Name);fflush(stdout);
										E3dCoordinate  LT;if(_PolygonIntersect(&LRay, (E3dModel*)LPolyItem->Node, (E3dMesh*)LPolyItem->Object, LPolyGroup, LPolyGroup->Polygons.Items + LPolyItem->PolygonIndex,  &LT))
										{
											E_AddScaled3_vvv(LRay.Origin, LRay.Direction, LT, LPosition);
printf("%s() %s:%d\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
											_PickPosition(PPickStruct, &LPosition);
										}
									}
								}
								else
								{
// Low-precision version, using OpenGL and the Z-buffer
//
									E3dMatrix      LViewerToWorldMatrix;E3dMatrix_Copy(LCameraModel->LocalToWorldMatrix, LViewerToWorldMatrix);
									E3dMatrix      LWorldToViewerMatrix;E3dMatrix_Invert3x4(LViewerToWorldMatrix, LWorldToViewerMatrix);
									E3dCoordinate  LZNear = (E3dCoordinate)(LPolyItem->ZNear) / 4294967295.0, LXF = (E3dCoordinate)LX, LYF = (E3dCoordinate)(L3DWindow->Size.Y - LY);
									E3dGLMatrix    LGLProjectionMatrix;E3dGL_GetFloatv(GL_PROJECTION_MATRIX, LGLProjectionMatrix);
									E3dMatrix      LProjectionMatrix;E3dMatrix_FromGL(LGLProjectionMatrix, LProjectionMatrix);
									GLint          LGLViewPort[4];EpglGetIntegerv(GL_VIEWPORT, LGLViewPort);

									const EIndex  LViewPort[4] = { LGLViewPort[0], LGLViewPort[1], LGLViewPort[2], LGLViewPort[3] };
									E3d_UnProject(LXF, LYF - (E3dCoordinate)(L3DWindow->Size.Y), LZNear,  LViewPort, LProjectionMatrix, LWorldToViewerMatrix,  &LPosition);
									_PickPosition(PPickStruct, &LPosition);
								}
							}
						}
					break;

					case E3dItemSURFACE:
printf("%s() %s:%d  %sIMPLEMENT ME%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
					break;

/*
					case E3dItemMODEL:
					 {
						 E3dObjItem*  LTItem = (E3dObjItem*)PPickedItems;

						 if(LTItem->Node && LTItem->Object == NULL)
						 {
printf("%s() %s:%d Node\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
						 }
						 else
						 {
							 _PickPosition(PPickStruct, PNumOfItems, PPickedItems);
printf("%s() %s:%d Node\n", __FUNCTION__, __FILE__, __LINE__);fflush(stdout);
							}
						}
					break;
*/

					case E3dItemVERTEX:
					break;

					case E3dItemSPLINE_CV:
					case E3dItemTANGENT_PREVIOUS:
					case E3dItemTANGENT_NEXT:
// Delete CV
//
						{
							E3dSplineItem*  LSplineItem = (E3dSplineItem*)LClosestItem;
							E3dSpline*      LSpline = LSplineItem->Spline;

							if(LSpline->CVs.Count > 1)
							{
								const EIndex  LCVIndex = LSplineItem->PointIndex;

								E3dp_PickStop();    // Must stop picking if we change a Geometry

//							EList_StructRemoveByIndex(LSpline->CVs, LCVIndex);

// Unhighlight CV (it was highlighted because the pointer was over it)
//
								LSpline->CVs.Items[LCVIndex].CVFlags &= ~E3dCV_ACTIVE;
// Initialize Operator, execute it and add it to the undo stack
//
								if(E3dOperator_DeleteSplineCVs_class)
								{
									EOps  LOps;EOps_Init(&LOps);

									E3dOperatorAddDelSplineCVs*  LOperator = (E3dOperatorAddDelSplineCVs*)EOperator_New(E3dOperator_DeleteSplineCVs_class);

									E_AssignToMember(LSplineItem->Object, LOperator,Object);
									E_AssignToMember(LSpline, LOperator,Spline);
									ENew(LOperator->CVs_Indices);LOperator->CVs_Indices[0] = LCVIndex;LOperator->CVs_Count = 1;

									EList_ObjectAddA_s(LOps.Operators, 1, LOperator);
									E3dp_RunOperators(&LOps, EOpDO);    // Run Operators
									E3dpUndoStack_AddOps(&LOps, NULL);  // Add OperatorList to undo stack
								}

								E3dp_PickResume();

								E3dp_PrintMessage(ELogMESSAGE, 2500, "Deleted Spline CV #%d", LCVIndex);
							}
							else E3dp_PrintMessage(ELogMESSAGE, 2500, "Spline has only one CV. Can not delete it.");
						}
					break;
				}

				return;
			}
		}
		else
		{
			_PickPosition(PPickStruct, NULL);
		}
	}
}


//========================================
// Create Spline
//========================================
static void _MCB_SplineCreate(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	XeString  LXeString = _GetMessage(EditMode_ADD_ADD_ADD, 0);

	E3dp_ResetWorkModes();
// E3dp_PickRequest(LXeString, E3dItemWORLD_POSITION|E3dItemSPLINE_CV, _PickCB_EditCVs, 0, TRUE);
	E3dp_PickRequest(LXeString, E3dItemWORLD_POSITION|E3dItemSPLINE_CV|E3dItemMODEL|E3dItemSURFACE,  _PickCB_EditCVs, NULL,  0, TRUE);

	_SplineType = (EPointerInt)PClientData;
}


//========================================
// Menu callback
//========================================
static void _MCB_Splines(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	switch((EPointerInt)PClientData)
	{
		case CR_SPLINE_OPEN:
		case CR_SPLINE_CLOSE:
			_OpenCloseSplines((EPointerInt)PClientData);
		break;

		case CR_SPLINE_EDIT_POINTS:
			{
				E3dItem  LItem;

				E3dp_ResetWorkModes();
				if(E3dScene_GetSingleSelection(E3d_Scene, &LItem, E3dItemSPLINE|E3dItemFACE_CONTOUR, E3dVISIBLE) != E3dItemNONE)
				{
					E3dSplineItem*  LSplineItem = (E3dSplineItem*)(&LItem);

					_Geometry = (E3dGeometry*)(LSplineItem->Object);
					_Spline = LSplineItem->Spline;
					_Model = E3dObject_GetSelectingModel((E3dObject*)LSplineItem->Spline, TRUE);

					E3dp_PickRequest(_GetMessage(EditMode_ADD_ADD_ADD, 0), E3dItemWORLD_POSITION|E3dItemSPLINE_CV|E3dItemMODEL|E3dItemSURFACE,  _PickCB_EditCVs, NULL,  0, TRUE);
				}
				else E3dp_PrintMessage(ELogWARNING, 3000, "Select a visible Spline to edit");
			}
		break;
	}
}


typedef struct
{
	XeWidget       UniformStepsBW, UniformLinStepsW, ClosedBW,
								 CardinalTensionW,
								 SplineLinearW, SplineBezierW, SplineBSplineW, SplineCardinalW,
								 StatsValLW, CVIndexW, SegmentVisibleW, ArcLengthTesselateW,
								 SegmentDrawStepsMatrixW, SegmentDrawStepsW,
								 SegmentShapeLinearW, SegmentShapeBezierW, SegmentShapeCircularArcW,
								 SegmentArcSettingsMatrixW, SegmentCircularArcModeW, SegmentCircularArcValueW, SegmentCircularArcAutoW,
								 CVTangentConstraintMatrixW, C0TangentConstraintBW, C1TangentConstraintBW, G1TangentConstraintBW,
								 SegmentShapeMatrixW, SegmentSettingsMatrixW, SegTypeLabelW;

	XeDrawer       ColorDrawer;
	XeWidget       UseCustomColorW, ColorSwatchW;
	EcColorPanel*  ColorPanel;

	XeWidget       VectorDrawScaleW;

	EIndex         CVIndex;
} E3dSplineResourcesGUI;


static void  _SplineUpdatePanel(E3dObjectPanel* PPanel);


//========================================
// Update Spline stats label
//========================================
static void _SplineUpdateStatsLabel(E3dObjectPanel* PPanel)
{
	E3dSpline*               LSpline = (E3dSpline*)(PPanel->Object);
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)PPanel, E3dSpline_class);if(LClassDrawer)
	{
		char                    LTmpStr[256];
		E3dSplineResourcesGUI*  LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

/*
		switch(LSpline->SplineType)
		{
			case E3dSplineLINEAR:
				if(LSpline->Closed) sprintf(LTmpStr, "LINEAR\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count), LSpline->Length);
				else sprintf(LTmpStr, "LINEAR\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count - 1), LSpline->Length);
			break;

			case E3dSplineBEZIER:
				if(LSpline->Closed) sprintf(LTmpStr, "BEZIER\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count), LSpline->Length);
				else sprintf(LTmpStr, "BEZIER\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count - 1), LSpline->Length);
			break;

			case E3dSplineBSPLINE:
				if(LSpline->Closed) sprintf(LTmpStr, "B-SPLINE\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count), LSpline->Length);
				else sprintf(LTmpStr, "B-SPLINE\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count - 3), LSpline->Length);
			break;

			case E3dSplineCARDINAL:
				if(LSpline->Closed) sprintf(LTmpStr, "CARDINAL\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count), LSpline->Length);
				else sprintf(LTmpStr, "CARDINAL\n%6lld\n%6lld\n%f", (long long)(LSpline->CVs_Count), (long long)(LSpline->CVs_Count - 3), LSpline->Length);
			break;
		}
*/
		if(LSpline->Closed) sprintf(LTmpStr, "%lld / %lld / %f", (long long)LSpline->CVs.Count, (long long)LSpline->CVs.Count, LSpline->Length);
		else sprintf(LTmpStr, "%6lld / %lld / %f", (long long)LSpline->CVs.Count, LSpline->CVs.Count > 0 ? (long long)(LSpline->CVs.Count - 1) : 0, LSpline->CVs.Count ? LSpline->Length : 0.0);


		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(LGUI->StatsValLW,  Args, ArgCnt);

			XeString_Free(LXeStr);
		}
	}
}


//========================================================
// Update Spline-segment stuff in the Spline dialog-box
//========================================================
static void _SplineSegmentUpdateGUI(E3dObjectPanel* PPanel)
{
	E3dSpline*               LSpline = (E3dSpline*)(PPanel->Object);if(LSpline == NULL) return;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)PPanel, E3dSpline_class);if(LClassDrawer == NULL) return;
	E3dSplineResourcesGUI*   LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;if(LGUI == NULL) return;

	if(LGUI->CVIndex < 0) LGUI->CVIndex = 0;
	else if(LGUI->CVIndex >= LSpline->CVs.Count) LGUI->CVIndex = LSpline->CVs.Count - 1;

	if(LSpline->CVs.Count > 0)
	{
		E3dSplineCV*  LCVs = LSpline->CVs.Items;
		E3dSplineCV*  LCV = LCVs + LGUI->CVIndex;

		XeToggleButton_SetState(LGUI->SegmentVisibleW, (LCV->Segment.Flags & E3dSegmentVISIBLE) != 0, FALSE);
		XeToggleButton_SetState(LGUI->ArcLengthTesselateW, (LCV->Segment.Flags & E3dSegmentARC_LENGTH_TESSELATE) != 0, FALSE);

		switch(LSpline->SplineType)
		{
			case E3dSplineBEZIER:
				{
					XeScale_SetValue(LGUI->SegmentDrawStepsW, LCV->Segment.NumOfLinSteps, FALSE);

					int  LI = 0;const int  LN = LSpline->CVs.Count;for(;LI < LN;LI++)
					{
						if(LCVs + LI == LCV)
						{
							LCVs[LI].CVFlags |= E3dCV_ACTIVE;
							LCVs[LI].Segment.Flags |= E3dSegmentSELECTED;
						}
						else
						{
							LCVs[LI].CVFlags &= ~E3dCV_ACTIVE;
							LCVs[LI].Segment.Flags &= ~E3dSegmentSELECTED;
						}
					}

					switch(LCV->Segment.Shape)
					{
						case E3dSplineLINEAR:
							XeToggleButton_SetState(LGUI->SegmentShapeLinearW, TRUE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeBezierW, FALSE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeCircularArcW, FALSE, FALSE);
							XeWidget_Unmanage(LGUI->SegmentArcSettingsMatrixW);
						break;

						case E3dSplineBEZIER:
							XeToggleButton_SetState(LGUI->SegmentShapeLinearW, FALSE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeBezierW, TRUE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeCircularArcW, FALSE, FALSE);
							XeWidget_Unmanage(LGUI->SegmentArcSettingsMatrixW);
						break;

						case E3dSplineARC:
							XeToggleButton_SetState(LGUI->SegmentShapeLinearW, FALSE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeBezierW, FALSE, FALSE);
							XeToggleButton_SetState(LGUI->SegmentShapeCircularArcW, TRUE, FALSE);
							XeScale_SetValue_f32(LGUI->SegmentCircularArcValueW, LCV->Segment.Radius, FALSE);
							XeToggleButton_SetState(LGUI->SegmentCircularArcAutoW, (LCV->Segment.Flags & E3dSegmentCircularArcAUTO) != 0, FALSE);

							{
								XeString  LXeStr;
								EBool     LRadiusMode;

								if(LCV->Segment.Flags & E3dSegmentCircularArcVAALUE_IS_RADIUS)
								{
									LRadiusMode = TRUE;
									LXeStr = XeString_NewVa(TRUE,  XeFONT, XeFontDEFAULT,  XeALIGNMENT, XeStringAlignCENTER,  XeTEXT, "Radius",  0);
									XeScale_SetValueAndRanges_Coord(LGUI->SegmentCircularArcValueW,  LCV->Segment.Radius,  -1000.0, 1000.0,  -100.0, 100.0,  FALSE);
								}
								else
								{
									LRadiusMode = FALSE;
									LXeStr = XeString_NewVa(TRUE,  XeFONT, XeFontDEFAULT,  XeALIGNMENT, XeStringAlignCENTER,  XeTEXT, "Degrees",  0);
									XeScale_SetValueAndRanges_Coord(LGUI->SegmentCircularArcValueW,  LCV->Segment.Radius,  -180.0, 180.0,  -180.0, 180.0,  FALSE);
								}

								Xe_SetValuesVa(LGUI->SegmentCircularArcModeW,  XeNLabelString, LXeStr,  XeNSet, LRadiusMode,  NULL);
								XeString_Free(LXeStr);
							}

							XeWidget_Manage(LGUI->SegmentArcSettingsMatrixW);
						break;
					}

					if(LCV->Segment.Shape == E3dSplineLINEAR) XeWidget_SetSensitive(LGUI->SegmentDrawStepsW, FALSE);  // Composite Widgets need this called (rather than just SetValues)
					else XeWidget_SetSensitive(LGUI->SegmentDrawStepsW, TRUE);  // Composite Widgets need this called (rather than just SetValues)

					switch(LCV->Constraint)
					{
					 case E3dBezierC0:
						 XeToggleButton_SetState(LGUI->C0TangentConstraintBW, TRUE, FALSE);
						 XeToggleButton_SetState(LGUI->C1TangentConstraintBW, FALSE, FALSE);
						 XeToggleButton_SetState(LGUI->G1TangentConstraintBW, FALSE, FALSE);
					 break;

					 case E3dBezierC1:
						 XeToggleButton_SetState(LGUI->C0TangentConstraintBW, FALSE, FALSE);
						 XeToggleButton_SetState(LGUI->C1TangentConstraintBW, TRUE, FALSE);
						 XeToggleButton_SetState(LGUI->G1TangentConstraintBW, FALSE, FALSE);
					 break;

					 case E3dBezierG1:
						 XeToggleButton_SetState(LGUI->C0TangentConstraintBW, FALSE, FALSE);
						 XeToggleButton_SetState(LGUI->C1TangentConstraintBW, FALSE, FALSE);
						 XeToggleButton_SetState(LGUI->G1TangentConstraintBW, TRUE, FALSE);
					 break;
					}
				}
			break;

			default:
				if(LSpline->CVs.Count > 0)
				{
					XeScale_SetValue(LGUI->SegmentDrawStepsW, LCV->Segment.NumOfLinSteps, FALSE);

					EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;for(;LI < LN;LI++)
					{
						if(LCVs + LI == LCV)
						{
							LCVs[LI].CVFlags |= E3dCV_ACTIVE;
							LCVs[LI].Segment.Flags |= E3dSegmentSELECTED;
						}
						else
						{
							LCVs[LI].CVFlags &= ~E3dCV_ACTIVE;
							LCVs[LI].Segment.Flags &= ~E3dSegmentSELECTED;
						}
					}
				}
			break;
		}
	}
	else XeScale_SetValue(LGUI->SegmentDrawStepsW, 0, FALSE);
}


static void _SetBezierSegmentType(E3dObjectPanel* PPanel, E3dSpline* PSpline, E3dSplineCV* PCV, const int PType)
{
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)PPanel, E3dSpline_class);if(LClassDrawer)
	{
		E3dSplineResourcesGUI*  LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

		PCV->Segment.Shape = PType;

		if(PCV->Segment.Shape == E3dSplineLINEAR) XeWidget_SetSensitive(LGUI->SegmentDrawStepsW, FALSE);  // Composite Widgets need this called (rather than just SetValues)
		else XeWidget_SetSensitive(LGUI->SegmentDrawStepsW, TRUE);  // Composite Widgets need this called (rather than just SetValues)

		EChangeFlags  LChanged = E3dSpline_FullUpdate(PSpline);
		_SplineUpdateStatsLabel(PPanel);

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


static void _SetTangentConstraintType(E3dObjectPanel* PPanel, E3dSpline* PSpline, E3dSplineCV* LCV, const int PType)
{
	LCV->Constraint = PType;

	EChangeFlags  LChanged = E3dSpline_FullUpdate(PSpline);
	_SplineUpdateStatsLabel(PPanel);

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

//================================
// GUI callbacks
//================================
static void _DCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*               LSpline = (E3dSpline*)LPanel->Object;if(LSpline == NULL) return;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dSpline_class);if(LClassDrawer)
	{
		E3dSplineResourcesGUI*  LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;if(LGUI)
		{
			if(LSpline->LODStep)
			{
				E3dGeometry*  LGeometry = LSpline->LODs[LSpline->LODStep - 1];
				if(EObject_IsSubClass(LGeometry, E3dSpline)) LSpline = (E3dSpline*)LGeometry;
				else LSpline = NULL;
			}

			if(LSpline)
			{
				E3dScene*  LScene = E3d_Scene;
				if(PW == LGUI->UniformStepsBW)
				{
					if(LSpline)
					{
						LSpline->UniformLinSteps = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

						EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
						_SplineUpdateStatsLabel(LPanel);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->UniformLinStepsW)
				{
					LSpline->UniformLinSteps = TRUE;
					XeToggleButton_SetState(LGUI->UniformStepsBW, TRUE, FALSE);

					LSpline->NumOfLinSteps = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

					EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
					_SplineUpdateStatsLabel(LPanel);

					E3dWindows_RedrawByChangeFlags(LScene, LChanged);
				}
				else if(PW == LGUI->CardinalTensionW)
				{
					LSpline->Tension = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

					EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
					_SplineUpdateStatsLabel(LPanel);

					E3dWindows_RedrawByChangeFlags(LScene, LChanged);
				}
				else if(PW == LGUI->ClosedBW)
				{
					LSpline->Closed = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

					EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);

					_SplineUpdateStatsLabel(LPanel);

					E3dWindows_RedrawByChangeFlags(LScene, LChanged);
				}
				else if(PW == LGUI->CVIndexW)
				{
					LGUI->CVIndex = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

					_SplineSegmentUpdateGUI(LPanel);
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgSELECTION);
				}
				else if(PW == LGUI->SegmentVisibleW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LCV->Segment.Flags |= E3dSegmentVISIBLE;
						else LCV->Segment.Flags &= ~E3dSegmentVISIBLE;

						EChangeFlags  LChanged = E3dChgSELECTION;
						LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->ArcLengthTesselateW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LCV->Segment.Flags |= E3dSegmentARC_LENGTH_TESSELATE;
						else LCV->Segment.Flags &= ~E3dSegmentARC_LENGTH_TESSELATE;

						E3dSpline_CreateLinearSegments(LSpline);

						EChangeFlags  LChanged = E3dChgSELECTION;
						LChanged |= E3dSpline_CallOutputs(LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY, FALSE);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SegmentDrawStepsW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						LSpline->UniformLinSteps = FALSE;
						XeToggleButton_SetState(LGUI->UniformStepsBW, FALSE, FALSE);

						LCV->Segment.NumOfLinSteps = ((XeScaleCallbackStruct*)PCallData)->Value.i32;

						EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
						_SplineUpdateStatsLabel(LPanel);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SegmentShapeLinearW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;
						_SetBezierSegmentType(LPanel, LSpline, LCV, E3dSplineLINEAR);
					}
				}
				else if(PW == LGUI->SegmentShapeBezierW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
						{
							E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;
							_SetBezierSegmentType(LPanel, LSpline, LCV, E3dSplineBEZIER);
						}
					}
				}
				else if(PW == LGUI->SegmentShapeCircularArcW)
				{
					if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;

						_SetBezierSegmentType(LPanel, LSpline, LCV, E3dSplineARC);
					}
				}
				else if(PW == LGUI->SegmentCircularArcModeW)
				{
					if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
						if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LCV->Segment.Flags |= E3dSegmentCircularArcVAALUE_IS_RADIUS;
						else LCV->Segment.Flags &= ~E3dSegmentCircularArcVAALUE_IS_RADIUS;

						EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
						_SplineUpdateStatsLabel(LPanel);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SegmentCircularArcValueW)
				{
					if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
						LCV->Segment.Radius = ((XeScaleCallbackStruct*)PCallData)->Value.f32;

						EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
						_SplineUpdateStatsLabel(LPanel);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SegmentCircularArcAutoW)
				{
					if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
						if(((XeToggleButtonCallbackStruct*)PCallData)->Set) LCV->Segment.Flags |= E3dSegmentCircularArcAUTO;
						else LCV->Segment.Flags &= ~E3dSegmentCircularArcAUTO;

						EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
						_SplineUpdateStatsLabel(LPanel);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->C0TangentConstraintBW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						_SetTangentConstraintType(LPanel, LSpline, LCV, E3dBezierC0);
					}
				}
				else if(PW == LGUI->C1TangentConstraintBW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						_SetTangentConstraintType(LPanel, LSpline, LCV, E3dBezierC1);
					}
				}
				else if(PW == LGUI->G1TangentConstraintBW)
				{
					if(LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
					{
						E3dSplineCV*  LCV = LSpline->CVs.Items + LGUI->CVIndex;

						_SetTangentConstraintType(LPanel, LSpline, LCV, E3dBezierG1);
					}
				}
				else if(PW == LGUI->SplineLinearW)
				{
					if(LSpline->SplineType != E3dSplineLINEAR)
					{
						LSpline->SplineType = E3dSplineLINEAR;
						{ EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.Shape = LSpline->SplineType; }

						EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass((E3dObject*)LSpline);

// This ensures that any Faces using this Spline are updated
//
						LChanged |= E3dSpline_Changed(LSpline, E3dChgSplineSHAPE,  NULL);
						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL,  LScene);

						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SplineBezierW)
				{
					if(LSpline->SplineType != E3dSplineBEZIER)
					{
						LSpline->SplineType = E3dSplineBEZIER;
						{ EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.Shape = LSpline->SplineType; }

						EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass((E3dObject*)LSpline);

// The Prev and Next fields of CVs may have not been initialized, so set them to Position.
//
/*
						{
							EIndex  LI = 0;const EIndex  LN = LSpline->CVs_Count;E3dSplineCV*  LCV = LSpline->CVs;for(;LI < LN;LI++, LCV++)
							{
								LCV->Previous = LCV->Next = LCV->Position;
							}
						}
*/

// This ensures that any Faces using this Spline are updated
//
						LChanged |= E3dSpline_Changed(LSpline, E3dChgSplineSHAPE,  NULL);

						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL,  LScene);
						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SplineBSplineW)
				{
					if(LSpline->SplineType != E3dSplineBSPLINE)
					{
						LSpline->SplineType = E3dSplineBSPLINE;
						{ EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.Shape = LSpline->SplineType; }

						EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass((E3dObject*)LSpline);

// This ensures that any Faces using this Spline are updated
//
						LChanged |= E3dSpline_Changed(LSpline, E3dChgSplineSHAPE,  NULL);

						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL,  LScene);
						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->SplineCardinalW)
				{
					if(LSpline->SplineType != E3dSplineCARDINAL)
					{
						LSpline->SplineType = E3dSplineCARDINAL;
						{ EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.Shape = LSpline->SplineType; }

						EChangeFlags  LChanged = E3dObject_GetChangeFlagsByClass((E3dObject*)LSpline);

// This ensures that any Faces using this Spline are updated
//
						LChanged |= E3dSpline_Changed(LSpline, E3dChgSplineSHAPE,  NULL);

						LChanged |= E3dObject_UpdateForDisplay((E3dObject*)LSpline,  E3dUpdateALL, E3dCallALL, LScene);
						E3dWindows_RedrawByChangeFlags(LScene, LChanged);
					}
				}
				else if(PW == LGUI->VectorDrawScaleW)
				{
					const float  LV = ((XeScaleCallbackStruct*)PCallData)->Value.f32;
					LSpline->VectorDrawScale = LV;
					E3dSpline_GLBufferUpdate(LSpline, FALSE, TRUE);

					if(LPanel->ApplyChangesToAllSelected)
					{
						EListAI_s(, E3dObjectPtr, LSelectedObjects);const EIndex  LN = E3dScene_GetObjects(LScene, E3dItemSPLINE, E3dSELECTED, FALSE,  &LSelectedObjects);if(LN > 0)
						{
							EIndex  LI = 0;for(;LI < LN;LI++)
							{
								E3dSpline*  LTSpline = (E3dSpline*)LSelectedObjects.Items[LI];
								if(LTSpline != LSpline) { LTSpline->VectorDrawScale = LV;E3dSpline_GLBufferUpdate(LTSpline, FALSE, TRUE); }
							}
							EList_FreeA_s(LSelectedObjects);
						}
					}
					E3dWindows_RedrawByChangeFlags(LScene, E3dChgWIREFRAME);
				}
			}
		}
	}
}


//================================================
// Shift CVs backward
//================================================
static void _DCB_CVsBack(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*       LSpline = (E3dSpline*)LPanel->Object;

	if(LSpline->LODStep)
	{
		if(EObject_IsSubClass(LSpline->LODs[LSpline->LODStep - 1], E3dSpline)) LSpline = (E3dSpline*)(LSpline->LODs[LSpline->LODStep - 1]);
		else LSpline = NULL;
	}

	if(LSpline->CVs.Count > 0)
	{
		E3dSplineCV  LCV;memcpy(&LCV, LSpline->CVs.Items, sizeof(E3dSplineCV));

		memmove(LSpline->CVs.Items, LSpline->CVs.Items + 1, sizeof(E3dSplineCV) * (LSpline->CVs.Count - 1));
		memcpy(LSpline->CVs.Items + LSpline->CVs.Count - 1, &LCV, sizeof(E3dSplineCV));

		EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
		_SplineUpdatePanel(LPanel);

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


//================================================
// Shift CVs forward
//================================================
static void _DCB_CVsForward(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*       LSpline = (E3dSpline*)LPanel->Object;

	if(LSpline->LODStep)
	{
		if(EObject_IsSubClass(LSpline->LODs[LSpline->LODStep - 1], E3dSpline)) LSpline = (E3dSpline*)(LSpline->LODs[LSpline->LODStep - 1]);
		else LSpline = NULL;
	}

	if(LSpline->CVs.Count > 0)
	{
		E3dSplineCV   LCV;memcpy(&LCV, LSpline->CVs.Items + LSpline->CVs.Count - 1, sizeof(E3dSplineCV));

		memmove(LSpline->CVs.Items + 1, LSpline->CVs.Items, sizeof(E3dSplineCV) * (LSpline->CVs.Count - 1));
		memcpy(LSpline->CVs.Items, &LCV, sizeof(E3dSplineCV));

		EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
		_SplineUpdatePanel(LPanel);

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


static void _DCB_SetSegmentShapeForAll(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*               LSpline = (E3dSpline*)LPanel->Object;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dSpline_class);
	E3dSplineResourcesGUI*   LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

	if(LSpline->LODStep)
	{
		if(EObject_IsSubClass(LSpline->LODs[LSpline->LODStep - 1], E3dSpline)) LSpline = (E3dSpline*)(LSpline->LODs[LSpline->LODStep - 1]);
		else LSpline = NULL;
	}

	if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
	{
		E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
		const int     LSegmentType = LCV->Segment.Shape;

		EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.Shape = LSegmentType;

		EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
		_SplineUpdateStatsLabel(LPanel);

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


static void _DCB_SetConstraintTypeForAll(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*               LSpline = (E3dSpline*)LPanel->Object;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dSpline_class);
	E3dSplineResourcesGUI*   LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

	if(LSpline->LODStep)
	{
		if(EObject_IsSubClass(LSpline->LODs[LSpline->LODStep - 1], E3dSpline)) LSpline = (E3dSpline*)(LSpline->LODs[LSpline->LODStep - 1]);
		else LSpline = NULL;
	}

	if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
	{
		E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
		const int     LConstraintType = LCV->Constraint;

		EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Constraint = LConstraintType;

		EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
		_SplineUpdateStatsLabel(LPanel);

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


static void _DCB_SetDrawingStepsForAll(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*               LSpline = (E3dSpline*)LPanel->Object;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dSpline_class);
	E3dSplineResourcesGUI*   LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

	if(LSpline->LODStep)
	{
		if(EObject_IsSubClass(LSpline->LODs[LSpline->LODStep - 1], E3dSpline)) LSpline = (E3dSpline*)(LSpline->LODs[LSpline->LODStep - 1]);
		else LSpline = NULL;
	}

	if(LSpline && LSpline->CVs.Count > 0 && LGUI->CVIndex >= 0 && LGUI->CVIndex < LSpline->CVs.Count)
	{
		E3dSplineCV*	LCV = LSpline->CVs.Items + LGUI->CVIndex;
		const int     LSegmentNumOfLinSteps = LCV->Segment.NumOfLinSteps;

		EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++) LCV->Segment.NumOfLinSteps = LSegmentNumOfLinSteps;

		EChangeFlags  LChanged = E3dSpline_FullUpdate(LSpline);
		_SplineUpdateStatsLabel(LPanel);

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


//========================================
// Update Spline dialog-box
//========================================
static void _SplineUpdatePanel(E3dObjectPanel* LPanel)
{
	E3dSpline*               LSpline = (E3dSpline*)LPanel->Object;
	XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dSpline_class);if(LClassDrawer == NULL) return;
	E3dSplineResourcesGUI*   LGUI = (E3dSplineResourcesGUI*)LClassDrawer->ResourcesGUI;

	_SplineUpdateStatsLabel(LPanel);

	XeToggleButton_SetState(LGUI->UniformStepsBW, LSpline->UniformLinSteps, FALSE);
	XeToggleButton_SetState(LGUI->ClosedBW, LSpline->Closed, FALSE);
	XeScale_SetValue(LGUI->UniformLinStepsW, LSpline->NumOfLinSteps, FALSE);

	_SplineSegmentUpdateGUI(LPanel);

	EBool  LSplineHasNormals = FALSE;

	{
		EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;const E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++)
		{
			if(LCV->Normal.X != 0.0 || LCV->Normal.Y != 0.0 || LCV->Normal.Z != 0.0) { LSplineHasNormals = TRUE;break; }
		}
	}

	{
		if(LSplineHasNormals) XeWidget_Manage(LGUI->VectorDrawScaleW);else XeWidget_Unmanage(LGUI->VectorDrawScaleW);

		XeChangeManage   LWidgetsToManageUnmanage[16];XeChangeManage*  LChild = LWidgetsToManageUnmanage;
		switch(LSpline->SplineType)
		{
			case E3dSplineLINEAR:
				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->UniformStepsBW;            LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->UniformLinStepsW;          LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->CardinalTensionW;          LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->SegmentDrawStepsMatrixW;   LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->SegmentShapeMatrixW;       LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->CVTangentConstraintMatrixW;LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				XeWidget_Unmanage(LGUI->ArcLengthTesselateW);
			break;

			case E3dSplineBEZIER:
				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->UniformStepsBW;            LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->UniformLinStepsW;          LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->CardinalTensionW;          LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->SegmentDrawStepsMatrixW;   LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->SegmentShapeMatrixW;       LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->CVTangentConstraintMatrixW;LChild->Manage = TRUE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				XeWidget_Manage(LGUI->ArcLengthTesselateW);
			break;

			case E3dSplineCARDINAL:
				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->UniformStepsBW;            LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->UniformLinStepsW;               LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->CardinalTensionW;          LChild->Manage = TRUE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->SegmentDrawStepsMatrixW;   LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->SegmentShapeMatrixW;       LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->CVTangentConstraintMatrixW;LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				XeWidget_Manage(LGUI->ArcLengthTesselateW);
			break;

			default:
				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->UniformStepsBW;            LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->UniformLinStepsW;               LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->CardinalTensionW;          LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				LChild = LWidgetsToManageUnmanage;
				LChild->W = LGUI->SegmentDrawStepsMatrixW;   LChild->Manage = TRUE;LChild++;
				LChild->W = LGUI->SegmentShapeMatrixW;       LChild->Manage = FALSE;LChild++;
				LChild->W = LGUI->CVTangentConstraintMatrixW;LChild->Manage = FALSE;LChild++;
				XeWidgets_ChangeManage(LWidgetsToManageUnmanage, LChild - LWidgetsToManageUnmanage);

				XeWidget_Manage(LGUI->ArcLengthTesselateW);
			break;
		}
	}
}


//========================================
// 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;E3dSpline*  LSpline = E_DynamicCast(LObject, E3dSpline);if(LSpline)
	{
		XeComponentClassDrawer*  LDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LObjectPanel, E3dSpline_class);if(LDrawer)
		{
			E3dSplineResourcesGUI*  LGUI = (E3dSplineResourcesGUI*)(LDrawer->ResourcesGUI);

			const EBool  LColorWasInactive = LSpline->RGBAfColor.A < 0.0 ? TRUE : FALSE;

			LSpline->RGBAfColor = 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);
				LSpline->RGBAfColor.A = 1.0;
				EcColorPanel_SetRGBAfColor(LGUI->ColorPanel, 0, &LSpline->RGBAfColor);
			}

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

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

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


//========================================
// Spline dialog callback handler
//========================================
static void _DCB_ColorEnable(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dObjectPanel*  LPanel = (E3dObjectPanel*)PClientData;
	E3dSpline*       LSpline = (E3dSpline*)LPanel->Object;

	LSpline->UseCustomColor = ((XeToggleButtonCallbackStruct*)PCallData)->Set;

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


//================================================================
// GUIFunc for E3dSpline
//================================================================
static void _SplineGUI(ECoreObject* PObject, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	E3dObjectPanel*          LPanel = (E3dObjectPanel*)PPanel;
	E3dSpline*               LSpline = (E3dSpline*)PObject;
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dSplineResourcesGUI*   LGUI;

	switch(PAction)
	{
		case E_UPDATE:
			{
				EIndex  LI = 0;const EIndex  LN = LSpline->CVs.Count;const E3dSplineCV*  LCV = LSpline->CVs.Items;for(;LI < LN;LI++, LCV++)
				{
					if(LCV->Segment.Flags & E3dSegmentSELECTED)
					{
						LPanel->PointIndex = LI;
						break;
					}
				}
			}
			if(LDrawer->ResourcesGUI)
			{
				LGUI = (E3dSplineResourcesGUI*)LDrawer->ResourcesGUI;

				if(LPanel->PointIndex >= 0) LGUI->CVIndex = LPanel->PointIndex;
				else
				{
// LPanel->PointIndex == -1 means, "don't set it", so just do a range check here
//
					if(LGUI->CVIndex >= LSpline->CVs.Count) LGUI->CVIndex = LSpline->CVs.Count - 1;
				}

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

//assert(0);
				XeScale_SetValue_f32(LGUI->CardinalTensionW, LSpline->Tension, FALSE);
				XeScale_SetValueAndRange(LGUI->CVIndexW,  LGUI->CVIndex,  0, LSpline->CVs.Count - 1,  FALSE);
				_SplineUpdatePanel(LPanel);

				XeToggleButton_SetState(LGUI->SplineLinearW,   LSpline->SplineType == E3dSplineLINEAR,   FALSE);
				XeToggleButton_SetState(LGUI->SplineBezierW,   LSpline->SplineType == E3dSplineBEZIER,   FALSE);
				XeToggleButton_SetState(LGUI->SplineCardinalW, LSpline->SplineType == E3dSplineCARDINAL, FALSE);
				XeToggleButton_SetState(LGUI->SplineBSplineW,  LSpline->SplineType == E3dSplineBSPLINE,  FALSE);

		    XeScale_SetValue_f32(LGUI->VectorDrawScaleW,  LSpline->VectorDrawScale,  FALSE);

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

				EcColorPanel_SetRGBAfColor(LGUI->ColorPanel, LGUI->ColorPanel->DrawColor0, &LSpline->RGBAfColor);
				XeToggleButton_SetState(LGUI->UseCustomColorW, LSpline->UseCustomColor, 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
				EcRGBAiColor  LButtonRGBAiColor, LButtonActiveRGBAiColor, LButtonArmRGBAiColor;
				EArg          Args[256];int  ArgCnt;

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

				if(LPanel->PointIndex < 0) LGUI->CVIndex = 0;
				else LGUI->CVIndex = LPanel->PointIndex;

				const XeCoordinate  LSpacingLeft = XeDrawer_LeftIndent, LSpacingRight = 4;

				LTraverseN = 0;

				{
// Spline types
//
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNMarginRight, 8);
					EXeSetArg(XeNSpacingX, 8);
					EXeSetArg(XeNAdjustEntrySizesX, FALSE);
					EXeSetArg(XeNAdjustEntrySizesY, FALSE);
					XeWidget  LRadioMatrixW = XeWidget_ManagedNew("MatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					XeWidget_ManagedNew("Type", XeLabelWidget_class, LRadioMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LSpline->SplineType == E3dSplineLINEAR);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->SplineLinearW = LW = XeWidget_ManagedNew("Linear", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LSpline->SplineType == E3dSplineBEZIER);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->SplineBezierW = LW = XeWidget_ManagedNew("Bezier", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LSpline->SplineType == E3dSplineBSPLINE);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->SplineBSplineW = LW = XeWidget_ManagedNew("B-spline", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LSpline->SplineType == E3dSplineCARDINAL);
					EXeSetArg(XeNResizableY, FALSE);
					LGUI->SplineCardinalW = LW = XeWidget_ManagedNew("Cardinal", XeToggleButton_class, LRadioMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);
				}

				EXeStart;
				EXeSetArg(XeNSpacingLeft, LSpacingLeft);
				EXeSetArg(XeNShadowThickness, 0);
				EXeSetArg(XeNHighlightThickness, 0);
				EXeSetArg(XeNSpacingTop, 4);EXeSetArg(XeNSpacingBottom, 2);
				EXeSetArg(XeNSet, LSpline->Closed);
				LGUI->ClosedBW = LW = XeWidget_ManagedNew("Closed", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 3);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSpacingX, 6);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;

					XeString  LXeStr = XeString_NewVa(TRUE,
					 XeFONT, XeFontDEFAULT,
					 XeALIGNMENT, XeStringAlignLEFT,
					 XeTEXT, "CVs / segments / length:  ",
					 0);

					if(LXeStr) { EXeSetArg(XeNLabelString, LXeStr); }

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

					EXeStart;
					LGUI->StatsValLW = XeWidget_ManagedNew("", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);
				}

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

					EXeStart;
					EXeSetArg(XeNFontList, Xe_LabelFontList);
					XeWidget_ManagedNew("Shift CVs", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNSizeX, 24);EXeSetArg(XeNSizeY, 24);
					EXeSetArg(XeNArrowDirection, XeLEFT);
					EXeSetArg(XeNArrowBehavior, XeARROW_ALWAYS_IN);
					LW = XeWidget_ManagedNew(" ", XeArrowButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_CVsBack, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNSizeX, 24);EXeSetArg(XeNSizeY, 24);
					EXeSetArg(XeNArrowDirection, XeRIGHT);
					EXeSetArg(XeNArrowBehavior, XeARROW_ALWAYS_IN);
					LW = XeWidget_ManagedNew(" ", XeArrowButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_CVsForward, (EPointer)PPanel);
				}


				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 3);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSpacingX, 4);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LSpline->UniformLinSteps);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					LGUI->UniformStepsBW = LW = XeWidget_ManagedNew("Uniform drawing steps", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 5);
					EXeSetArg(XeNMinimum, 2);EXeSetArg(XeNMaximum, 1000);EXeSetArg(XeNValue, LSpline->NumOfLinSteps);
					EXeSetArg(XeNScaleMinimum, 2);EXeSetArg(XeNScaleMaximum, 32);
					LTraverseWs[LTraverseN++] = LGUI->UniformLinStepsW = LW = XeWidget_ManagedNew(NULL, XeScaleWidget_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);
				}

				LIndentN = 0;

				EXeStart;
				EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
				EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 4);
				LIndentWs[LIndentN++] = LTraverseWs[LTraverseN++] = LGUI->CardinalTensionW = LW = XeScale_ManagedNew_f32(LTopMatrixW, "Tension",  Args, ArgCnt);
				XeScale_SetValueAndRange_f32(LW,  LSpline->Tension,  -2.0, 1.0,  FALSE);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

				EXeStart;
				EXeSetArg(XeNShadowThickness, 1);EXeSetArg(XeNShadowType, XeShadowIN);
				EXeSetArg(XeNOrientation, XeVERTICAL);
				EXeSetArg(XeNSpacingX, 4);EXeSetArg(XeNSpacingY, 0);
				LGUI->SegmentSettingsMatrixW = XeWidget_ManagedNew("_SplineSegmentSettingsMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

				EXeStart;
				EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
				EXeSetArg(XeNSpacingTop, 4);
				EXeSetArg(XeNTextFieldDigits, 4);
				EXeSetArg(XeNMinimum, 0);EXeSetArg(XeNMaximum, LSpline->CVs.Count - 1);EXeSetArg(XeNValue, LGUI->CVIndex);
				EXeSetArg(XeNResizableY, FALSE);
				EXeSetArg(XeNCreateUpButton, TRUE);
				EXeSetArg(XeNCreateDownButton, TRUE);
				LGUI->CVIndexW = LTraverseWs[LTraverseN++] = LW = XeWidget_ManagedNew("CV/segment", XeScaleWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);
				XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNMarginSizeY, 3);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSpacingX, 8);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					XeToolTip8("Segment is visible / hidden. If a segment is not visible, tools like Extrude will not create a corresponding side.");
					LGUI->SegmentVisibleW = LW = XeWidget_ManagedNew("Segment visible", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					XeToolTip8("Create equal-length linear segments to approximate this segment.");
					LGUI->ArcLengthTesselateW = LW = XeWidget_ManagedNew("Proportional", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);
				}

				Ec_GainRGBAi(&Xe_BackgroundRGBAiColor, 1.0, &LButtonRGBAiColor);
				Ec_GainRGBAi(&Xe_BackgroundRGBAiColor, XeActiveColorFactor, &LButtonActiveRGBAiColor);
				Ec_GainRGBAi(&Xe_BackgroundRGBAiColor, XeArmColorFactor, &LButtonArmRGBAiColor);

				LIndentN = 0;

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);
					EXeSetArg(XeNSpacingTop, 4);
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingX, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);
					LGUI->SegmentShapeMatrixW = LMatrixW;

					EXeStart;
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					LIndentWs[LIndentN++] = LGUI->SegTypeLabelW = XeWidget_ManagedNew("Segment shape", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNSpacingLeft, 8);
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);
					EXeSetImages3(_Image_SplSegmentLin);
					XeToolTip8("Linear segment");
					LGUI->SegmentShapeLinearW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
//				EXeSetArg(XeNIndicatorSize, 18);
//				EXeSetArg(XeNButtonType, XeButtonTypeLED);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);
					EXeSetImages3(_Image_SplSegmentCrv);
					XeToolTip8("Bezier-curve segment");
					LGUI->SegmentShapeBezierW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);
					EXeSetImages3(_Image_SplSegmentArc);
					XeToolTip8("Circular arc segment");
					LGUI->SegmentShapeCircularArcW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeY, -1);EXeSetArg(XeNMarginRight, 2);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Set this shape for all segments.");
					LW = XeWidget_ManagedNew("All", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_SetSegmentShapeForAll, (EPointer)PPanel);

					{
						EXeStart;
						EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight + 2);
						EXeSetArg(XeNSpacingTop, 2);
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNSpacingX, 0);
						EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
						XeWidget  LHMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);
						LGUI->SegmentArcSettingsMatrixW = LHMatrixW;

						EXeStart;
						EXeSetArg(XeNShadowThickness, 2);EXeSetArg(XeNMarginSizeX, 4);
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNIndicatorOn, FALSE);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
						EXeSetArg(XeNSelfResizePolicyX, XeGROW);
						EXeSetArg(XeNAlignmentX, XeAlignCENTER);
						XeToolTip8("Switch between specifying the radius of the arc, or the length in degrees.\nThe arc length has the benefit that it's scale-independent, but sometimes\nyou need to specify an exact radius (e.g. for an engineering drawing).");
						LGUI->SegmentCircularArcModeW = LW = XeWidget_ManagedNew("ModeW", XeToggleButton_class, LHMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNSpacingLeft, 4);
						EXeSetArg(XeNTextFieldDigits, 7);EXeSetArg(XeNDecimalPoints, 5);
						EXeSetArg(XeNIncrement, 10);
						EXeSetArg(XeNCreateLabel, FALSE);EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("The radius or length of the arc. A negative value means that the arc curves in the opposite\ndirection (the center is on the opposite side of the Spline segment).");
						LTraverseWs[LTraverseN++] = LGUI->SegmentCircularArcValueW = LW = XeScale_New_Coord(LHMatrixW, "ValueW",  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNSpacingLeft, 2);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNResizableY, FALSE);
						XeToolTip8("Compute the radius or arc length automatically from the directions of the neighboring segments.");
						LGUI->SegmentCircularArcAutoW = LW = XeWidget_ManagedNew("Auto", XeToggleButton_class, LHMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);
					}
				}

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingX, 7);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);
					LGUI->CVTangentConstraintMatrixW = LMatrixW;

					EXeStart;
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					LIndentWs[LIndentN++] = XeWidget_ManagedNew("Continuity constraint", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);EXeSetImages3(_Image_TgC0);
					XeToolTip8("\"C0\" constraint:\nTangents may have different lengths and directions.");
					LGUI->C0TangentConstraintBW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);
					EXeSetImages3(_Image_TgG1);
					XeToolTip8("\"G1\" constraint\nTangents may have different lengths, but the directions match.");
					LGUI->G1TangentConstraintBW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNIndicatorOn, FALSE);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					EXeSetArg(XeNSet, FALSE);
					EXeSetArg(XeNResizableX, FALSE);
					EXeSetArg(XeNActivateOnArm, TRUE);
					EXeSetArg(XeNLabelType, XeIMAGE);
					EXeSetImages3(_Image_TgC1);
					XeToolTip8("\"C1\" constraint\nTangent directions and lenghts match.");
					LGUI->C1TangentConstraintBW = LW = XeWidget_ManagedNew("", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeY, -1);EXeSetArg(XeNMarginRight, 2);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Set this for all CVs.");
					LW = XeWidget_ManagedNew("All", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_SetConstraintTypeForAll, (EPointer)PPanel);
				}

				XeWidgets_IndentFromLeft(LIndentWs, LIndentN, 0);

				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNRadioBehavior, TRUE);
					EXeSetArg(XeNOrientation, XeHORIZONTAL);
					EXeSetArg(XeNSpacingX, 2);
					EXeSetArg(XeNMarginSizeX, 0);EXeSetArg(XeNMarginSizeY, 0);
					XeWidget  LMatrixW = XeWidget_ManagedNew("", XeMatrixWidget_class, LGUI->SegmentSettingsMatrixW,  Args, ArgCnt);
					LGUI->SegmentDrawStepsMatrixW = LMatrixW;

					EXeStart;
					EXeSetArg(XeNTextFieldDigits, 4);
					EXeSetArg(XeNMinimum, 2);EXeSetArg(XeNMaximum, 1000);EXeSetArg(XeNValue, LSpline->NumOfLinSteps);
					EXeSetArg(XeNScaleMinimum, 2);EXeSetArg(XeNScaleMaximum, 32);
					EXeSetArg(XeNMarginBottom, 5);
					LTraverseWs[LTraverseN++] = LGUI->SegmentDrawStepsW = LW = XeWidget_ManagedNew("Drawing steps", XeScaleWidget_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNMarginSizeY, -1);EXeSetArg(XeNMarginRight, 2);
					EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Set this for all CVs / segments.");
					LW = XeWidget_ManagedNew("All", XePushButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNActivateCallback, _DCB_SetDrawingStepsForAll, (EPointer)PPanel);
				}

// Color Drawer
//
				{
					EXeStart;
					EXeSetArg(XeNSpacingLeft, LSpacingLeft);EXeSetArg(XeNSpacingRight, LSpacingRight);
					EXeSetArg(XeNSpacingTop, 4);EXeSetArg(XeNSpacingBottom, 4);
					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, "Color", 0, 0,  NULL);

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

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSpacingLeft, 4);EXeSetArg(XeNMarginSizeX, 0);
					EXeSetArg(XeNSet, LSpline->UseCustomColor);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Use the color specified here, for the drawing this Spline.\nWhen this is OFF, use the default wireframe color.");
					LGUI->UseCustomColorW = LW = XeWidget_ManagedNew("Enable", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB_ColorEnable, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNSpacingLeft, 12);
					EXeSetArg(XeNTextFieldDigits, 5);EXeSetArg(XeNDecimalPoints, 3);
					EXeSetArg(XeNIncrement, 10);
					EXeSetArg(XeNResizableY, FALSE);
					XeToolTip8("Scale the lenght of vectors such as normals and tangents with this value.");
					LTraverseWs[LTraverseN++] = LGUI->VectorDrawScaleW = LW = XeScale_New_Coord(LMatrixW, "Vector scale",  Args, ArgCnt);
					XeScale_SetValueAndRanges_Coord(LW,  LSpline->VectorDrawScale,  0.001, 1000.0,  0.001, 100.0,  FALSE);
					XeAddCallback(LW, XeNValueChangedCallback, _DCB, (EPointer)PPanel);

					EcColorPanel*  LColorPanel = EcColorPanel_NewVa(LTopMatrixW, "Color",  XeNColorChangedCallback, _CB_ColorChanged,  XeNColorChangedCallbackClientData, LPanel,
					 XeNSpacingLeft, LSpacingLeft,  XeNSpacingRight, LSpacingRight,  XeNSpacingBottom, 4,
					 XeNColorComponentType, EResFLOAT32,  XeNAlphaOn, TRUE,  XeNRGBAfColor, &LSpline->RGBAfColor,  XeNIntensityOn, TRUE,  XeNCreateColorWheel, TRUE,  NULL);

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

				XeTraversal_SetRing(LTraverseWs, LTraverseN);
			}

			_SplineUpdatePanel(LPanel);

// If it's a contour Spline of the Face that's attributes are being shown, don't display the "Geometry" subclass folder
//
			if(LGUI)
			{
				if(LSpline->Faces)
				{
					EBool  LHideGeometryPanel = FALSE;

					EIndex  LPI = 0;for(;LPI < E3dp_ObjectPanels_Count;LPI++)
					{
						E3dObjectPanel*  LTPanel = E3dp_ObjectPanels[LPI];
						EIndex  LI = 0;const EIndex  LN = LSpline->Faces_Count;for(;LI < LN;LI++)
						{
							E3dObject*  LFace = LSpline->Faces[LI];
							if(LTPanel->Object == LFace) { LHideGeometryPanel = TRUE;goto DoneChecking; }
						}
					}


DoneChecking:
					if(LHideGeometryPanel)
					{
						XeComponentClassDrawer*  LClassDrawer = XeInfoPanel_GetFolderByClass((XeInfoPanel*)LPanel, E3dGeometry_class);if(LClassDrawer)
						{
							LClassDrawer->Show = FALSE;
						}
					}
				}
			}
		break;

		case E_FREE:
			if(LDrawer->ResourcesGUI)
			{
				LGUI = (E3dSplineResourcesGUI*)LDrawer->ResourcesGUI;
				if(LGUI->ColorPanel) { EcColorPanel_Free(LGUI->ColorPanel);LGUI->ColorPanel = NULL; }
			}
		break;
	}
}


//================================================
// Constructor
//================================================
static int _EdgesToSplines_Init(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)PInfo;

	EList_Init(LThis->SrcObjects);
	EList_Init(LThis->SrcModels);
	LThis->SrcActiveFlags = NULL;

	LThis->DoHiddenEdges = FALSE;
	LThis->DoUnselectedEdges = TRUE;
	LThis->MergeContiguous = TRUE;
	LThis->SplineNormalsMode = 1;

	EList_InitA(LThis->Splines);

// Initialize InputInterface for _InputChanged()
//
	{
		EInterface*  LInput = &LThis->InputInterface;EInterface_Init(LInput);
		LInput->InputChanged    = _EdgesToSplines_InputChanged;
		LInput->Object          = (ECoreObject*)PNode;
		LInput->ObjectComponent = (ECoreObject*)PInfo;
	}

	return(E_SUCCESS);
}

//========================================
// Copy-constructor
//========================================
static int _EdgesToSplines_Copy(const E3dNode* PSrcNode, const E3dNodeComponent* PSrcComponent,  E3dNode* PDstNode, E3dNodeComponent* PDstComponent)
{
	E3dEdgesToSplines*  LSInfo = (E3dEdgesToSplines*)PSrcComponent;
	E3dEdgesToSplines*  LDInfo = (E3dEdgesToSplines*)PDstComponent;

	_EdgesToSplines_Init(PDstNode, PDstComponent);

	if(LSInfo->SrcObjects_Count)
	{
		ENewN(LSInfo->SrcObjects_Count, LDInfo->SrcActiveFlags);
		const EBool  LHasSrcActiveFlags = LSInfo->SrcActiveFlags != NULL;

		EIndex  LI = 0;const EIndex  LN = LSInfo->SrcObjects_Count;for(;LI < LN;LI++)
		{
			EList_ObjectAdd(LDInfo->SrcObjects, LSInfo->SrcObjects[LI]);
			if(LHasSrcActiveFlags) LDInfo->SrcActiveFlags[LI] = LSInfo->SrcActiveFlags[LI];
			else LDInfo->SrcActiveFlags[LI] = TRUE;
		}
	}

	{
		EIndex  LI = 0;const EIndex  LN = LSInfo->SrcModels_Count;for(;LI < LN;LI++)
		{
			EList_ObjectAdd(LDInfo->SrcModels, LSInfo->SrcModels[LI]);
		}
	}

#define _CopyField(mField)  LDInfo->mField = LSInfo->mField
	_CopyField(DoHiddenEdges);
	_CopyField(DoUnselectedEdges);
	_CopyField(SplineNormalsMode);
	_CopyField(MergeContiguous);
#undef _CopyField

	return(E_SUCCESS);
}

//================================================
// Destructor
//================================================
static int _EdgesToSplines_Free(E3dNode* PNode, E3dNodeComponent* PInfo)
{
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)PInfo;

	{
		EIndex  LI = 0;const EIndex  LN = LThis->SrcObjects_Count;for(;LI < LN;LI++)
		{
			if(LThis->SrcObjects[LI]) E3dBaseObject_RemoveOutput((E3dBaseObject*)LThis->SrcObjects[LI], &LThis->InputInterface);
		}
	}
	EList_ObjectsFree(LThis->SrcObjects);

	{
		EIndex  LI = 0;const EIndex  LN = LThis->SrcModels_Count;for(;LI < LN;LI++)
		{
			if(LThis->SrcModels[LI]) E3dBaseObject_RemoveOutput((E3dBaseObject*)LThis->SrcModels[LI], &LThis->InputInterface);
		}
	}
	EList_ObjectsFree(LThis->SrcModels);

	EFreeChkClr(LThis->SrcActiveFlags);

	EList_ObjectsFreeA(LThis->Splines);

	return(E_SUCCESS);
}

static EChangeFlags _EdgesToSplines_Update(E3dNode* PChangedBranchRootNode, E3dNode* PNode, E3dNodeComponent* PInfo, const EUpdateFlags PUpdateFlags, const EBool PCheckOnly)
{
	E3dEdgesToSplines*  LInfo = (E3dEdgesToSplines*)PInfo;

	if(PCheckOnly)
	{
		if(LInfo->SrcObjects_Count > 0)
		{
			ENewN(LInfo->SrcObjects_Count, LInfo->SrcActiveFlags);
			EIndex  LI = 0;const EIndex  LN = LInfo->SrcObjects_Count;for(;LI < LN;LI++)
			{
				LInfo->SrcActiveFlags[LI] = TRUE;

				E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->SrcObjects[LI], &LInfo->InputInterface);
				E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->SrcModels[LI], &LInfo->InputInterface);
			}
		}
		return(0);
	}

	EChangeFlags  LChangeFlags = 0;
	E3dModel*     LModel = (E3dModel*)PNode;

	{
		EIndex  LI = 0;const EIndex  LN = LInfo->Splines_Count;for(;LI < LN;LI++)
		{
			E3dSpline*  LSpline = LInfo->Splines[LI];
			E3dModel_ObjectRemove(LModel, (E3dObject*)LSpline);
		}
	}

	const EBool  LDoHiddenEdges = LInfo->DoHiddenEdges, LDoUnselectedEdges = LInfo->DoUnselectedEdges, LMergeContiguous = LInfo->MergeContiguous;

	uint8_t  LSplineNormalsMode = LInfo->SplineNormalsMode;
	if(LMergeContiguous && LSplineNormalsMode == E3dSnEDGE) LSplineNormalsMode = E3dSnVERTEX;

	const EBool  LDoTransform = FALSE;

	{
		EIndex  LI = 0;const EIndex  LN = LInfo->SrcObjects_Count;for(;LI < LN;LI++)
		{
			const E3dObject*  LSrcObject = LInfo->SrcObjects[LI];E3dMesh*  LSrcMesh = E_DynamicCast(LSrcObject, E3dMesh);if(LSrcMesh)
			{
				const E3dModel*  LSrcModel = LInfo->SrcModels[LI];
				E3dMatrix        LLocalToWorldMatrix;E3dMatrix_Copy(LSrcModel->LocalToWorldMatrix, LLocalToWorldMatrix);
				const EIndex     LVN = LSrcMesh->Vertices.Count;
				E3dCoord3*       LDVertices = NULL;
				const float      LVectorDrawScale = LSrcMesh->PolyGroups.Count > 0 ? LSrcMesh->PolyGroups.Items[0]->VectorDrawScale : 1.0;

				if(LVN)
				{
					ENewN(LVN, LDVertices);
					const E3dVertexSize  LVertexSize = LSrcMesh->VertexSize;
					if(LDoTransform)
					{
						EIndex  LVC = 0;const E3dVertex*  LSVertex = LSrcMesh->Vertices.Items;E3dCoord3*  LDVertex = LDVertices;for(;LVC < LVN;LVC++, EStruct_Inc(LSVertex, LVertexSize), LDVertex++)
						{
							EMatrix_Transform3x4_vp(LLocalToWorldMatrix, LSVertex->Position, LDVertex);
						}
					}
					else
					{
						EIndex  LVC = 0;const E3dVertex*  LSVertex = LSrcMesh->Vertices.Items;E3dCoord3*  LDVertex = LDVertices;for(;LVC < LVN;LVC++, EStruct_Inc(LSVertex, LVertexSize), LDVertex++) *LDVertex = LSVertex->Position;
					}
				}

// Check if there are any selected Edges on LMesh. If so, only create Splines for those
//
/*
				EIndex  LNSelectedEdges = 0;
				{ EIndex  LEdgeI = 0;const EIndex  LEdgeN = LSrcMesh->Edges.Count;const E3dEdge*  LEdge = LSrcMesh->Edges;for(;LEdgeI < LEdgeN;LEdgeI++, LEdge++) if(LEdge->Flags & E3dEdgeSELECTED) LNSelectedEdges++; }
*/


				EBool  LWeCreatedWingedEdges = FALSE, LWeCreatedVertexLinks = FALSE;

				if(LSrcMesh->WingedEdges == NULL)
				{
					const EBool  LHadVertexLinks = LSrcMesh->VertexLinksNew != NULL;
					E3dMesh_CreateWingedEdgesFromEdges(LSrcMesh, TRUE, FALSE);LWeCreatedWingedEdges = TRUE;
					if(!LHadVertexLinks) LWeCreatedVertexLinks = TRUE;
				}

//if(LWeCreatedVertexLinks) { printf("%s() %s:%d %sLWeCreatedVertexLinks%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout); }
//if(LWeCreatedWingedEdges) { printf("%s() %s:%d %sLWeCreatedWingedEdges%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, ECodeNormal);fflush(stdout); }

// Count Edges
//
				const EIndex  LEN = LSrcMesh->Edges.Count;
				EIndex  LNTotalSplinesNeeded = 0;

				{
					EIndex  LEC = 0;E3dEdge*  LEdge = LSrcMesh->Edges.Items;for(;LEC < LEN;LEC++, LEdge++)
					{
						if((LDoHiddenEdges || (LEdge->Flags & E3dEdgeVISIBLE)) && (LDoUnselectedEdges || (LEdge->Flags & E3dEdgeSELECTED)))
						{
							LEdge->Flags |= E3dEdgePASS;   // We'll use this flag to identify which Edges should be processed
							LEdge->Flags |= E3dEdgeMARK0;  // We'll use this flag to identify which Edges have not been processed yet (cleared after an Edge is processed)
							LNTotalSplinesNeeded++;
						}
						else LEdge->Flags &= ~(E3dEdgePASS|E3dEdgeMARK0);
					}
				}

// Add Splines to LInfo->Splines, if needed
//
				if(LInfo->Splines_Count < LNTotalSplinesNeeded)
				{
					EIndex  LEC = LInfo->Splines_Count;for(;LEC < LNTotalSplinesNeeded;LEC++)
					{
						E3dSpline*  LSpline;E_New(E3dSpline, LSpline);LSpline->SplineType = E3dSplineLINEAR;
						EList_ObjectAddA(LInfo->Splines, 256, LSpline);
					}
				}

				E3dCoord3Sum*  LEdgeNormalSums = NULL;
				E3dCoord3Sum*  LVertexNormalSums = NULL;

				switch(LSplineNormalsMode)
				{
					case E3dSnVERTEX:  LVertexNormalSums = E3dMesh_GetVertexNormals(LSrcMesh, E3dMeshVertexNormalFROM_EDGES);break;
					case E3dSnEDGE:    LEdgeNormalSums = E3dMesh_GetEdgeNormals(LSrcMesh);break;
				}

				{
					E3dEdge*                 LSrcMeshEdges = LSrcMesh->Edges.Items;
					const E3dVertexLinkNew*  LVertexLinks = LSrcMesh->VertexLinksNew;
					const EIndex             LVertexLinksStride = LSrcMesh->VertexLinksStride;

					EIndex  LEdgeI = 0, LEAC = 0;E3dEdge*  LEdge = LSrcMesh->Edges.Items;for(;LEdgeI < LEN;LEdgeI++, LEdge++)
					{
						if(LEdge->Flags & E3dEdgeMARK0)
						{
							LEdge->Flags &= ~E3dEdgeMARK0;

							E3dSpline*  LSpline = LInfo->Splines[LEAC];LSpline->SplineType = E3dSplineLINEAR;
							if(LSpline->CVs.Count != 2)
							{
								EList_ResizeA_s(LSpline->CVs, 2);
							}

							E3dSplineCVs_Init(LSpline->CVs.Items, E3dSplineLINEAR, LSpline->CVs.Count);
							E3dSplineCV*  LCV = LSpline->CVs.Items;

							{
								E3dCoord3  LEdgeNormal;
								if(LSplineNormalsMode == E3dSnEDGE)
								{
									if(LDoTransform) { const E3dCoord3  LNml = LEdgeNormalSums[LEdgeI].Vector;EMatrix_Transform3x3_vv(LLocalToWorldMatrix, LNml, LEdgeNormal); }
									else LEdgeNormal = LEdgeNormalSums[LEdgeI].Vector;
								}


								{
									const EIndex  LVertexIndex = LEdge->Start;

									LCV->Position = LDVertices[LVertexIndex];

									if(LDoTransform)
									{
										switch(LSplineNormalsMode)
										{
											case E3dSnVERTEX:  { const E3dCoord3  LNml = LVertexNormalSums[LVertexIndex].Vector;EMatrix_Transform3x3_vv(LLocalToWorldMatrix, LNml, LCV->Normal); } break;
											case E3dSnEDGE:    LCV->Normal = LEdgeNormal;break;
										}
									}
									else
									{
										switch(LSplineNormalsMode)
										{
											case E3dSnVERTEX:  LCV->Normal = LVertexNormalSums[LVertexIndex].Vector;break;
											case E3dSnEDGE:    LCV->Normal = LEdgeNormal;break;
										}
									}
								}

								LCV++;

								{
									const EIndex  LVertexIndex = LEdge->End;

									LCV->Position = LDVertices[LVertexIndex];

									if(LDoTransform)
									{
										switch(LSplineNormalsMode)
										{
											case E3dSnVERTEX:  { const E3dCoord3  LNml = LVertexNormalSums[LVertexIndex].Vector;EMatrix_Transform3x3_vv(LLocalToWorldMatrix, LNml, LCV->Normal); } break;
											case E3dSnEDGE:    LCV->Normal = LEdgeNormal;break;
										}
									}
									else
									{
										switch(LSplineNormalsMode)
										{
											case E3dSnVERTEX:  LCV->Normal = LVertexNormalSums[LVertexIndex].Vector;break;
											case E3dSnEDGE:    LCV->Normal = LEdgeNormal;break;
										}
									}
								}
							}

							LEdge->Flags &= ~E3dEdgeMARK0;


							if(LMergeContiguous)
							{
// 1  62 20 63  2

//printf("%s() %s:%d  %3lld -> %3lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LEdge->Start, (long long)LEdge->End);fflush(stdout);
// Walk from the Start and End points along the VertexLinks, until we hit Edges with valence > 2
// When evaluating the valence, we have to take Edge visibility, etc. into consideration.
// This simply means checking the E3dEdgeMARK0 flag on the Edge, as we set this flag above, for Edges that need to be considered
//
								{
									EIndex                LPrevVertexIndex = LEdge->Start;  // This is used for preventing back-tracking
									EIndex                LVertexIndex = LEdge->End;
									const E3dVertexLinkNew*  LVertexLinksRec = EStruct_ByIndex(LVertexLinks, LVertexLinksStride, LVertexIndex);

									for(;1;)
									{
// Find the first marked Edge, and check if this Vertex's valence is 2
//
										EIndex  LEVC = 0, LValence = 0, LNextVertexIndex = -1, LNextEdgeIndex = -1;const EIndex  LEVN = LVertexLinksRec->EdgeEnds.Count;const E3dEdgeEnd*  LEE = LVertexLinksRec->EdgeEnds.Items;for(;LEVC < LEVN;LEVC++, LEE++)
										{
											const EIndex  LTNextVertexIndex = LEE->VertexIndex, LTNextEdgeIndex = LEE->EdgeIndex;
											if(LSrcMeshEdges[LTNextEdgeIndex].Flags & E3dEdgePASS)
											{
												LValence++;if(LValence > 2) break;
//printf("%s() %s:%d  LValence %3lld  LTNextVertexIndex %3lld  LTNextEdgeIndex %3lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LValence, (long long)LTNextVertexIndex, (long long)LTNextEdgeIndex);fflush(stdout);
												if(LTNextVertexIndex != LPrevVertexIndex) { LNextVertexIndex = LTNextVertexIndex;LNextEdgeIndex = LTNextEdgeIndex; }
											}
										}
//printf("%s() %s:%d  LVertexIndex %3lld  LValence %3lld  LNextVertexIndex %3lld  LNextEdgeIndex %3lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LVertexIndex, (long long)LValence, (long long)LNextVertexIndex, (long long)LNextEdgeIndex);fflush(stdout);
										if(LValence == 2)
										{
											E3dEdge*  LNEdge = LSrcMeshEdges + LNextEdgeIndex;if(LNEdge->Flags & E3dEdgeMARK0)
											{
												LNEdge->Flags &= ~E3dEdgeMARK0;
												E3dSplineCV*  LNCV = EList_StructAddA_s(LSpline->CVs, 16);
												E3dSplineCV_Init(LNCV, E3dSplineLINEAR);
												LNCV->Position = LDVertices[LNextVertexIndex];
												if(LSplineNormalsMode == E3dSnVERTEX)
												{
													if(LDoTransform) { const E3dCoord3  LNml = LVertexNormalSums[LNextVertexIndex].Vector;EMatrix_Transform3x3_vv(LLocalToWorldMatrix, LNml, LNCV->Normal); }
													else LNCV->Normal = LVertexNormalSums[LNextVertexIndex].Vector;
												}

												LVertexLinksRec = EStruct_ByIndex(LVertexLinks, LVertexLinksStride, LNextVertexIndex);
												LPrevVertexIndex = LVertexIndex;
												LVertexIndex = LNextVertexIndex;
											}
											else break;
										}
										else break;
									}
								}

								{
									EIndex                LPrevVertexIndex = LEdge->End;  // This is used for preventing back-tracking
									EIndex                LVertexIndex = LEdge->Start;
									const E3dVertexLinkNew*  LVertexLink = EStruct_ByIndex(LVertexLinks, LVertexLinksStride,  LVertexIndex);

									for(;1;)
									{
// Find the first marked Edge, and check if this Vertex's valence is 2
//
										EIndex  LEVC = 0, LValence = 0, LNextVertexIndex = -1, LNextEdgeIndex = -1;const EIndex  LEVN = LVertexLink->EdgeEnds.Count;const E3dEdgeEnd*  LEE = LVertexLink->EdgeEnds.Items;for(;LEVC < LEVN;LEVC++, LEE++)
										{
											const EIndex  LTNextVertexIndex = LEE->VertexIndex, LTNextEdgeIndex = LEE->EdgeIndex;
											if(LSrcMeshEdges[LTNextEdgeIndex].Flags & E3dEdgePASS)
											{
												LValence++;if(LValence > 2) break;
												if(LTNextVertexIndex != LPrevVertexIndex) { LNextVertexIndex = LTNextVertexIndex;LNextEdgeIndex = LTNextEdgeIndex; }
											}
										}
//printf("%s() %s:%d  LVertexIndex %3lld  LValence %3lld  LNextVertexIndex %3lld  LNextEdgeIndex %3lld\n", __FUNCTION__, __FILE__, __LINE__, (long long)LVertexIndex, (long long)LValence, (long long)LNextVertexIndex, (long long)LNextEdgeIndex);fflush(stdout);
										if(LValence == 2)
										{
											E3dEdge*  LNEdge = LSrcMeshEdges + LNextEdgeIndex;if(LNEdge->Flags & E3dEdgeMARK0)
											{
												LNEdge->Flags &= ~E3dEdgeMARK0;
												E3dSplineCV*  LNCV = EList_StructInsertA_s(LSpline->CVs, 16, 0);
												E3dSplineCV_Init(LNCV, E3dSplineLINEAR);
												LNCV->Position = LDVertices[LNextVertexIndex];
												if(LSplineNormalsMode == E3dSnVERTEX)
												{
													if(LDoTransform) { const E3dCoord3  LNml = LVertexNormalSums[LNextVertexIndex].Vector;EMatrix_Transform3x3_vv(LLocalToWorldMatrix, LNml, LNCV->Normal); }
													else LNCV->Normal = LVertexNormalSums[LNextVertexIndex].Vector;
												}

												LVertexLink = EStruct_ByIndex(LVertexLinks, LVertexLinksStride,  LNextVertexIndex);
												LPrevVertexIndex = LVertexIndex;
												LVertexIndex = LNextVertexIndex;
											}
											else break;
										}
										else break;
									}
								}
							}


							LSpline->VectorDrawScale = LVectorDrawScale;
							LSpline->Class->UpdateForDisplay((E3dObject*)LSpline, E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY);
							LEAC++;

							E3dModel_ObjectAppend(LModel, (E3dObject*)LSpline, FALSE);
						}
					}
				}

				EFreeChkClr(LEdgeNormalSums);
				EFreeChkClr(LVertexNormalSums);

				if(E3dp_Prefs.OptimizeFor == EOptimizeMEMORY)
				{
					if(LWeCreatedWingedEdges) E3dMesh_WingedEdgesFree(LSrcMesh);
					if(LWeCreatedVertexLinks) E3dMesh_VertexLinksNewFree(LSrcMesh);
				}

//			E3dSpline*  LSpline = LInfo->Splines[LI];

				EFreeChkClr(LDVertices);
			}
		}

		LChangeFlags |= E3dChgSplineSHAPE|E3dChgSplineCONNECTIVITY|E3dChgHIERARCHY;
	}

	return(LChangeFlags);
}


//================================================================
// Callback for input changes
// This function should Not call any 3DWindow redrawing function,
// just return with TRUE if a redraw is needed!
//================================================================
static EChangeFlags _EdgesToSplines_InputChanged(EInterface* PInterface, EInterfaceCallbackStruct* PCBS)
{
	E3dNode*            LNode = (E3dNode*)PInterface->Object;
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)E3dNodeComponent_ByClass(LNode, _EdgesToSplines_class, FALSE);
	EChangeFlags        LChangeFlags = _EdgesToSplines_Update(NULL, LNode, (E3dNodeComponent*)LThis, E3dUpdateALL, FALSE);
/*
	E3dScene_ObjectsUpdateModelList(LScene);
	E3dScene_LayoutOnSchematics(LScene, FALSE);
	if(LChangeFlags) E3dWindows_RedrawByChangeFlags(LChangeFlags);
	E3dNodePanel_UpdateValues(LPanel,  NULL, NULL);
*/


/*
	E3dEdgesToSplines*  LInfo    = (E3dEdgesToSplines*)PInterface->ObjectComponent;
	EChangeFlags        LChanged = E3dObject_GetChangeFlagsByClass(LObject);

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

	return(LChangeFlags);
}


#define _CB(pField)\
static void _EdgesToSplines_CB_##pField(XeWidget PW, EPointer PClientData, EPointer PCallData)\
{\
	E3dNodePanel*       LPanel = (E3dNodePanel*)PClientData;\
	E3dNode*            LNode = LPanel->Node;\
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)E3dNodeComponent_ByClass(LNode, _EdgesToSplines_class, FALSE);\
	E3dScene*           LScene = E3d_Scene;\
\
	LThis->pField = ((XeToggleButtonCallbackStruct*)PCallData)->Set;\
\
	EChangeFlags  LChangeFlags = _EdgesToSplines_Update(NULL, LNode, (E3dNodeComponent*)LThis, E3dUpdateALL, FALSE);\
	E3dScene_ObjectsUpdateModelList(LScene);\
	E3dScene_LayoutOnSchematics(LScene, FALSE);\
	if(LChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LChangeFlags);\
	E3dNodePanel_UpdateValues(LPanel,  NULL, NULL);\
}


_CB(DoHiddenEdges)
_CB(DoUnselectedEdges)
_CB(MergeContiguous)

static void _EdgesToSplines_CB_SplineNormalsNone(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*       LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)E3dNodeComponent_ByClass(LNode, _EdgesToSplines_class, FALSE);
	E3dScene*           LScene = E3d_Scene;

	LThis->SplineNormalsMode = 0;

	EChangeFlags  LChangeFlags = _EdgesToSplines_Update(NULL, LNode, (E3dNodeComponent*)LThis, E3dUpdateALL, FALSE);
	E3dScene_ObjectsUpdateModelList(LScene);
	E3dScene_LayoutOnSchematics(LScene, FALSE);
	if(LChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LChangeFlags);
	E3dNodePanel_UpdateValues(LPanel,  NULL, NULL);
}

static void _EdgesToSplines_CB_SplineNormalsVertex(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*       LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)E3dNodeComponent_ByClass(LNode, _EdgesToSplines_class, FALSE);
	E3dScene*           LScene = E3d_Scene;

	LThis->SplineNormalsMode = E3dSnVERTEX;

	EChangeFlags  LChangeFlags = _EdgesToSplines_Update(NULL, LNode, (E3dNodeComponent*)LThis, E3dUpdateALL, FALSE);
	E3dScene_ObjectsUpdateModelList(LScene);
	E3dScene_LayoutOnSchematics(LScene, FALSE);
	if(LChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LChangeFlags);
	E3dNodePanel_UpdateValues(LPanel,  NULL, NULL);
}

static void _EdgesToSplines_CB_SplineNormalsEdge(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dNodePanel*       LPanel = (E3dNodePanel*)PClientData;E3dNode*  LNode = LPanel->Node;
	E3dEdgesToSplines*  LThis = (E3dEdgesToSplines*)E3dNodeComponent_ByClass(LNode, _EdgesToSplines_class, FALSE);
	E3dScene*           LScene = E3d_Scene;

	LThis->SplineNormalsMode = E3dSnEDGE;

	EChangeFlags  LChangeFlags = _EdgesToSplines_Update(NULL, LNode, (E3dNodeComponent*)LThis, E3dUpdateALL, FALSE);
	E3dScene_ObjectsUpdateModelList(LScene);
	E3dScene_LayoutOnSchematics(LScene, FALSE);
	if(LChangeFlags) E3dWindows_RedrawByChangeFlags(LScene, LChangeFlags);
	E3dNodePanel_UpdateValues(LPanel,  NULL, NULL);
}

static void _EdgesToSplines_GUI(E3dNode* PNode, E3dNodeComponent* PInfo, XeInfoPanel* PPanel, void* PDrawer, const int PAction)
{
	XeComponentClassDrawer*  LDrawer = (XeComponentClassDrawer*)PDrawer;
	E3dEdgesToSplines*       LInfo = (E3dEdgesToSplines*)PInfo;
	E3dResourcesGUI*         LGUI;

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

					XeToggleButton_SetState(LGUI->DoHiddenEdgesW, LInfo->DoHiddenEdges, FALSE);
					XeToggleButton_SetState(LGUI->DoUnselectedEdgesW, LInfo->DoUnselectedEdges, FALSE);
					XeToggleButton_SetState(LGUI->MergeContiguousW, LInfo->MergeContiguous, FALSE);
					XeToggleButton_SetState(LGUI->SplineNormalsModeNoneW, LInfo->SplineNormalsMode == 0, FALSE);
					XeToggleButton_SetState(LGUI->SplineNormalsModeVertexW, LInfo->SplineNormalsMode == E3dSnVERTEX, FALSE);
					XeToggleButton_SetState(LGUI->SplineNormalsModeEdgeW, LInfo->SplineNormalsMode == E3dSnEDGE, FALSE);
				}
				else
				{
					XeWidget  LTopMatrixW = LDrawer->Drawer.DrawerW, LW;
					EArg      Args[256];int  ArgCnt;

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

					EXeStart;
					EXeSetArg(XeNResizableY, TRUE);
					EXeSetArg(XeNMarginSizeX, 2);
					EXeSetArg(XeNSpacingY, 0);
					Xe_SetValues(LTopMatrixW,  Args, ArgCnt);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->DoHiddenEdges);
					XeToolTip8("Use hidden Edges as well.");
					LGUI->DoHiddenEdgesW = LW = XeWidget_ManagedNew("Hidden Edges", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_DoHiddenEdges, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->DoUnselectedEdges);
					XeToolTip8("Use unselected Edges as well (if FALSE, only selected Edges are used).");
					LGUI->DoUnselectedEdgesW = LW = XeWidget_ManagedNew("Unselected Edges", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_DoUnselectedEdges, (EPointer)PPanel);

					EXeStart;
					EXeSetArg(XeNShadowThickness, 0);
					EXeSetArg(XeNHighlightThickness, 0);
					EXeSetArg(XeNSet, LInfo->MergeContiguous);
					XeToolTip8("Generate a single Spline for non-branching Edge sequences.");
					LGUI->MergeContiguousW = LW = XeWidget_ManagedNew("Merge continuous", XeToggleButton_class, LTopMatrixW,  Args, ArgCnt);
					XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_MergeContiguous, (EPointer)PPanel);

					{
						EXeStart;
						EXeSetArg(XeNOrientation, XeHORIZONTAL);
						EXeSetArg(XeNSpacingLeft, 4);EXeSetArg(XeNSpacingRight, 4);
						EXeSetArg(XeNRadioBehavior, TRUE);
						EXeSetArg(XeNSpacingX, 4);
						EXeSetArg(XeNResizableX, FALSE);EXeSetArg(XeNAlignmentX, XeAlignBEGINNING);
						XeWidget  LMatrixW = XeWidget_ManagedNew("SplineNormalModeMatrixW", XeMatrixWidget_class, LTopMatrixW,  Args, ArgCnt);

						EXeStart;
						XeWidget_ManagedNew("Normals from", XeLabelWidget_class, LMatrixW,  Args, ArgCnt);

						EXeStart;
						EXeSetArg(XeNSpacingLeft, 8);
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->SplineNormalsMode == 0);
						XeToolTip8("Don't generate Spline normals.");
						LGUI->SplineNormalsModeNoneW = LW = XeWidget_ManagedNew("None", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_SplineNormalsNone, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->SplineNormalsMode == 0);
						XeToolTip8("Don't generate Spline normals.");
						LGUI->SplineNormalsModeVertexW = LW = XeWidget_ManagedNew("Vertex", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_SplineNormalsVertex, (EPointer)PPanel);

						EXeStart;
						EXeSetArg(XeNShadowThickness, 0);
						EXeSetArg(XeNHighlightThickness, 0);
						EXeSetArg(XeNSet, LInfo->SplineNormalsMode == 0);
						XeToolTip8("Don't generate Spline normals.");
						LGUI->SplineNormalsModeEdgeW = LW = XeWidget_ManagedNew("Edge", XeToggleButton_class, LMatrixW,  Args, ArgCnt);
						XeAddCallback(LW, XeNValueChangedCallback, _EdgesToSplines_CB_SplineNormalsEdge, (EPointer)PPanel);
					}
				}
			}
		break;

		case E_FREE:
		break;
	}
}


//========================================================
// Create linear Splines for Edges of selected Meshes
//========================================================
static void _EdgesToSplines_MCB(XeWidget PW, EPointer PClientData, EPointer PCallData)
{
	E3dScene*  LScene = E3d_Scene;

	E3dNodeComponentClass*  LClass = _EdgesToSplines_class;

	EListAI_s(, E3dObjectPtr, LObjects);const EIndex  LNObjects = E3dScene_GetObjects(LScene, E3dItemMESH, E3dSELECTED, FALSE,  &LObjects);if(LNObjects > 0)
	{
		E3dModel*  LModel;E_New(E3dModel, LModel);EObject_SetName(LModel, E3dScene_GetUniqueNodeName(LScene, LClass->Name), FALSE);

//	E3dModel*  LModel = E3dModel_CustomNew(LScene, LClass, NULL, E3dp_Prefs.Duplicate_Flags & E3dCopyMODEL_UNIQUE_NAME ? TRUE : FALSE);if(LModel)

		if(LModel)
		{
			E3dScene_NodeHrcAdd(LScene, (E3dNode*)LModel, E3dCallSCENE_CALLBACKS);
			E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(LScene, TRUE);
		}

		if(LModel)
		{
			E3dEdgesToSplines*  LInfo = (E3dEdgesToSplines*)E3dNodeComponent_Add((E3dNode*)LModel, LClass);

			LModel->SchemObject.BranchExpanded = FALSE;

//printf("%s() %s:%d  %s[%p]%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeGreen, LInfo, ECodeNormal);fflush(stdout);

// Collect source Meshes
//
			{
				EIndex  LI = 0;for(;LI < LNObjects;LI++)
				{
					EIndex  LMI = 0;E3dObject*  LObject = LObjects.Items[LI];for(;LMI < LObject->Models.Count;LMI++)
					{
						E3dModel*  LSrcModel = LObject->Models.Items[LMI];
						if(LSrcModel->Selection == E3dNodeSelNODE || LSrcModel->Selection == E3dNodeSelBRANCH || LSrcModel->Selection == E3dNodeSelBRANCH_ROOT ||
						  (LSrcModel->Selection == E3dNodeSelOBJECT && LObject->Selection == E3dObjectSelOBJECT))
						{
							EList_ObjectAdd(LInfo->SrcObjects, LObject);
							EList_ObjectAdd(LInfo->SrcModels,  LSrcModel);
						}
					}
				}
			}

			if(LInfo->SrcObjects_Count > 0)
			{
				ENewN(LInfo->SrcObjects_Count, LInfo->SrcActiveFlags);
				EIndex  LI = 0;const EIndex  LN = LInfo->SrcObjects_Count;for(;LI < LN;LI++)
				{
					LInfo->SrcActiveFlags[LI] = TRUE;

					E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->SrcObjects[LI], &LInfo->InputInterface);
					E3dBaseObject_AddOutput((E3dBaseObject*)LInfo->SrcModels[LI], &LInfo->InputInterface);
				}
			}

			LClass->Update(NULL, (E3dNode*)LModel, (E3dNodeComponent*)LInfo, E3dUpdateALL, FALSE);

			E3dScene_ObjectsUpdateModelList(LScene);
			E3dScene_LayoutOnSchematics(LScene, TRUE);

			E3dWindows_RedrawByChangeFlags(LScene, E3dChgFACE|E3dChgHIERARCHY);

			if(((XePushButtonCallbackStruct*)PCallData)->Reason == XeCR_ACTIVATE_OPTIONS) E3dNode_Edit((E3dNode*)LModel, _EdgesToSplines_class);
		}
		else
		{
printf("%s() %s:%d  %sCould not create Model%s\n", __FUNCTION__, __FILE__, __LINE__, ECodeYellow, ECodeNormal);fflush(stdout);
		}

		EList_FreeA_s(LObjects);
	}
	else E3dp_PrintMessage(ELogMESSAGE, 5000, "%s: Select a Mesh as a source", LClass->Name);
}


//========================================
// Entry point of the plugin
//========================================
int E_PluginInit(EPlugin* PPlugin)
{
	E3dSpline_class->GUI = _SplineGUI;

	EpM_CreateIconStates3_sp(SplSegmentLin);
	EpM_CreateIconStates3_sp(SplSegmentCrv);
	EpM_CreateIconStates3_sp(SplSegmentArc);
	EpM_CreateIconStates3_sp(TgC0);
	EpM_CreateIconStates3_sp(TgC1);
	EpM_CreateIconStates3_sp(TgG1);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Close", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)CR_SPLINE_CLOSE);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Open", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)CR_SPLINE_OPEN);

	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Draw Linear", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineCreate, (EPointer)E3dSplineLINEAR);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Draw Bezier", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineCreate, (EPointer)E3dSplineBEZIER);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Draw BSpline", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineCreate, (EPointer)E3dSplineBSPLINE);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Draw Cardinal", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineCreate, (EPointer)E3dSplineCARDINAL);
//_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Draw NURBS", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dSplineNURBS);
	_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Edit points", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)CR_SPLINE_EDIT_POINTS);


	{
		E3dNodeComponentClass  LNodeComponentClass =
		{
			.Name=        "Edges to Splines",
			.StructSize=  sizeof(E3dEdgesToSplines),
			.Resources=   EListS_s( _EdgesToSplines_Resources),
			.Init=        _EdgesToSplines_Init,
			.Copy=        _EdgesToSplines_Copy,
			.Free=        _EdgesToSplines_Free,
			.Update=      _EdgesToSplines_Update,
			.GUI=         _EdgesToSplines_GUI
		};
		_EdgesToSplines_class = E3dNodeComponent_ClassRegister(&LNodeComponentClass);

		_MenuButtons[_MenuButtons_Count++] = XePushButton_Add(Xe_App, "Menu->Spline", "Edges to Splines", '\0', NULL, NULL, TRUE, NULL, _EdgesToSplines_MCB, (EPointer)NULL);
	}


	return(E_SUCCESS);
}


//========================================
// Exit method of the plugin
//========================================
int E_PluginExit(const EBool PExiting)
{
	if(!PExiting || Xe_GUIFreeOnExit)
	{
		int  LI = 0;for(;LI < _MenuButtons_Count;LI++) XeItemFree(_MenuButtons[LI]);
		_MenuButtons_Count = 0;
	}

	if(_EdgesToSplines_class) { E3dNodeComponent_ClassDeactivate(_EdgesToSplines_class);_EdgesToSplines_class = NULL; }

	EpIconFree3(SplSegmentLin);
	EpIconFree3(SplSegmentCrv);
	EpIconFree3(SplSegmentArc);
	EpIconFree3(TgC0);
	EpIconFree3(TgC1);
	EpIconFree3(TgG1);

	E3dSpline_class->GUI = NULL;

	return(E_SUCCESS);
}
