//================================================================
// E3D library
//
// Splines header file
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Mar-17 18:23:04
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _E3D_Spline_h
#define _E3D_Spline_h

#ifndef _EMalloc_h
#include <EMalloc.h>
#endif

#ifndef _E3D_Geometry_h
#include <E3D/Geometry.h>
#endif


#define	E3dCVALLOC_INCREMENT  4

#define E3dSPLINE_SHOW_TANGENTS_AT_END  1

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif


#define E3dBezierDEFAULT	0

// Continuity constraint
//
// Geometric continuity: in terms of the directions of tangent vectors only
//   G0: endpoints meet
//   G1: direction of tangent vectors agree
//
// Parametric continuity: defined in terms of direction and magnitude of tangent vectors
//   C0: endpoints meet
//   C1: tangent vectors (first derivative: velocity) are equal
//   C2: derivative of tangents (second derivative: acceleration) are equal
//
#define E3dBezierC0  1
#define E3dBezierC1  2
#define E3dBezierC2  3
#define E3dBezierG1  4
#define E3dBezierG2  5


// Sub-CV identifiers for TranslatePanel.c
//
enum
{
	E3dCV_NONE = 0,
	E3dCV_IN_TANGENT,
	E3dCV_POSITION,
	E3dCV_OUT_TANGENT
};


// Segment flags
//
#define E3dSegmentVISIBLE                       0x0001
#define E3dSegmentSELECTED                      0x0002
#define E3dSegmentARC_LENGTH_TESSELATE          0x0004  // Uniformly subdivide segment by length (vs. natural parameterization)

#define E3dSegmentCircularArcAUTO               0x0008  // For circular arc segments : compute the arc length automatically from the direction of the neighboring segments
#define E3dSegmentCircularArcVAALUE_IS_RADIUS   0x0010  // For circular arc segments. If true, ArcValue means radius, otherwise it's arc length in degrees. We let the user specify either, so we can accurately preserve the user-specified value vs. trying to calculate it.

#define E3dSegmentDEFAULT  (E3dSegmentVISIBLE|E3dSegmentARC_LENGTH_TESSELATE)

typedef struct
{
// User-settable values
//
	uint16_t       Shape;          // Segment shape E3dSplineLINEAR, E3dSplineBEZIER etc.
	uint16_t       Flags;
	int32_t        NumOfLinSteps;  // User-settable number of linear segment approximations for this segment.

	union
	{
		E3dCoordinate  Radius;       // For E3dSplineARC segments when Flags has E3dSegmentCircularArcVAALUE_IS_RADIUS. 0.0 : automatic, using adjacent segments to determine tangents. Positive number means arc to the right of the CV on counter-clocwwise CVs.
		E3dCoordinate  ArcLength;    // For E3dSplineARC segments. In degrees.
	};

	EcRGBAu8Color  Color;
	float          Thickness;

// Internal values
//
	E3dCoordinate*  LinearSegments;       // Piece-wise linear segment points, including the last one (the beginning point, if the Spline is closed and this CV is for the last segment), so the number of valid entries here always (NumOfLinearSegments + 1) * Dimensions
	EIndex          NumOfLinearSegments;  // Number of linear segments for this segment.

	E3dCoordinate   Length;
	E3dCoord4*      StepsForLength;int  NumOfStepsForLength;  // Linear segments used for (approximating) the length of this segment.
                                                            // The W component is the cumulative length at the end of a linear segment
} E3dSplineSegment;


//--------------------------------
// CV flags
//--------------------------------

// Saveables
//

#define E3dCV_SELECTED                  0x0001

// Runtime-only
//
#define E3dCV_PREV_ACTIVE       0x0100  // On a Bezier or Hermite curve, the 'Previous' position is active (for moving etc.)
#define E3dCV_ACTIVE            0x0200
#define E3dCV_NEXT_ACTIVE       0x0400  // On a Bezier or Hermite curve, the 'Next' position is active (for moving etc.)

// Temporary flags, used to mark CVs for faster/more memory-efficient algorithms
//
#define E3dCV_MARK0        0x1000
//--------------------------------

typedef struct
{
	E3dCoord3         Position, Previous, Next, Normal;
	uint16_t          CVFlags;
	uint16_t          Constraint;            // For tangents

	E3dSplineSegment  Segment;
} E3dSplineCV;
EListA_t(E3dSplineCV);


// This structure is used for taking "snapshots" of SplineCVs for interactive
// transforming and for Undo/Redo of transforming and deleting SplineCVs
//
typedef struct
{
	E3dCoord3  Position, Previous, Next;
	int        Index;    // Index in the original array
	uint16_t   CVFlags;
	uint16_t   SegmentFlags;
} E3dSnapshotSplineCV;



enum
{
	E3dSplineNONE = 0,
	E3dSplineLINEAR,
	E3dSplineBEZIER,
	E3dSplineHERMITE,
	E3dSplineBSPLINE,
	E3dSplineNURBS,
	E3dSplineCARDINAL,
	E3dSplineSTEP,
	E3dSplineARC    // Circular arc
};


#define caseE3dSplineHAS_TANGENTS\
	case E3dSplineBEZIER:\
	case E3dSplineHERMITE

#define caseE3dSplineGOES_THROUGH_CVS\
	case E3dSplineARC:\
	case E3dSplineBEZIER:\
	case E3dSplineCARDINAL:\
	case E3dSplineHERMITE:\
	case E3dSplineLINEAR

#define caseE3dSplineINDIRECT_CVS\
	case E3dSplineCARDINAL:\
	case E3dSplineBSPLINE

#define caseE3dSplineNO_INDIRECT_CVS\
	case E3dSplineARC:\
	case E3dSplineBEZIER:\
	case E3dSplineHERMITE:\
	case E3dSplineLINEAR

// Spline Flags
#define E3dSplfSHOW_CVS           0x0001  // Show CVs
#define E3dSplfSHOW_LINES         0x0002  // Show tangents (Bezier) and polyline (B-spline and Cardinal)
#define E3dSplfSEGMENT_COLORS     0x0004  // Segments have their own colors
#define E3dSplfSEGMENT_THICKNESS  0x0008  // Segments have their own relative thickness
#define E3dSplfSEGMENT_VISIBIITY  0x0010  // Some segments are visible, others aren't

#define E3dSplfDEFAULT  (E3dSplfSHOW_CVS|E3dSplfSHOW_LINES)

// What to save / load for Spline CVs
#define E3dCVsPOSITION               0x0001
#define E3dCVsPREVIOUS               0x0002
#define E3dCVsNEXT                   0x0004
#define E3dCVsNORMAL                 0x0008
#define E3dCVsCV_FLAGS               0x0010

#define E3dCVsSEGMENT_CONSTRAINT     0x0020
#define E3dCVsSEGMENT_SHAPE          0x0040
#define E3dCVsSEGMENT_FLAGS          0x0080
#define E3dCVsSEGMENT_NUMOFLINSTEPS  0x0100
#define E3dCVsSEGMENT_ARCRADIUS      0x0200

#define E3dCVsALL                    0x3FFF


typedef struct
{
	EListA_s(, uint8_t,  VertexArray);             // GPU vertex array, used for drawing the Spline if E3dGL_FLOAT_BITS != E3dFLOAT_BITS. Otherwise, Spline->SLinearSegments can be used directly
	EIndex               VertexArrayStride;        // CV size in the above, in bytes

	EListA_s(, uint8_t,  CVNormals);               // GPU vertex array for drawing CV normals (Position, Position + Normal * VectorScale)
} E3dSplineGPUData;


typedef struct
{
	E3dGeometryCore;

	int             Dimensions;

	EcRGBAf32Color  RGBAfColor;EBool  UseCustomColor;
	float           Thickness;

	EListA_s(, E3dSplineCV, CVs);          // Control Vertices


	EList(, E3dObject*, Faces);            // Faces that use this Spline as a contour, if any

	EObject*        AnimationChannel;      // If this Spline defines an animation curve, this points to the Channel

	E3dCoordinate   Length;                // Total length of the Spline
	int             NumOfLinSteps;         // Number of linear segments between 2 CVs, for drawing the Spline.
	int             NumOfStepsForLength;   // # of steps (linear segments) used for determining (approximating) the length of segments

	E3dCoordinate*  SLinearSegments;       // Piece-wise linear segment points, including the last one (the beginning point, if the Spline is closed), so the number of valid entries here is always (SNumOfLinearSegments + 1) * Dimensions
	EIndex          SNumOfLinearSegments;  // Total number of piece-wise linear segments.

	int             Phantom;               // Automatic phantom key (extremity) (CARDINAL, B-SPLINE)
	float           Tension;               // Tension of the curve (CARDINAL)
	int             Constraint;            // Default key constraint (BEZIER)
	int             SplineType;


	EBool           UniformLinSteps;       // Use Steps field of Spline for all segments
	EBool           Closed;                // Curve is closed (wrap-around)

	uint16_t        Flags;                 // Or-ed flags of E3dSplfSHOW_CVS etc.
	float           VectorDrawScale;       // For drawing Spline CV normals and tangents

	E3dSplineGPUData*  GPUData;
} E3dSpline, *E3dSplinePtr;
EListA_t(E3dSplinePtr);



enum
{
	E3dBevelARC = 1,
	E3dBevelCUSTOM
};

typedef struct
{
	int            Type;
	E3dSpline*     Profile;    // The bevel profile is specified with a Spline

	E3dCoordinate  Radius;
	EIndex         NumOfRoundingSteps;
	EBool          Invert, AddStartSmoother, AddEndSmoother;
} E3dBevel;



// Coordinate modes for E3dVectorInfo
//
enum
{
	E3dVTM_P1_P2 = 0,
	E3dVTM_P1_DIR,
	E3dVTM_P1_DIR_LENGTH
};


typedef struct
{
	E3dObjectComponentCore;

// E3dVectorInfo part
//
	E3dCoord3  P0, P1;
	char       CoordinateMode;
	EBool      ArrowOn;
} E3dVectorInfo;



extern E3dObjectClass   E3dSpline_class[];


//--------------------------------------------------------
// Inline functions
//--------------------------------------------------------

static inline EIndex	E3dSpline_NumOfLinearSegmentPoints(const E3dSpline* PSpline)
{
	return(PSpline->Closed ? PSpline->SNumOfLinearSegments : PSpline->SNumOfLinearSegments + 1);
}


static inline void E3dSplineCV_FreeContents(E3dSplineCV* PCV)
{
//printf("%s() %s:%d  %p  %p\n", __FUNCTION__, __FILE__, __LINE__, PCV, PCV->LinearSegments);fflush(stdout);
	if(PCV->Segment.LinearSegments) { EFree(PCV->Segment.LinearSegments);PCV->Segment.LinearSegments = NULL;PCV->Segment.NumOfLinearSegments = 0; }
	if(PCV->Segment.StepsForLength) { EFree(PCV->Segment.StepsForLength);PCV->Segment.StepsForLength = NULL;PCV->Segment.NumOfStepsForLength = 0; }
}


static inline void E3dSplineCV_Copy(const E3dSplineCV* PSrcCV, E3dSplineCV* PDstCV)
{
#define _Copy(pField)	PDstCV->pField = PSrcCV->pField;
	_Copy(Position);
	_Copy(Previous);
	_Copy(Next);
	_Copy(Normal);
	_Copy(CVFlags);
	_Copy(Constraint);

	_Copy(Segment.Shape);
	_Copy(Segment.Flags);
	_Copy(Segment.NumOfLinSteps);
	_Copy(Segment.Radius);
	_Copy(Segment.NumOfStepsForLength);
#undef _Copy
}


static inline void E3dSplineCV_SegmentInfoCopy(const E3dSplineCV* PSrcCV, E3dSplineCV* PDstCV)
{
#define _Copy(pField)  PDstCV->pField = PSrcCV->pField;
	_Copy(Constraint);

	_Copy(Segment.Shape);
	_Copy(Segment.Flags);
	_Copy(Segment.NumOfLinSteps);
	_Copy(Segment.Radius);

	_Copy(Segment.NumOfStepsForLength);
#undef _Copy
}

enum
{
	E3dSplineNormalAUTO = 0,
	E3dSplineNormalCV,
	E3dSplineNormalFIXED
};

#define E3dSplineAT_CVs  -1

#ifndef _E3D_Math_h
#include <E3D/Math.h>
#endif


//================================================================================
// Compute the tangent at, and the angle between two consecutive line segments
//================================================================================
static inline int E3dLinSegments_ComputeTangent(const EIndex PUC, const EIndex PUN, const E3dCoordinate* PPathPoints, const int PDimensions, const EPointerInt PStride, const EBool PClosed,  E3dCoord3* PTangentP, E3dCoordinate* PAngleCosineP)
{
	if(PUN > 0)
	{
		const E3dCoordinate*  LPathPrevPoint;
		const E3dCoordinate*  LPathNextPoint;

		if(PUN > 1)
		{
			const E3dCoordinate*  LPathPoint = (E3dCoordinate*)((uint8_t*)PPathPoints + PStride * PUC);
			EBool  LCheckLengths = TRUE;

			if(PUC == 0)
			{
				if(PClosed) LPathPrevPoint = PPathPoints + (PUN - 1) * PDimensions;
				else { LPathPrevPoint = LPathPoint;LCheckLengths = FALSE; }
				LPathNextPoint = (E3dCoordinate*)((uint8_t*)LPathPoint + PStride);
			}
			else
			{
				LPathPrevPoint = (E3dCoordinate*)((uint8_t*)LPathPoint - PStride);
				if(PClosed)
				{
					if(PUC == PUN - 1) LPathNextPoint = PPathPoints;
					else LPathNextPoint = (E3dCoordinate*)((uint8_t*)LPathPoint + PStride);
				}
				else if(PUC == PUN) { LPathNextPoint = LPathPoint;LCheckLengths = FALSE; }
				else LPathNextPoint = (E3dCoordinate*)((uint8_t*)LPathPoint + PStride);
			}

			if(LCheckLengths)
			{
				const E3dCoord3  E_Sub3i_aa(LPosToPrev,  LPathPrevPoint, LPathPoint);const E3dCoordinate  LPosToPrevLenSq = E_Dot3_vv(LPosToPrev, LPosToPrev);
				const E3dCoord3  E_Sub3i_aa(LPosToNext,  LPathNextPoint, LPathPoint);const E3dCoordinate  LPosToNextLenSq = E_Dot3_vv(LPosToNext, LPosToNext);
				if(LPosToPrevLenSq != LPosToNextLenSq && LPosToPrevLenSq > 0.0 && LPosToNextLenSq > 0.0)
				{
					const E3dCoordinate  LPosToPrevLen = E3d_Sqrt(LPosToPrevLenSq);const E3dCoord3  E_Div3i_vs(LNormalizedPosToPrev,  LPosToPrev, LPosToPrevLen);
					const E3dCoordinate  LPosToNextLen = E3d_Sqrt(LPosToNextLenSq);const E3dCoord3  E_Div3i_vs(LNormalizedPosToNext,  LPosToNext, LPosToNextLen);

					E_Sub3_vvp(LNormalizedPosToNext, LNormalizedPosToPrev,  PTangentP);

					goto TangentDone;
				}
			}
		}
		else
		{
			LPathPrevPoint = (E3dCoordinate*)((uint8_t*)PPathPoints);
			LPathNextPoint = (E3dCoordinate*)((uint8_t*)PPathPoints + PStride);
		}

// Compute tangent of the curve at the current point
//
		PTangentP->X = LPathNextPoint[E3dX] - LPathPrevPoint[E3dX];
		PTangentP->Y = LPathNextPoint[E3dY] - LPathPrevPoint[E3dY];
		PTangentP->Z = LPathNextPoint[E3dZ] - LPathPrevPoint[E3dZ];

TangentDone:
		E3d_Normalize3_p(PTangentP);  // Normalize tangent vector
		return(E_SUCCESS);
	}
	else
	{
		*PTangentP = E3dCoord3_s(1.0, 0.0, 0.0);
		return(E_NO_DATA);
	}
}


//--------------------------------------------------------
// Function prototypes
//--------------------------------------------------------
extern int            E3dSpline_DebugDrawing;

extern void           E3dSpline_Print(E3dSpline* PSpline);

extern E3dSpline*     E3dSpline_FromPositions(const E3dCoord3* PCVPositions, const EIndex PNumOfCVs, const EBool PClosed);

extern E3dCoordinate*  E3dSpline_GetUsAtCVs(E3dSpline* PSpline);

extern int            E3dSpline_GetTransforms(E3dSpline* PSpline, const E3dCoordinate* PMatrix,  const E3dCoordinate* PUs, const EIndex PUCount,  const uint8_t PNormalSource, const E3dCoord3* PNormal,  E3dCoordinate** PMatrices, EIndex* PMatrices_CountAllocated,  E3dCoord3** PPositions, E3dCoord3** PTangents, E3dCoord3** PNormalsP,  E3dCoordinate** PAngleCosinesP);

extern E3dSpline*     E3dSpline_ConvertToLinear(E3dSpline* PSpline, E3dSpline* PDSpline);

extern int            E3dSpline_Invert(E3dSpline* PSpline);
extern EIndex         E3dSpline_NumOfSelectedCVs(const E3dSpline* PSpline);

extern int            E3dSpline_CopyContents(const E3dSpline* PSrc, E3dSpline* PDst);

extern void	          E3dSplineCV_Init(E3dSplineCV* PCV, const int PSegmentType);
extern void           E3dSplineCVs_Init(E3dSplineCV* PCVs, const int PSegmentType, const EIndex PN);

extern E3dSplineCV*   E3dSplineCVs_New(const int PType, const EIndex PNumOfCVs);

extern void           E3dSpline_SetCVs(E3dSpline* PSpline, E3dSplineCV* PCVs, const EIndex PCount);
extern int            E3dSpline_CollectCVs(E3dSpline* PSpline, void** PTransformedPointsRet, unsigned int* PPointSizeRet, const EBool PAllCVs);

extern void*          E3dSpline_AddCV(E3dSpline* PSpline);
extern void*          E3dSpline_AddCVs(E3dSpline* PSpline, const EIndex PN);

extern void           E3dSpline_FreeCVs(E3dSpline* PSpline);

extern E3dCoordinate  E3dSpline_GetDrawingArcLengthParameters(const E3dSpline* PSpline, const E3dCoordinate PGapSize, const EBool PInvert,  E3dCoordinate* PTable, EIndex* PNumOfGapsP);

extern EBool          E3dSpline_GetBoundingBox(E3dSpline* PSpline, const uint32_t PWhatToInclude,  E3dBBox* PBBox);
extern EBool          E3dSpline_GetTransformedBoundingBox(E3dSpline* PSpline, const E3dMatrix PMatrix,  E3dBBox* PBBox);
extern EBool          E3dSpline_GetPerspectiveProjectedBoundingBox(E3dSpline* PSpline, const E3dMatrix PMatrix, const uint32_t PWhatToInclude,  E3dCoord2* PBBMin, E3dCoord2* PBBMax,  E3dCoord4* PMaxProjectedXYAbsValPoint);

extern EBool          E3dSpline_CreateLinearSegments(E3dSpline* PSpline);
extern void           E3dSpline_FreeRenderData(E3dSpline* PSpline);

extern E3dCoordinate  E3dSpline_UpdateSegmentLengths(E3dSpline* PSpline, EIndex PSteps);

extern int            E3dSpline_GetXYRange(const E3dSpline* PSpline,  E3dCoord2* PXY0, E3dCoord2* PXY1);

extern int            E3dSpline_GetYValueFromX(E3dSpline* PSpline, const E3dCoordinate PX, E3dCoordinate* PValue);

extern int            E3dSpline_Get3DPosition(E3dSpline* PSpline, E3dCoordinate PTime,  E3dCoord3* PPositionP, E3dCoord3* PTangentP, E3dCoord3* PNormalP);
extern int            E3dSpline_PlaneNormal(const E3dSpline* PSpline, const EBool PFromTesselation,  E3dCoord3* PNormal);

extern int            E3dSpline_GetClosestPointToPoint(const E3dSpline* PSpline,  const E3dCoord3* PPoint,  E3dCoord3* PPositionR, E3dCoord3* PTangentR, E3dCoord3* PNormalR);
extern EChangeFlags   E3dSpline_Unselect(E3dSpline* PSpline, const unsigned int PFlags);

extern void           E3dSpline_Transform(E3dSpline* PSpline, const E3dMatrix PMatrix);

extern void           E3d_CreateVectorArrow(E3dSplineCV* PCV,  const E3dCoordinate PX2, const E3dCoordinate PY2, const E3dCoordinate PZ2);

extern E3dCoord2*     E3d_CreateUnitArc(const EIndex PSides, const E3dCoordinate PStartAngle, const E3dCoordinate PEndAngle,  E3dCoord2* PPoints);
extern const E3dCoordinate  E3d_CircleArcBezierTangentLength(const EIndex PNSegments, const E3dCoordinate PAngleRange);

extern EIndex         E3dSpline_CVsShift(E3dSpline* PSpline, const EIndex PShift);

extern EIndex         E3dSpline_Load(const uint8_t** PPtr, E3dSpline* PSpline);
extern EIndex         E3dSpline_Save(E3dSpline* PSpline,  const uint16_t PCoordinateType, const uint16_t PDimensions, const uint16_t PCVContentFlags,  FILE* PFile, uint8_tListA* PBuffer);

extern EIndex         E3dSpline_DrawIntoImage(E3dSpline* PSpline,  const EIndex PX, const EIndex PY,  const float PScale,  EpImage* PImage);

extern int            E3dSplines_ReadFromCSV(const char* PBuffer, const EIndex PBufferSize,  E3dSpline*** PSplinesP, EIndex* PSplines_CountP);


extern void           E3dSpline_GLBufferUpdate(E3dSpline* PSpline, const EBool PShape, const EBool PCVNormals);

//--------------------------------------------------------
// Face.c
//--------------------------------------------------------



extern EChangeFlags   E3dSpline_CallOutputs(E3dSpline* PSpline, const EChangeFlags PChangeFlags, const EBool PClearFaceCaches);
extern EChangeFlags   E3dSpline_FullUpdate(E3dSpline* PSpline);


//--------------------------------------------------------
// SplineOps.c
//--------------------------------------------------------
extern void           E3dSplineOps_Init(void);
extern void           E3dSplineOps_Free(void);


extern void    E3dBevel_Init(E3dBevel* PBevel);
extern void    E3dBevel_Copy(const E3dBevel* PSBevel, E3dBevel* PDBevel);
extern void    E3dBevel_FreeContents(E3dBevel* PBevel);
extern void    E3dBevel_CreateSegments(E3dBevel* PBevel);


#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}	// Close scope of 'extern "C"' declaration
#endif

#endif  // _E3D_Spline_h
