/*======================================================================================================*/
/* Create and edit Splines										*/
/*													*/
/* Plugin for EQUINOX-3D										*/
/*													*/
/* AUTHOR:	Gabor Nagy										*/
/* DATE:	1996-Nov-23 23:02:44									*/
/*													*/
/* EQUINOX-3D(TM), 3DPanel(TM) and 3DLib(TM) Copyright (C) 1995 By Gabor Nagy. All rights reserved.	*/
/*======================================================================================================*/
#include <math.h>
#include <stdio.h>

#include <EMalloc.h>

#include <E3D/E3D.h>
#include <E3D/Item.h>
#include <E3D/Math.h>
#include <E3D/Matrix.h>
#include <E3D/Model.h>
#include <E3D/Panel.h>
#include <E3D/Pick.h>
#include <E3D/Polygon.h>
#include <E3D/Scene.h>
#include <E3D/StatusPanel.h>	// For E3dp_PrintMessage

#include <EGUI/PushButton.h>
#include <EGUI/Dialog.h>

#include <Xe/Label.h>


/*----------------------------------------------*/
/* From TransformPanel.c			*/
/*----------------------------------------------*/
extern E3dMatrix	E3dp_WorldToLocalMatrix;
extern E3dGeometry*	E3dp_ActiveGeometry;
extern E3dSpline*	E3dp_ActiveSpline;
extern int		E3dp_BezCVActiveSubCV;
extern E3dBezierCV*	E3dp_ActiveBezierCV;
extern E3dSplineCV*	E3dp_ActiveSplineCV;


static EguiItem		_MenuButtons[16];
static unsigned int	_NumOfMenuButtons=0;

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

static char		_ModelName[256];
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;


enum
{
 E3dCR_SPLINE_OPEN,
 E3dCR_SPLINE_CLOSE,
 E3dCR_SPLINE_DRAW,
 E3dCR_SPLINE_ADD_POINTS,
 E3dCR_DELETE_POINTS
};


#define EPSILON	0.00001


static void	_PickCB_AddCV(E3dPickCallbackStruct* LPickStruct, int LNumOfItems, E3dItem* LPickedItems);


/*======================================*/
/* Open or close selected Splines	*/
/*======================================*/
static void _OpenCloseSplines(int LAction)
{
 E3dModel**	LRootModels;
 E3dModel*	LModel;
 unsigned int	LC, LN, LSplinesSelected, LPerformedCnt;


 LSplinesSelected=0;LPerformedCnt=0;
 LRootModels=E3d_Scene->RootModels;
 for(LC=0, LN=E3d_Scene->NumOfRootModels;LC<LN;LC++)
 {
  LModel=LRootModels[LC];

  {
   E3dGeometry**	LGeometries;
   E3dGeometry*		LGeometry;
   unsigned int		LGmCnt, LGmNum;


   for(;LModel;LModel=LModel->Next)
   {
    if(LModel->Selection != E3dSEL_NONE)
    {
     switch(LModel->Type)
     {
      case E3dMDL_NORMAL:

       LGmNum=LModel->NumOfGeometries;LGeometries=LModel->Geometries;
       for(LGmCnt=0;LGmCnt<LGmNum;LGmCnt++)
       {
	if((LGeometry=LGeometries[LGmCnt])!=NULL)
	{
	 switch(LGeometry->GeoType)
	 {
          case E3dGEO_SPLINE:
	   {
	    E3dSpline*			LSpline=(E3dSpline*)LGeometry;
	    E3dGeometryCallbackStruct	LCBS;

	    LSplinesSelected++;

	    LCBS.Reasons=E3dGF_TOPOLOGY;
	    LCBS.Geometry=(E3dGeometry*)LSpline;
	    switch(LAction)
	    {
	     case E3dCR_SPLINE_OPEN:
	      if(LSpline->Closed)
	      {
		LSpline->Closed=FALSE;
		if(LSpline->SplineType!=E3dSPL_LINEAR)
		{
		 E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_ALL);
		 E3d_CallOutputs(LSpline, LSpline->Outputs, LSpline->NumOfOutputs, &LCBS);
		}
		LPerformedCnt++;
	      }
	     break;

	     case E3dCR_SPLINE_CLOSE:
	      if(!LSpline->Closed)
	      {
		LSpline->Closed=TRUE;
		if(LSpline->SplineType!=E3dSPL_LINEAR)
		{
		 E3d_GeometryUpdateForDisplay(LGeometry, E3dGF_ALL);
		 E3d_CallOutputs(LSpline, LSpline->Outputs, LSpline->NumOfOutputs, &LCBS);
		}
		LPerformedCnt++;
	      }
	     break;
	    }
	   }
	  break;
	 }
	}
       }
      break;
     }
    }
   }
  }
 }

 if(LSplinesSelected>0) 
 {
  switch(LAction)
  {
   case E3dCR_SPLINE_CLOSE:
    if(LPerformedCnt>0)
    {
     E3dp_PrintMessage(0, 5000, "%d Spline(s) closed", LPerformedCnt);
     E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_ALL);
    }
    else E3dp_PrintMessage(0, 5000, "No open Spline selected");
   break;

   case E3dCR_SPLINE_OPEN:
    if(LPerformedCnt>0)
    {
     E3dp_PrintMessage(0, 5000, "%d spline(s) opened", LPerformedCnt);
     E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_ALL);
    }
    else E3dp_PrintMessage(0, 5000, "No closed Spline selected");
   break;
  }
 }
 else E3dp_PrintMessage(0, 5000, "No Spline selected");
}


/*==============================================*/
/* Start CV moving				*/
/*==============================================*/
static void _CVStartMoving(E3dWindow* L3DWindow, int LPtrX, int LPtrY, E3dModel* LModel, E3dSpline* LSpline, E3d3DPosition* LPoint)
{
 E3dp_PushPointerMode(E3dpPTR_MOVE_POINT);E3dp_PushMessage(0, 0, "Moving Spline CV");
 E3dp_ActiveGeometry=_Geometry;
 E3dp_ActiveSpline=LSpline;
 if(E3dp_ActiveSplineCV) E3dp_ActiveSplineCV->Flags|=E3dKEYF_ACTIVE;
 if(E3dp_ActiveBezierCV) E3dp_ActiveBezierCV->Flags|=E3dKEYF_ACTIVE;
 E3d_MatrixInvert3x4(LModel->LocalToWorldMatrix, E3dp_WorldToLocalMatrix);
 E3dp_InitPointMove(L3DWindow, LPtrX, LPtrY, LPoint->X, LPoint->Y, LPoint->Z, FALSE);
}


/*==============================================*/
/* Insert new CV between two exinsting ones	*/
/*==============================================*/
static EBool _InsertBetween(E3dSpline* LSpline, E3dWindow* L3DWindow, E3dMatrix LWorldToLocalMatrix, int LPtrX, int LPtrY, E3dCoordinate LX, E3dCoordinate LY, E3dCoordinate LZ, E3dRay* LRay)
{
 E3dCoordinate	LDistance=-1.0,
		mX, mY, mZ;
 int		LC;
 EBool		LChanged=FALSE;


// Transform the ray into the Spline's local space
//
 mX=LRay->X;mY=LRay->Y;mZ=LRay->Z;
 E3dM_MatrixTransform3x4(LWorldToLocalMatrix, LRay->X, LRay->Y, LRay->Z);

// The ray direction is a unit VECTOR, so transform it with the top-left 3x3
// sub-matrix
//
 mX=LRay->DirX;mY=LRay->DirY;mZ=LRay->DirZ;
 E3dM_MatrixTransform3x3(LWorldToLocalMatrix, LRay->DirX, LRay->DirY, LRay->DirZ);


 switch(LSpline->SplineType)
 {
  case E3dSPL_BSPLINE:
  case E3dSPL_LINEAR:
   {
    E3dSplineCV*	LSplineCV=(E3dSplineCV*)(LSpline->CVs);
    E3dSplineCV*	LSplineCVs=(E3dSplineCV*)(LSpline->CVs);
    E3d3DPosition	LClosestPoint;
    E3dCoordinate	LX0, LY0, LZ0,
			LDst;
    int			LNumOfCVs=LSpline->NumOfCVs, LClosestSegment=-1;


// Find segment closest to the ray
//
    for(LC=0;LC<LNumOfCVs;LC++, LSplineCV++)
    {
     LX0=LSplineCV->Position.X;
     LY0=LSplineCV->Position.Y;
     LZ0=LSplineCV->Position.Z;

// Even if the Spline is not closed, we use the imaginary last linear segment (end to beginning)
// to possibly add a new CV between the last and the 0th CV
//
     if(LC==(LNumOfCVs-1))
     {
      LDst=E3d_RayClosestPointOnSegment(LRay, LX0, LY0, LZ0, LSplineCVs->Position.X, LSplineCVs->Position.Y, LSplineCVs->Position.Z, &LClosestPoint, NULL);
     }
     else
     {
      LDst=E3d_RayClosestPointOnSegment(LRay, LX0, LY0, LZ0, (LSplineCV+1)->Position.X, (LSplineCV+1)->Position.Y, (LSplineCV+1)->Position.Z, &LClosestPoint, NULL);
     }

     if(LDst>=0.0)
     {
      if(LClosestSegment==-1)
      {
       LDistance=LDst;LClosestSegment=LC;
       LX=LClosestPoint.X;LY=LClosestPoint.Y;LZ=LClosestPoint.Z;
      }
      else
      {
       if(LDst<LDistance)
       {
	LDistance=LDst;LClosestSegment=LC;
	LX=LClosestPoint.X;LY=LClosestPoint.Y;LZ=LClosestPoint.Z;
       }
      }
     }

    }

    if(LClosestSegment!=-1)
    {
     E3dSplineCV*	LSplineCV=(E3dSplineCV*)(LSpline->CVs);
     unsigned int	LAfterCV=0, LCVC;



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


//printf("Distance: %f  Segment: %d\n", LDistance, LClosestSegment);fflush(stdout);

     LAfterCV=LClosestSegment;


//printf("Insert after CV: %d  at %f,%f,%f\n", LAfterCV, LX, LY, LZ);fflush(stdout);

     LCVC=LAfterCV+1;
     if(LSpline->CVs==NULL) { LSpline->CVs=LSplineCV=(E3dSplineCV*)EMalloc(sizeof(E3dSplineCV)); }
     else
     {
      E3dSplineCV*	LTCVs=(E3dSplineCV*)ERealloc(LSpline->CVs, sizeof(E3dSplineCV)*(LSpline->NumOfCVs+1));

      if(LTCVs)
      {
       LSpline->CVs=LTCVs;
       LSplineCV=LTCVs+LCVC;

       if(LCVC<(LSpline->NumOfCVs)) memmove(LSplineCV+1, LSplineCV, sizeof(E3dSplineCV)*(LSpline->NumOfCVs-LCVC));
      }
      else LSplineCV=NULL;
     }

     E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

     if(LSplineCV)
     {
      E3d_SplineCVDefault(E3dSPL_CARDINAL, (void*)LSplineCV);
      LSplineCV->Position.X=LX;
      LSplineCV->Position.Y=LY;
      LSplineCV->Position.Z=LZ;

      LSpline->NumOfCVs+=1;

      E3dp_PrintMessage(0, 4000, "Spline CV: %f,%f,%f  added after CV %d", LX, LY, LZ, LAfterCV);

      E3dp_ActiveSplineCV=LSplineCV;
      _CVStartMoving(L3DWindow, LPtrX, LPtrY, _Model, LSpline, &(LSplineCV->Position));
      E3dp_PrintMessage(0, 0, "Point #%d %.4f, %.4f, %.4f", LSpline->NumOfCVs-1, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);

      LChanged=TRUE;
     }
    }
   }
  break;

// These Splines go through their CVs, so we treat them differently
//
  case E3dSPL_BEZIER:
  case E3dSPL_CARDINAL:
   {
    E3dGLCoordinate*	LGLLinSegments=LSpline->GLLinSegments;
    E3dGLCoordinate*	LGLLinSegment=LGLLinSegments;
    E3d3DPosition	LClosestPoint;
    E3dCoordinate	LX1, LY1, LZ1,
			LDst;
    int			LNumOfLinSegments=LSpline->NumOfLinSegments, LClosestLinSegment=-1;


// Find linear Spline segment closest to the ray
//
    for(LC=0;LC<LNumOfLinSegments;LC++, LGLLinSegment+=3)
    {
// Even if the Spline is not closed, we use the imaginary last linear segment (end to beginning)
// to possibly add a new CV between the last and the 0th CV
//
     if(LC==(LNumOfLinSegments-1)) { LX1=LGLLinSegments[E3dX];LY1=LGLLinSegments[E3dY];LZ1=LGLLinSegments[E3dZ]; }
     else { LX1=LGLLinSegment[E3dX+3];LY1=LGLLinSegment[E3dY+3];LZ1=LGLLinSegment[E3dZ+3]; }

     LDst=E3d_RayClosestPointOnSegment(LRay, LGLLinSegment[E3dX], LGLLinSegment[E3dY], LGLLinSegment[E3dZ], LX1, LY1, LZ1, &LClosestPoint, NULL);

     if(LDst>=0.0)
     {
      if(LClosestLinSegment==-1)
      {
       LDistance=LDst;LClosestLinSegment=LC;
       LX=LClosestPoint.X;LY=LClosestPoint.Y;LZ=LClosestPoint.Z;
      }
      else
      {
       if(LDst<LDistance)
       {
	LDistance=LDst;LClosestLinSegment=LC;
	LX=LClosestPoint.X;LY=LClosestPoint.Y;LZ=LClosestPoint.Z;
       }
      }
     }

    }



    switch(LSpline->SplineType)
    {
     case E3dSPL_BEZIER:
//printf("Distance: %f  LinSegment: %d\n", LDistance, LClosestLinSegment);fflush(stdout);
      if(LClosestLinSegment!=-1)
      {
       E3dBezierCV*	LBezierCV=(E3dBezierCV*)(LSpline->CVs);
       unsigned int	LKN=LSpline->NumOfCVs, LAfterCV=0, LCVC, LThisLinSegment=0, LLinSegmentsInS;

       for(LC=0;LC<LKN;LC++, LBezierCV++)
       {
	if(LBezierCV->Segment==E3dBEZ_LINEAR) LLinSegmentsInS=1;
	else
	{
	 if(LSpline->UniformSteps) LLinSegmentsInS=LSpline->NumOfDrawSteps;
	 else LLinSegmentsInS=LBezierCV->NumOfDrawSteps;
	}

	if(LClosestLinSegment<(LThisLinSegment+LLinSegmentsInS)) break;
	else { LThisLinSegment+=LLinSegmentsInS;LAfterCV++; }
       }

//printf("Insert after CV: %d  at %f,%f,%f\n", LAfterCV, LX, LY, LZ);fflush(stdout);

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

       LCVC=LAfterCV+1;
       if(LSpline->CVs==NULL) { LSpline->CVs=LBezierCV=(E3dBezierCV*)EMalloc(sizeof(E3dBezierCV)); }
       else
       {
	E3dBezierCV*	LTCVs=(E3dBezierCV*)ERealloc(LSpline->CVs, sizeof(E3dBezierCV)*(LSpline->NumOfCVs+1));

	if(LTCVs)
	{
	 LSpline->CVs=LTCVs;
	 LBezierCV=LTCVs+LCVC;

	 if(LCVC<(LSpline->NumOfCVs)) memmove(LBezierCV+1, LBezierCV, sizeof(E3dBezierCV)*(LSpline->NumOfCVs-LCVC));
	}
	else LBezierCV=NULL;
       }

       E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

       if(LBezierCV)
       {
	E3d_SplineCVDefault(E3dSPL_BEZIER, (void*)LBezierCV);
	LBezierCV->Position.X=LX;
	LBezierCV->Position.Y=LY;
	LBezierCV->Position.Z=LZ;

// With this part commented out, it's a neuron-drawer
// :)
	LBezierCV->Previous.X=LX;
	LBezierCV->Previous.Y=LY;
	LBezierCV->Previous.Z=LZ;

	LBezierCV->Next.X=LX;
	LBezierCV->Next.Y=LY;
	LBezierCV->Next.Z=LZ;

	LBezierCV->Constraint=E3dBEZ_C1;
	LBezierCV->Segment=E3dBEZ_CURVE;

	LSpline->NumOfCVs+=1;

	E3dp_PrintMessage(0, 4000, "Bezier CV: %f,%f,%f  added after CV %d", LX, LY, LZ, LAfterCV);

	E3dp_ActiveBezierCV=LBezierCV;
	E3dp_BezCVActiveSubCV=E3dBEZ_NEXT;
	_CVStartMoving(L3DWindow, LPtrX, LPtrY, _Model, LSpline, &(LBezierCV->Next));
	E3dp_PrintMessage(0, 0, "Point #%d (Next): %.4f, %.4f, %.4f", LAfterCV+1, LBezierCV->Next.X, LBezierCV->Next.Y, LBezierCV->Next.Z);

	LChanged=TRUE;
       }
      }
     break;

     case E3dSPL_CARDINAL:
      if(LClosestLinSegment!=-1)
      {
       E3dSplineCV*	LSplineCV=(E3dSplineCV*)(LSpline->CVs);
       unsigned int	LKN=LSpline->NumOfCVs, LAfterCV=0, LCVC, LThisLinSegment=0, LLinSegmentsInS;

//printf("Distance: %f  Segment: %d\n", LDistance, LClosestLinSegment);fflush(stdout);

       for(LC=0;LC<LKN;LC++, LSplineCV++)
       {
	if(LSpline->UniformSteps) LLinSegmentsInS=LSpline->NumOfDrawSteps;
	else LLinSegmentsInS=LSplineCV->NumOfDrawSteps;

	if(LClosestLinSegment<(LThisLinSegment+LLinSegmentsInS)) break;
	else { LThisLinSegment+=LLinSegmentsInS;LAfterCV++; }
       }

// If the Spline is not closed, the curve starts from the 1st CV (after the 0th)
//
       LAfterCV++;


//printf("Insert after CV: %d  at %f,%f,%f\n", LAfterCV, LX, LY, LZ);fflush(stdout);

       LCVC=LAfterCV+1;
       if(LSpline->CVs==NULL) { LSpline->CVs=LSplineCV=(E3dSplineCV*)EMalloc(sizeof(E3dSplineCV)); }
       else
       {
	E3dSplineCV*	LTCVs=(E3dSplineCV*)ERealloc(LSpline->CVs, sizeof(E3dSplineCV)*(LSpline->NumOfCVs+1));

	if(LTCVs)
	{
	 LSpline->CVs=LTCVs;
	 LSplineCV=LTCVs+LCVC;

	 if(LCVC<(LSpline->NumOfCVs)) memmove(LSplineCV+1, LSplineCV, sizeof(E3dSplineCV)*(LSpline->NumOfCVs-LCVC));
	}
	else LSplineCV=NULL;
       }

       if(LSplineCV)
       {
	E3d_SplineCVDefault(E3dSPL_CARDINAL, (void*)LSplineCV);
	LSplineCV->Position.X=LX;
	LSplineCV->Position.Y=LY;
	LSplineCV->Position.Z=LZ;

	LSpline->NumOfCVs+=1;

	E3dp_PrintMessage(0, 4000, "Spline CV: %f,%f,%f  added after CV %d", LX, LY, LZ, LAfterCV);

	E3dp_ActiveSplineCV=LSplineCV;
	_CVStartMoving(L3DWindow, LPtrX, LPtrY, _Model, LSpline, &(LSplineCV->Position));
	E3dp_PrintMessage(0, 0, "Point #%d %.4f, %.4f, %.4f", LSpline->NumOfCVs-1, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);

	LChanged=TRUE;
       }
      }
     break;
    }
   }
  break;
 }

 return(LChanged);
}


/*==============================================================*/
/* Add Spline CV pick-callback					*/
/*==============================================================*/
static void _PickCB_AddCV(E3dPickCallbackStruct* LPickStruct, int LNumOfItems, E3dItem* LPickedItems)
{
 if(LPickStruct->Reason==E3dCR_ABORT)
 {
  _SplineType=E3dSPL_NONE;
  _Model=NULL;
  _Geometry=NULL;
  _Spline=NULL;
  E3dp_PopMessage();
  return;
 }


 {
  E3dModel*	LModel=NULL;
  E3dSpline*	LSpline=_Spline;
  E3dCamera*	LCamera=LPickStruct->Camera;
  E3dMatrix	LMatrix;
  E3dCoordinate	LX, LY, LZ;
  E3dRay	LRay;
  EBool		LChanged=FALSE;

  E3dCoordinate	LRDX=LPickStruct->RayDirection.X, LRDY=LPickStruct->RayDirection.Y, LRDZ=LPickStruct->RayDirection.Z,
		LROX=LPickStruct->RayOrigin.X, LROY=LPickStruct->RayOrigin.Y, LROZ=LPickStruct->RayOrigin.Z;



  LRay.X=LPickStruct->RayOrigin.X;
  LRay.Y=LPickStruct->RayOrigin.Y;
  LRay.Z=LPickStruct->RayOrigin.Z;
  LRay.DirX=LPickStruct->RayDirection.X;
  LRay.DirY=LPickStruct->RayDirection.Y;
  LRay.DirZ=LPickStruct->RayDirection.Z;

// Intersect the ray cast by the pick algorithm with a plane that is perpendicular
// to the Camera's view direction and passes through the Camera's interest point
//
  {
   E3dCoordinate	LD,
			LCDX, LCDY, LCDZ,		// Camera Direction vector
			LT;				// t parameter

// Get D in the plane equation
//
   LCDX=LCamera->Direction.X;
   LCDY=LCamera->Direction.Y;
   LCDZ=LCamera->Direction.Z;
   LD=LCDX*LCamera->InterestPoint.X+LCDY*LCamera->InterestPoint.Y+LCDZ*LCamera->InterestPoint.Z;

// t is the parameter for the ray where it intersects the plane
//
   LT=(LD-LCDX*LROX-LCDY*LROY-LCDZ*LROZ)/(LCDX*LRDX+LCDY*LRDY+LCDZ*LRDZ);

   LX=LROX+LT*LRDX;
   LY=LROY+LT*LRDY;
   LZ=LROZ+LT*LRDZ;

   if(LPickStruct->SnapToX) LX=E3d_SnapCoordinateTo(LX, LPickStruct->SnapToXV);
   if(LPickStruct->SnapToY) LY=E3d_SnapCoordinateTo(LY, LPickStruct->SnapToYV);
   if(LPickStruct->SnapToZ) LZ=E3d_SnapCoordinateTo(LZ, LPickStruct->SnapToZV);
  }

  switch(LPickStruct->Button)
  {
   case Button1:
   case Button3:
    LChanged=TRUE;

    if(_Model==NULL)
    {
     LModel=E3d_ModelAllocate(_ModelName);
     if(LModel)
     {
      LSpline=E3d_SplineAllocate(_SplineType);
      if(LSpline)
      {
       LSpline->CastsShadows=FALSE;
       LSpline->ReceivesShadows=FALSE;
       E3d_ModelAppendGeometry(LModel, (E3dGeometry*)LSpline);
       E3d_SceneAddModelHrc(E3d_Scene, LModel);
       E3dp_SceneLayoutOnSchematics(E3d_Scene);

       LModel->Selection=E3dSEL_NODE;
       E3dp_TransformPanelUpdate(E3dDO_ALL, E3dDO_ALL, E3dDO_ALL, TRUE);

       _Model=LModel;
       _Geometry=(E3dGeometry*)LSpline;
       _Spline=LSpline;
       E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_SCHEMATICS);
      }
     }
    }
   break;
  }


  if(LSpline)
  {
// Find the Model that 'makes' this Spline selected and use the
// inverse of its LocalToWorldMatrix to go to local space...
//
   if(LModel==NULL) LModel=E3d_GeometryGetSelectingModel(_Geometry);

   if(LModel)
   {
    E3dCoordinate	mX=LX, mY=LY, mZ=LZ;

    E3d_MatrixInvert3x4(LModel->LocalToWorldMatrix, LMatrix);
    E3dM_MatrixTransform3x4(LMatrix, LX, LY, LZ);    
   }
   else E3d_MatrixLoadIdentity(LMatrix);

   switch(LPickStruct->Button)
   {
// Add CV after the last one
//
    case Button1:
     switch(LSpline->SplineType)
     {
      case E3dSPL_BEZIER:
       {
	E3dBezierCV*	LBezierCV;

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

	if(LSpline->CVs==NULL) { LSpline->CVs=LBezierCV=(E3dBezierCV*)EMalloc(sizeof(E3dBezierCV)); }
	else
	{
	 E3dBezierCV*	LTCVs=(E3dBezierCV*)ERealloc(LSpline->CVs, sizeof(E3dBezierCV)*(LSpline->NumOfCVs+1));

	 if(LTCVs)
	 {
	  LSpline->CVs=LTCVs;
	  LBezierCV=LTCVs+LSpline->NumOfCVs;
	 }
	 else LBezierCV=NULL;
	}

	E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

	if(LBezierCV)
	{
	 E3d_SplineCVDefault(E3dSPL_BEZIER, (void*)LBezierCV);
	 LBezierCV->Position.X=LX;
	 LBezierCV->Position.Y=LY;
	 LBezierCV->Position.Z=LZ;

// With this part commented out, it's a neuron-drawer
// :)
	 LBezierCV->Previous.X=LX;
	 LBezierCV->Previous.Y=LY;
	 LBezierCV->Previous.Z=LZ;

	 LBezierCV->Next.X=LX;
	 LBezierCV->Next.Y=LY;
	 LBezierCV->Next.Z=LZ;

	 E3dM_InitBezierCV(LBezierCV);

	 LSpline->NumOfCVs+=1;

	 E3dp_PrintMessage(0, 4000, "Bezier CV added to end: %f,%f,%f", LX, LY, LZ);

	 LChanged=TRUE;

	 E3dp_ActiveBezierCV=LBezierCV;
	 E3dp_BezCVActiveSubCV=E3dBEZ_NEXT;
	 _CVStartMoving(LPickStruct->MWindow, LPickStruct->PtrX, LPickStruct->PtrY, _Model, LSpline, &(LBezierCV->Next));

	 E3dp_PrintMessage(0, 0, "Point #%d (Next): %.4f, %.4f, %.4f", LSpline->NumOfCVs-1, LBezierCV->Next.X, LBezierCV->Next.Y, LBezierCV->Next.Z);
	}
       }
      break;

      default:
       {
	E3dSplineCV*	LSplineCV;

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

	if(LSpline->CVs==NULL) { LSpline->CVs=LSplineCV=(E3dSplineCV*)EMalloc(sizeof(E3dSplineCV)); }
	else
	{
	 E3dSplineCV*	LTCVs=(E3dSplineCV*)ERealloc(LSpline->CVs, sizeof(E3dSplineCV)*(LSpline->NumOfCVs+1));

	 if(LTCVs)
	 {
	  LSpline->CVs=LTCVs;
	  LSplineCV=LTCVs+LSpline->NumOfCVs;
	 }
	 else LSplineCV=NULL;
	}

	E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

	if(LSplineCV)
	{
	 LSplineCV->Position.X=LX;
	 LSplineCV->Position.Y=LY;
	 LSplineCV->Position.Z=LZ;

	 E3dM_InitSplineCV(LSplineCV);

	 LSpline->NumOfCVs+=1;


//	 E3dp_PrintMessage(0, 4000, "Spline CV added to end: %f,%f,%f", LX, LY, LZ);

	 E3dp_ActiveSplineCV=LSplineCV;
	 _CVStartMoving(LPickStruct->MWindow, LPickStruct->PtrX, LPickStruct->PtrY, _Model, LSpline, &(LSplineCV->Position));

	 E3dp_PrintMessage(0, 0, "Point #%d %.4f, %.4f, %.4f", LSpline->NumOfCVs-1, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);

	 LChanged=TRUE;
	}
       }
      break;
     }
    break;

    case Button2:		// Add CV between 2 existing ones
     if(LSpline->NumOfCVs<2)
     {
      E3dp_PrintMessage(0, 4000, "Spline must have at least 2 CVs to add between");
      return;
     }

     LChanged=_InsertBetween(LSpline, LPickStruct->MWindow, LMatrix, LPickStruct->PtrX, LPickStruct->PtrY, LX, LY, LZ, &LRay);
    break;

    case Button3:		// Add CV to the beginning
     switch(LSpline->SplineType)
     {
      case E3dSPL_BEZIER:
       {
	E3dBezierCV*	LBezierCV;

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

	if(LSpline->CVs==NULL) { LSpline->CVs=LBezierCV=(E3dBezierCV*)EMalloc(sizeof(E3dBezierCV)); }
	else
	{
	 E3dBezierCV*	LTCVs=(E3dBezierCV*)ERealloc(LSpline->CVs, sizeof(E3dBezierCV)*(LSpline->NumOfCVs+1));

	 if(LTCVs)
	 {
	  memmove(LTCVs+1, LTCVs, sizeof(E3dBezierCV)*LSpline->NumOfCVs);
	  LSpline->CVs=LTCVs;
	  LBezierCV=LTCVs;
	 }
	 else LBezierCV=NULL;
	}

	E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

	if(LBezierCV)
	{
	 E3d_SplineCVDefault(E3dSPL_BEZIER, (void*)LBezierCV);
	 LBezierCV->Position.X=LX;
	 LBezierCV->Position.Y=LY;
	 LBezierCV->Position.Z=LZ;

	 LBezierCV->Previous.X=LX;
	 LBezierCV->Previous.Y=LY;
	 LBezierCV->Previous.Z=LZ;

	 LBezierCV->Next.X=LX;
	 LBezierCV->Next.Y=LY;
	 LBezierCV->Next.Z=LZ;

	 E3dM_InitBezierCV(LBezierCV);

	 LSpline->NumOfCVs+=1;

	 E3dp_PrintMessage(0, 4000, "Bezier CV added to beginning: %f,%f,%f", LX, LY, LZ);

	 E3dp_ActiveBezierCV=LBezierCV;
	 E3dp_BezCVActiveSubCV=E3dBEZ_PREVIOUS;
	 _CVStartMoving(LPickStruct->MWindow, LPickStruct->PtrX, LPickStruct->PtrY, _Model, LSpline, &(LBezierCV->Previous));

	 E3dp_PrintMessage(0, 0, "Point #%d (Previous): %.4f, %.4f, %.4f", 0, LBezierCV->Previous.X, LBezierCV->Previous.Y, LBezierCV->Previous.Z);

	 LChanged=TRUE;
	}
       }
      break;

      default:
       {
	E3dSplineCV*	LSplineCV;

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

	if(LSpline->CVs==NULL) { LSpline->CVs=LSplineCV=(E3dSplineCV*)EMalloc(sizeof(E3dSplineCV)); }
	else
	{
	 E3dSplineCV*	LTCVs=(E3dSplineCV*)ERealloc(LSpline->CVs, sizeof(E3dSplineCV)*(LSpline->NumOfCVs+1));

	 if(LTCVs)
	 {
	  memmove(LTCVs+1, LTCVs, sizeof(E3dSplineCV)*LSpline->NumOfCVs);
	  LSpline->CVs=LTCVs;
	  LSplineCV=LTCVs;
	 }
	 else LSplineCV=NULL;
	}

	E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE);

	if(LSplineCV)
	{
	 LSplineCV->Position.X=LX;
	 LSplineCV->Position.Y=LY;
	 LSplineCV->Position.Z=LZ;

	 E3dM_InitSplineCV(LSplineCV);

	 LSpline->NumOfCVs+=1;

//	 E3dp_PrintMessage(0, 4000, "Spline CV added to beginning: %f,%f,%f", LX, LY, LZ);

	 E3dp_ActiveSplineCV=LSplineCV;
	 _CVStartMoving(LPickStruct->MWindow, LPickStruct->PtrX, LPickStruct->PtrY, _Model, LSpline, &(LSplineCV->Position));
	 E3dp_PrintMessage(0, 0, "Point #%d %.4f, %.4f, %.4f", LSpline->NumOfCVs-1, LSplineCV->Position.X, LSplineCV->Position.Y, LSplineCV->Position.Z);

	 LChanged=TRUE;
	}
       }
      break;
     }
    break;
   }

   if(LChanged) E3d_GeometryUpdateForDisplay(_Geometry, E3dGF_ALL);
  }

  if(LChanged) E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_ALL-E3dVM_SCHEMATICS);
 }
}


/*======================================*/
/* Draw Spline				*/
/*======================================*/
static void _MCB_SplineDraw(EguiItem LGUIItem, EPointer LClientData, EPointer LCallData)
{
 E3dp_ResetWorkModes();
 if(E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE)==0)
 {
  E3dp_PushMessage(0, 0, "Draw Spline  LMB: add CV after   MMB: add CV between CVs   RMB: add CV before  Esc: end mode");
 }

 _SplineType=(int)LClientData;

 switch(_SplineType)
 {
  case E3dSPL_BEZIER:
   strcpy(_ModelName, "BezSpline");
  break;

  case E3dSPL_BSPLINE:
   strcpy(_ModelName, "BSpline");
  break;

  case E3dSPL_CARDINAL:
   strcpy(_ModelName, "CardSpline");
  break;

  case E3dSPL_LINEAR:
   strcpy(_ModelName, "LinSpline");
  break;
 }
}


/*======================================*/
/* Menu callback			*/
/*======================================*/
static void _MCB_Splines(EguiItem LGUIItem, EPointer LClientData, EPointer LCallData)
{
 switch((int)LClientData)
 {
  case E3dCR_SPLINE_OPEN:
  case E3dCR_SPLINE_CLOSE:
   _OpenCloseSplines((int)LClientData);
  break;

  case E3dCR_SPLINE_ADD_POINTS:
   {
    E3dItem	LItem;

    E3dp_ResetWorkModes();
    if(E3d_SceneGetSingleSelection(E3d_Scene, &LItem, E3dITEM_SPLINE|E3dITEM_FACE_CONTOUR)!=E3dITEM_NONE)
    {
     E3dSplineItem*	LSplineItem=(E3dSplineItem*)(&LItem);

     _Model=LSplineItem->Model;
     _Geometry=LSplineItem->Geometry;
     _Spline=LSplineItem->Spline;

     if(E3dp_PickRequest(E3dITEM_WORLD_POSITION, _PickCB_AddCV, 0, FALSE)==0)
     {
      E3dp_PushMessage(0, 0, "Add point  LMB: add point after   MMB: add point between   RMB: add point before  Esc: end mode");
     }
    }
    else E3dp_PrintMessage(0, 3000, "Select a Spline to add CVs to");
   }
  break;
 }
}


/*==============================================================*/
/* Delete point pick-callback					*/
/*==============================================================*/
static void _PickCB_DeleteCV(E3dPickCallbackStruct* LPickStruct, int LNumOfItems, E3dItem* LPickedItems)
{
 if(LPickStruct->Reason==E3dCR_ABORT)
 {
  E3dp_PopMessage();
  return;
 }

 switch(LPickStruct->Button)
 {
  case Button1:
   if(LPickedItems)
   {
    E3dItem*	LItem=LPickedItems;

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

    switch(LItem->Type)
    {
     case E3dITEM_VERTEX:
     break;

     case E3dITEM_SPLINE_KEY:
     case E3dITEM_BEZIER_PREVIOUS:
     case E3dITEM_BEZIER_NEXT:
      {
       E3dSplineItem*	LSplineItem=(E3dSplineItem*)LItem;
       E3dSpline*	LSpline=LSplineItem->Spline;
       int		LNumOfCVs=LSpline->NumOfCVs, LCVIndex=LSplineItem->PointIndex;

       switch(LSpline->SplineType)
       {
	case E3dSPL_BEZIER:
	 {
	  E3dBezierCV*	LBezierCV=(E3dBezierCV*)(LSplineItem->CV);

	  if(LCVIndex<(LNumOfCVs-1)) memmove(LBezierCV, LBezierCV+1, sizeof(E3dBezierCV)*(LNumOfCVs-LCVIndex-1));
	 }
	break;

	default:
	 {
	  E3dSplineCV*	LSplineCV=(E3dSplineCV*)(LSplineItem->CV);

	  if(LCVIndex<(LNumOfCVs-1)) memmove(LSplineCV, LSplineCV+1, sizeof(E3dSplineCV)*(LNumOfCVs-LCVIndex-1));
	 }
	break;
       }

       LSpline->NumOfCVs-=1;
       E3d_GeometryUpdateForDisplay(LSplineItem->Geometry, E3dGF_ALL);


       E3dp_PrintMessage(0, 2500, "Deleted Spline CV #%d", LCVIndex);

       E3dp_Refresh3DWindows(E3dDF_ALL, E3dVM_NORMAL3D);
      }
     break;
    }

    E3dp_PickRequest(E3dITEM_POINT, _PickCB_DeleteCV, 0, FALSE);
   }
  break;

  case Button3:
   E3dp_PickEnd();E3dp_PopMessage();
  break;
 }
}


/*======================================*/
/* Menu callback			*/
/*======================================*/
static void _MCB_Delete(EguiItem LGUIItem, EPointer LClientData, EPointer LCallData)
{
 switch((int)LClientData)
 {
  case E3dCR_DELETE_POINTS:
   {
    E3dItem	LItem;

    if(E3d_SceneGetSingleSelection(E3d_Scene, &LItem, E3dITEM_MODEL)!=E3dITEM_NONE)
    {
//     E3dGeoItem*	LGeoItem=(E3dGeoItem*)(&LItem);

     E3dp_ResetWorkModes();

     if(E3dp_PickRequest(E3dITEM_POINT, _PickCB_DeleteCV, 0, FALSE)==0)
     {
      E3dp_PushMessage(0, 0, "Delete point  LMB: delete point  RMB or Esc key: end mode");
     }
    }
    else E3dp_PrintMessage(0, 3000, "Select an object to delete points from");
   }
  break;
 }
}


/*======================================*/
/* Entry point of the plugin		*/
/*======================================*/
int Plugin_Init()
{
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Close", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dCR_SPLINE_CLOSE);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Open", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dCR_SPLINE_OPEN);

 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Draw Linear", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineDraw, (EPointer)E3dSPL_LINEAR);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Draw Bezier", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineDraw, (EPointer)E3dSPL_BEZIER);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Draw BSpline", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineDraw, (EPointer)E3dSPL_BSPLINE);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Draw Cardinal", '\0', NULL, NULL, FALSE, NULL, _MCB_SplineDraw, (EPointer)E3dSPL_CARDINAL);
// _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Draw NURBS", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dSPL_NURBS);
// _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Convert to face", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dCR_SPLINE_DRAW);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Add point tool", '\0', NULL, NULL, FALSE, NULL, _MCB_Splines, (EPointer)E3dCR_SPLINE_ADD_POINTS);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Spline", "Delete point tool", '\0', NULL, NULL, FALSE, NULL, _MCB_Delete, (EPointer)E3dCR_DELETE_POINTS);
 _MenuButtons[_NumOfMenuButtons++]=EGUI_AddPushButton("Menu->Delete", "Delete point tool", '\0', NULL, NULL, FALSE, NULL, _MCB_Delete, (EPointer)E3dCR_DELETE_POINTS);



/*
 E_KeySetCallback("Close", (unsigned short)'O', EAltMask, EKeyPRESS, E3dKCB_Splines, E3dCR_SPLINE_CLOSE);
 E_KeySetCallback("Open", (unsigned short)'C', EAltMask, EKeyPRESS, E3dKCB_Splines, E3dCR_SPLINE_OPEN);
*/
 return(0);
}


/*======================================*/
/* Exit method of the plugin		*/
/*======================================*/
int Plugin_Exit()
{
 unsigned int	LC;

 for(LC=0;LC<_NumOfMenuButtons;LC++)
 {
  if(_MenuButtons[LC]) EGUI_DestroyItem(_MenuButtons[LC]);
 }

/*
 E_KeyRemoveCallback((unsigned short)'C', EAltMask, EKeyPRESS, E3dKCB_Splines, E3dCR_SPLINE_CLOSE);
 E_KeyRemoveCallback((unsigned short)'O', EAltMask, EKeyPRESS, E3dKCB_Splines, E3dCR_SPLINE_OPEN);
*/
 return(0);
}
