//========================================================================
// E3D library
//
// - Math
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Jan-20 22:15:31
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//========================================================================
#ifndef _E3D_Math_h
#define _E3D_Math_h

#include <float.h>
#include <math.h>

#include <EMath.h>

#ifndef _E3D_Types_h
#include <E3D/Types.h>
#endif


// Return values for E3d_2DIntersection
//
enum
{
	E3dDISJOINT = 0,
	E3dINTERSECT,
	E3dINTERSECT_P1,
	E3dINTERSECT_P2,
	E3dINTERSECT_Q1,
	E3dINTERSECT_Q2,
	E3dSHARE_P1,
	E3dSHARE_P2,
	E3dSHARE_Q1,
	E3dSHARE_Q2,
	E3dPARALLEL,
	E3dOVERLAP,
	E3dOVERLAP_P1,
	E3dOVERLAP_P2,
	E3dOPPOSITE,
	E3dEQUAL
};


#if E3dFLOAT_BITS == 64
	#define E3d_Ceil   ceil
	#define E3d_Floor  floor
	#define E3d_FMod   fmod
	#define E3d_Abs    fabs
	#define E3d_FMin   fmin
	#define E3d_FMax   fmax
	#define E3d_ATan   atan
	#define E3d_ATan2  atan2
	#define E3d_Sqrt   sqrt
	#define E3d_Sin    sin
	#define E3d_ASin   asin
	#define E3d_Cos    cos
	#define E3d_ACos   acos
	#define E3d_Exp    exp
	#define E3d_LogN   log
	#define E3d_Pow    pow
	#define E3d_Tan    tan
	#define E3d_Finite(p)  isfinite(p)
#else
	#if E3dFLOAT_BITS == 32
		#define E3d_Ceil   ceilf
		#define E3d_Floor  floorf
		#define E3d_FMod   fmodf
		#define E3d_Abs    fabsf
		#define E3d_FMin   fmin
		#define E3d_FMax   fmaxf
		#define E3d_ATan   atanf
		#define E3d_ATan2  atan2f
		#define E3d_Sqrt   sqrtf
		#define E3d_Sin    sinf
		#define E3d_ASin   asinf
		#define E3d_Cos    cosf
		#define E3d_ACos   acosf
		#define E3d_Exp    expf
		#define E3d_LogN   logf
		#define E3d_Pow    powf
		#define E3d_Tan    tanf

		#ifdef __APPLE__  // Macs don't have finitef()
			#define E3d_Finite(p)  isfinite(p)
		#else
			#define E3d_Finite(p)  finitef(p)
		#endif  // __APPLE__
	#else
ERROR
	#endif
#endif



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

static inline E3dCoord3 E3d_Add3_vv(const E3dCoord3 P0, const E3dCoord3 P1)
{
	const E3dCoord3  LV = { .X= P0.X + P1.X, .Y= P0.Y + P1.Y, .Z= P0.Z + P1.Z };
	return(LV);
}

static inline E3dCoord3 E3d_Sub3_vv(const E3dCoord3 P0, const E3dCoord3 P1)
{
	const E3dCoord3  LV = { .X= P0.X - P1.X, .Y= P0.Y - P1.Y, .Z= P0.Z - P1.Z };
	return(LV);
}


static inline E3dCoord3 E3d_Div3_vs(const E3dCoord3 P0, const E3dCoordinate P1)
{
	const E3dCoord3  LV = { .X= P0.X / P1, .Y= P0.Y / P1, .Z= P0.Z / P1 };
	return(LV);
}


//================================================
// Reflect a ray around a normal
//================================================
static inline void E3d_Reflect(E3dCoord3* PDirection, const E3dCoord3 PNormal)
{
	const E3dCoordinate  LDirX = PDirection->X, LDirY = PDirection->Y, LDirZ = PDirection->Z,
	                     LNormalX = PNormal.X, LNormalY = PNormal.Y, LNormalZ = PNormal.Z, 
	                     LD = -(LNormalX * LDirX + LNormalY * LDirY + LNormalZ * LDirZ) * 2.0;

	PDirection->X = LNormalX * LD + LDirX;
	PDirection->Y = LNormalY * LD + LDirY;
	PDirection->Z = LNormalZ * LD + LDirZ;
}


#ifdef E3dSIMD
#define E3d_Reflect3_vvv(pDirectionIn, pNormal, pDirectionOut) do\
 {\
 const float	lMinusTwo = -2.0;\
\
__asm__ __volatile__\
     (\
               "movaps		%[DirectionIn], %%xmm0\n"\
       "	movaps		%[Normal], %%xmm1\n"\
       "	movaps		%%xmm1, %%xmm2\n"\
       "	movss		%[MinusTwo], %%xmm3\n"\
       "	mulps		%%xmm0, %%xmm2\n"		/* Dot product (1) X = src.X * src.X, Y = src.Y * src.Y, Z = src.Z * src.Z, W = src.W * src.W	*/\
       "	shufps		$00, %%xmm3, %%xmm3\n"		/* Copy .X to .[XYZW]	*/\
       "	haddps		%%xmm2, %%xmm2\n"		/*             (2) X = X * X + Y * Y, Y = Z * Z + W * W	*/\
       "	haddps		%%xmm2, %%xmm2\n"		/*             (3) X = X + Y	*/\
       "	mulss		%%xmm3, %%xmm2\n"		/* * -2.0			*/\
       "	shufps		$00, %%xmm2, %%xmm2\n"		/* Copy .X to .[XYZW]		*/\
       "	mulps		%%xmm2, %%xmm1\n"\
       "	addps		%%xmm0, %%xmm1\n"\
       "	movaps		%%xmm1, %[DirectionOut]\n"\
\
       : [DirectionOut]"=m" (pDirectionOut)\
       : [DirectionIn]"m" (pDirectionIn), [Normal]"m" (pNormal), [MinusTwo]"m" (lMinusTwo)\
       : "%xmm0", "%xmm1", "%xmm2"\
     );\
} while(0)
#else
#define E3d_Reflect3_vvv(pDirectionIn, pNormal, pDirectionOut) do\
{\
	const E3dCoordinate  lDirX = pDirectionIn.X, lDirY = pDirectionIn.Y, lDirZ = pDirectionIn.Z,\
	                     lNormalX = pNormal.X, lNormalY = pNormal.Y, lNormalZ = pNormal.Z, \
	                     LD = -(lNormalX * lDirX + lNormalY * lDirY + lNormalZ * lDirZ) * 2.0;\
\
	pDirectionOut.X = lNormalX * LD + lDirX;\
	pDirectionOut.Y = lNormalY * LD + lDirY;\
	pDirectionOut.Z = lNormalZ * LD + lDirZ;\
} while(0)
#endif

static inline void E3d_Reflect3_pvp(const E3dCoord3* PDirectionIn, const E3dCoord3 PNormal, E3dCoord3* PDirectionOut)
{
	const E3dCoordinate  LDirX = PDirectionIn->X, LDirY = PDirectionIn->Y, LDirZ = PDirectionIn->Z,
	                     LNormalX = PNormal.X, LNormalY = PNormal.Y, LNormalZ = PNormal.Z, 
	                     LD = -(LNormalX * LDirX + LNormalY * LDirY + LNormalZ * LDirZ) * 2.0;

	PDirectionOut->X = LNormalX * LD + LDirX;
	PDirectionOut->Y = LNormalY * LD + LDirY;
	PDirectionOut->Z = LNormalZ * LD + LDirZ;
}


#define E3d_Reflect3_vpv(pDirectionIn, pNormal, pDirectionOut) do\
{\
	const E3dCoordinate  lDirX = pDirectionIn.X, lDirY = pDirectionIn.Y, lDirZ = pDirectionIn.Z,\
	                     lNormalX = pNormal->X, lNormalY = pNormal->Y, lNormalZ = pNormal->Z, \
	                     LD = -(lNormalX * lDirX + lNormalY * lDirY + lNormalZ * lDirZ) * 2.0;\
\
	pDirectionOut.X = lNormalX * LD + lDirX;\
	pDirectionOut.Y = lNormalY * LD + lDirY;\
	pDirectionOut.Z = lNormalZ * LD + lDirZ;\
} while(0)


#define E3d_Abs3_vv(pA, pB)          do { pB.X = E3d_Abs(pA.X), pB.Y = E3d_Abs(pA.Y), pB.Z = E3d_Abs(pA.Z); } while(0)


//================================================
// Dot product of two vectors
//================================================
static inline E3dCoordinate E3d_Dot3(const E3dCoord3 PA, const E3dCoord3 PB)
{
	return(PA.X * PB.X  +  PA.Y * PB.Y  +  PA.Z * PB.Z);
}

static inline double E3d_Dot3_f64(const EFloat64_3 PA, const EFloat64_3 PB)
{
	return(PA.X * PB.X  +  PA.Y * PB.Y  +  PA.Z * PB.Z);
}

static inline E3dCoordinate E3d_Dot3_2(const E3dCoord3 PA)
{
	return(PA.X * PA.X  +  PA.Y * PA.Y  +  PA.Z * PA.Z);
}

static inline double E3d_Dot3_2_f64(const EFloat64_3 PA)
{
	return(PA.X * PA.X  +  PA.Y * PA.Y  +  PA.Z * PA.Z);
}

static inline E3dCoordinate E3d_Sign(const E3dCoordinate PV)
{
	return(PV < 0.0 ? -1.0 : 1.0);
}

static inline E3dCoordinate E3d_Clamp(const E3dCoordinate PV,  const E3dCoordinate PMin, const E3dCoordinate PMax)
{
	return(PV < PMin ? PMin : (PV > PMax ? PMax : PV));
}


static inline E3dCoord3 E3d_Cross3(const E3dCoord3 PA, const E3dCoord3 PB)
{
	const E3dCoord3  LResult = E3dCoord3_s(PA.Y * PB.Z - PA.Z * PB.Y, PA.Z * PB.X - PA.X * PB.Z, PA.X * PB.Y - PA.Y * PB.X);
	return(LResult);
}



//================================================
// Normalize 2D vector
//================================================
#define E3d_Normalize2_s(pX, pY) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt(pX * pX + pY * pY);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		pX *= lLInv;pY *= lLInv;\
	}\
	else\
	{\
		pX = pY = 0.0;\
	}\
} while(0)


//================================================
// Normalize 2D vector
//================================================
#define E3d_Normalize2_ss(pX, pY,  pOutX, pOutY) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt(pX * pX + pY * pY);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		pOutX = pX * lLInv;pOutY = pY * lLInv;\
	}\
	else\
	{\
		pOutX = pOutY = 0.0;\
	}\
} while(0)


//========================================
// Normalize 2D vector in a struct
//========================================
#define E3d_Normalize2_v(pVec) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt((pVec).X * (pVec).X + (pVec).Y * (pVec).Y);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pVec).X *= lLInv;(pVec).Y *= lLInv;\
	}\
	else\
	{\
		(pVec).X = (pVec).Y = 0.0;\
	}\
} while(0)


//========================================
// Normalize 2 components of a vector
// No null-vector check
//========================================
#define E3d_Normalize2_pc(pVec,  pX, pY) do\
{\
	const E3dCoordinate  lLInv = 1.0 / E3d_Sqrt((pVec)->pX * (pVec)->pX + (pVec)->pY * (pVec)->pY);\
	(pVec)->pX *= lLInv;(pVec)->pY *= lLInv;\
} while(0)


//========================================================
// Normalize 2D vector in a struct and tell its length
//========================================================
#define E3d_Length2_v(pIn, pLength) do\
{\
	const E3dCoordinate  lInX = pIn.X, lInY = pIn.Y;\
	const E3dCoordinate  lLength = E3d_Sqrt(lInX * lInX + lInY * lInY);\
\
	pLength = lLength;\
} while(0)


//========================================================
// Normalize 2D vector in a struct and tell its length
//========================================================
#define E3d_NormalizeGetLength2_vv(pIn, pOut, pLength) do\
{\
	const E3dCoordinate  lInX = pIn.X, lInY = pIn.Y;\
	const E3dCoordinate  lLength = E3d_Sqrt(lInX * lInX + lInY * lInY);if(lLength > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lLength;\
\
		pOut.X = lInX * lLInv;\
		pOut.Y = lInY * lLInv;\
	}\
	else\
	{\
		pOut.X = pOut.Y = 0.0;\
	}\
\
	pLength = lLength;\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_s(pX, pY, pZ) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt(pX * pX + pY * pY + pZ * pZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		pX *= lLInv;pY *= lLInv;pZ *= lLInv;\
	}\
	else\
	{\
		pX = pY = pZ = 0.0;\
	}\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_a(pInOut) do\
{\
	const E3dCoordinate  lX = pInOut[0], lY = pInOut[1], lZ = pInOut[2];\
	const E3dCoordinate  lL = E3d_Sqrt(lX * lX + lY * lY + lZ * lZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		pInOut[0] = lX * lLInv;pInOut[1] = lY * lLInv;pInOut[2] = lZ * lLInv;\
	}\
	else\
	{\
		pInOut[0] = pInOut[1] = pInOut[2] = 0.0;\
	}\
} while(0)


//================================================
// Normalize a 3D vector
//================================================
#define E3d_Normalize3_vv(pIn, pOut) do\
{\
	const typeof((pIn).X)  lInX = (pIn).X, lInY = (pIn).Y, lInZ = (pIn).Z;\
	const typeof((pIn).X)  lL = E3d_Sqrt(lInX * lInX + lInY * lInY + lInZ * lInZ);if(lL > 0.0)\
	{\
		const typeof((pIn).X)  lLInv = 1.0 / lL;\
		(pOut).X = lInX * lLInv;(pOut).Y = lInY * lLInv;(pOut).Z = lInZ * lLInv;\
	}\
	else\
	{\
		(pOut).X = (pOut).Y = (pOut).Z = 0.0;\
	}\
} while(0)


//================================================
// Normalize a 3D vector
//================================================
#define E3d_Normalize3_av(pIn, pOut) do\
{\
	const E3dCoordinate  lInX = (pIn)[0], lInY = (pIn)[1], lInZ = (pIn)[2];\
	const E3dCoordinate  lL = E3d_Sqrt(lInX * lInX + lInY * lInY + lInZ * lInZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pOut).X = lInX * lLInv;(pOut).Y = lInY * lLInv;(pOut).Z = lInZ * lLInv;\
	}\
	else\
	{\
		(pOut).X = (pOut).Y = (pOut).Z = 0.0;\
	}\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_vp(pIn, pOut) do\
{\
	const E3dCoordinate  lInX = (pIn).X, lInY = (pIn).Y, lInZ = (pIn).Z;\
	const E3dCoordinate  lL = E3d_Sqrt(lInX * lInX + lInY * lInY + lInZ * lInZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pOut)->X = lInX * lLInv;(pOut)->Y = lInY * lLInv;(pOut)->Z = lInZ * lLInv;\
	}\
	else\
	{\
		(pOut)->X = (pOut)->Y = (pOut)->Z = 0.0;\
	}\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_pv(pIn, pOut) do\
{\
	const E3dCoordinate  lInX = (pIn)->X, lInY = (pIn)->Y, lInZ = (pIn)->Z;\
	const E3dCoordinate  lL = E3d_Sqrt(lInX * lInX + lInY * lInY + lInZ * lInZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pOut).X = lInX * lLInv;(pOut).Y = lInY * lLInv;(pOut).Z = lInZ * lLInv;\
	}\
	else\
	{\
		(pOut).X = (pOut).Y = (pOut).Z = 0.0;\
	}\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_sv(pX, pY, pZ,  pOut) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt(pX * pX + pY * pY + pZ * pZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pOut).X = pX * lLInv;(pOut).Y = pY * lLInv;(pOut).Z = pZ * lLInv;\
	}\
	else\
	{\
		(pOut).X = (pOut).Y = (pOut).Z = 0.0;\
	}\
} while(0)


//================================================
// Normalize 3D vector
//================================================
#define E3d_Normalize3_sp(pX, pY, pZ,  pOut) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt(pX * pX + pY * pY + pZ * pZ);if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pOut)->X = pX * lLInv;(pOut)->Y = pY * lLInv;(pOut)->Z = pZ * lLInv;\
	}\
	else\
	{\
		(pOut)->X = (pOut)->Y = (pOut)->Z = 0.0;\
	}\
} while(0)


//#ifdef E3dSIMD
/*
// rsqrtss causes precision problems in _E3d_AddAreaLightSh (and probably elsewhere too)
//
#define E3d_Normalize3_s(pVec) do\
{\
__asm__ __volatile__\
     (\
               "movaps		%[src], %%xmm0\n"\
       "	movaps		%%xmm0, %%xmm1\n"\
       "	mulps		%%xmm1, %%xmm1\n"		@* Dot product (1) X = src.X * src.X, Y = src.Y * src.Y, Z = src.Z * src.Z, W = src.W * src.W	@/\
       "	haddps		%%xmm1, %%xmm1\n"		@*             (2) X = X * X + Y * Y, Y = Z * Z + W * W	@/\
       "	haddps		%%xmm1, %%xmm1\n"		@*             (3) X = X + Y	@/\
       "	rsqrtss		%%xmm1, %%xmm1\n"		@*             (4) sqrt(X)	@/\
       "	shufps		$00, %%xmm1, %%xmm1\n"		@* Copy xmm1.X to xmm1.[XYZW]	@/\
       "	mulps		%%xmm1, %%xmm0\n"\
       "	movaps		%%xmm0, %[dst]\n"\
\
       : [dst]"=m" (pVec)	@* output operands		@/\
       : [src]"m" (pVec)	@* input operands		@/\
       : "%xmm0", "%xmm1"	@* list of clobbered registers	@/\
     );\
} while(0)
*/
/*
// Check for division by zero!
// Due to the large latency of sqrtss and movss, this is probably not worth using, unless 2-4 normalization can be parallelized...
//
#define E3d_Normalize3_s(pVec) do\
{\
 const float	one = 1.0;\
__asm__ __volatile__\
     (\
               "movaps		%[src], %%xmm0\n"\
       "	movaps		%%xmm0, %%xmm1\n"\
       "	mulps		%%xmm1, %%xmm1\n"		@* Dot product (1) X = src.X * src.X, Y = src.Y * src.Y, Z = src.Z * src.Z, W = src.W * src.W	@/\
       "	haddps		%%xmm1, %%xmm1\n"		@*             (2) X = X * X + Y * Y, Y = Z * Z + W * W	@/\
       "	haddps		%%xmm1, %%xmm1\n"		@*             (3) X = X + Y	@/\
       "	sqrtss		%%xmm1, %%xmm1\n"		@*             (4) sqrt(X)	@/\
       "	movss		%[one], %%xmm2\n"\
       "	divss		%%xmm1, %%xmm2\n"		@*             (5) 1/sqrt(X)	@/\
       "	shufps		$00, %%xmm2, %%xmm2\n"		@* Copy .X to .XYZW		@/\
       "	mulps		%%xmm2, %%xmm0\n"\
       "	movaps		%%xmm0, %[dst]\n"\
\
       : [dst]"=m" (pVec)	@* output operands		@/\
       : [src]"m" (pVec),	@* input operands		@/\
	 [one]"m" (one)\
       : "%xmm0", "%xmm1", "%xmm2"	@* list of clobbered registers	@/\
     );\
} while(0)
#else
*/
//========================================
// Normalize 3D vector in a struct
//========================================
#define E3d_Normalize3_v(pVec) do\
{\
	const E3dCoordinate  lLenSq = (pVec).X * (pVec).X + (pVec).Y * (pVec).Y + (pVec).Z * (pVec).Z;if(lLenSq > 0.0)\
	{\
		const E3dCoordinate  lLen = E3d_Sqrt(lLenSq);\
		const E3dCoordinate  lLenInv = (E3dCoordinate)1.0 / lLen;\
		(pVec).X *= lLenInv;(pVec).Y *= lLenInv;(pVec).Z *= lLenInv;\
	}\
	else\
	{\
		(pVec).X = (pVec).Y = (pVec).Z = (E3dCoordinate)0.0;\
	}\
} while(0)
//#endif


#define E3d_Length3_v(pVec, pL) do\
{\
	const E3dCoordinate  lX = (pVec).X, lY = (pVec).Y, lZ = (pVec).Z;\
	pL = E3d_Sqrt(lX * lX + lY * lY + lZ * lZ);\
} while(0)


#define E3d_Length3_p(pVec, pL) do\
{\
	const E3dCoordinate  lX = (pVec)->X, lY = (pVec)->Y, lZ = (pVec)->Z;\
	pL = E3d_Sqrt(lX * lX + lY * lY + lZ * lZ);\
} while(0)


//========================================
// Normalize 3D vector in a struct
//========================================
#define E3d_Normalize3_p(pVec) do\
{\
	const E3dCoordinate  lL = E3d_Sqrt((pVec)->X * (pVec)->X + (pVec)->Y * (pVec)->Y + (pVec)->Z * (pVec)->Z);\
\
	if(lL > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lL;\
		(pVec)->X *= lLInv;(pVec)->Y *= lLInv;(pVec)->Z *= lLInv;\
	}\
	else\
	{\
		(pVec)->X = (pVec)->Y = (pVec)->Z = 0.0;\
	}\
} while(0)


//========================================================
// Length of a 3D vector
//========================================================
#define E3d_Length3_s(pX, pY, pZ)  E3d_Sqrt((pX) * (pX) + (pY) * (pY) + (pZ) * (pZ))

#define E3d_Length3_vr(p)  E3d_Sqrt((p.X) * (p.X) + (p.Y) * (p.Y) + (p.Z) * (p.Z))


//========================================================
// Normalize 3D vector in a struct and tell its length
//========================================================
#define E3d_NormalizeGetLength3_v(pVec, pLength) do\
{\
	pLength = E3d_Sqrt(pVec.X * pVec.X + pVec.Y * pVec.Y + pVec.Z * pVec.Z);if(pLength > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / pLength;\
\
		pVec.X *= lLInv;pVec.Y *= lLInv;pVec.Z *= lLInv;\
	}\
	else\
	{\
		pVec.X = pVec.Y = pVec.Z = 0.0;\
	}\
} while(0)


//========================================================
// Normalize 3D vector in a struct and tell its length
//========================================================
#define E3d_NormalizeGetLength3_vv(pIn, pOut, pLength) do\
{\
	const E3dCoord3      lIn = pIn;\
	const E3dCoordinate  lLength = E3d_Sqrt(lIn.X * lIn.X + lIn.Y * lIn.Y + lIn.Z * lIn.Z);if(lLength > 0.0)\
	{\
		const E3dCoordinate  lLInv = 1.0 / lLength;\
\
		pOut.X = lIn.X * lLInv;\
		pOut.Y = lIn.Y * lLInv;\
		pOut.Z = lIn.Z * lLInv;\
	}\
	else\
	{\
		pOut.X = pOut.Y = pOut.Z = 0.0;\
	}\
\
	pLength = lLength;\
} while(0)


//========================================================
// Normalize 3D vector in a struct and tell its length
//========================================================
#define E3d_NormalizeGetLengthAndLenghtInv3_v(pVec, pLength, pLengthInv) do\
{\
	pLength = E3d_Sqrt(pVec.X * pVec.X + pVec.Y * pVec.Y + pVec.Z * pVec.Z);if(pLength > 0.0)\
	{\
		const E3dCoordinate	lLInv = 1.0 / pLength;\
		pLengthInv = lLInv;\
\
		pVec.X *= lLInv;pVec.Y *= lLInv;pVec.Z *= lLInv;\
	}\
	else\
	{\
		pVec.X = pVec.Y = pVec.Z = 0.0;\
		pLengthInv = 0.0;\
	}\
} while(0)


#ifdef E3dSIMD
#define E3d_Bary3_vv(pIn,  pU, pV, pNUV,  pOut) do\
{\
__asm__ __volatile__\
     (\
               "movaps		%[src0], %%xmm0\n"\
       "	movaps		%[src1], %%xmm1\n"\
       "	movaps		%[src2], %%xmm2\n"\
       "	movss		%[w], %%xmm5\n"\
       "	movss		%[u], %%xmm3\n"\
       "	movss		%[v], %%xmm4\n"\
       "	shufps		$00, %%xmm5, %%xmm5\n"\
       "	shufps		$00, %%xmm3, %%xmm3\n"\
       "	shufps		$00, %%xmm4, %%xmm4\n"\
       "	mulps		%%xmm5, %%xmm0\n"\
       "	mulps		%%xmm3, %%xmm1\n"\
       "	mulps		%%xmm4, %%xmm2\n"\
       "	addps		%%xmm1, %%xmm0\n"\
       "	addps		%%xmm2, %%xmm0\n"\
       ""\
       "	movaps		%%xmm0, %[dst]\n"\
\
       : [dst]"=m" (pOut)	/* output operands		*/\
       : [src0]"m" (pIn[0]), [src1]"m" (pIn[1]), [src2]"m" (pIn[2]),  [u]"m" (pU), [v]"m" (pV), [w]"m" (pNUV)	/* input operands		*/\
       : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5"	/* list of clobbered registers	*/\
     );\
} while(0)
#else
#define E3d_Bary3_vv(pIn,  pU, pV, pNUV,  pOut) do\
{\
	pOut.X = (pIn)[0].X * pNUV + (pIn)[1].X * pU + (pIn)[2].X * pV;\
	pOut.Y = (pIn)[0].Y * pNUV + (pIn)[1].Y * pU + (pIn)[2].Y * pV;\
	pOut.Z = (pIn)[0].Z * pNUV + (pIn)[1].Z * pU + (pIn)[2].Z * pV;\
} while(0)
#endif


static inline E3dCoord3 E3d_Bary3_vvo(const void* PStructs,  const int PStructSize, const int PFieldOffset,  const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoordinate PNUV)
{
	const uint8_t*    LStructs = (const uint8_t*)PStructs;
	const E3dCoord3*  LV0 = (const E3dCoord3*)(LStructs + PStructSize * 0 + PFieldOffset);
	const E3dCoord3*  LV1 = (const E3dCoord3*)(LStructs + PStructSize * 1 + PFieldOffset);
	const E3dCoord3*  LV2 = (const E3dCoord3*)(LStructs + PStructSize * 2 + PFieldOffset);

	return(E3dCoord3_s(LV0->X * PNUV + LV1->X * PU + LV2->X * PV, LV0->Y * PNUV + LV1->Y * PU + LV2->Y * PV, LV0->Z * PNUV + LV1->Z * PU + LV2->Z * PV));
}


#ifdef E3dSIMD
#define E3dM_BaryNormalize3_v(pIn,  pU, pV, pNUV,  pOut) do\
{\
__asm__ __volatile__\
     (\
               "movaps		%[src0], %%xmm0\n"\
       "	movaps		%[src1], %%xmm1\n"\
       "	movaps		%[src2], %%xmm2\n"\
       "	movss		%[w], %%xmm5\n"\
       "	movss		%[u], %%xmm3\n"\
       "	movss		%[v], %%xmm4\n"\
       "	shufps		$00, %%xmm5, %%xmm5\n"\
       "	shufps		$00, %%xmm3, %%xmm3\n"\
       "	shufps		$00, %%xmm4, %%xmm4\n"\
       "	mulps		%%xmm5, %%xmm0\n"\
       "	mulps		%%xmm3, %%xmm1\n"\
       "	mulps		%%xmm4, %%xmm2\n"\
       "	addps		%%xmm1, %%xmm0\n"\
       "	addps		%%xmm2, %%xmm0\n"\
       ""\
       "	movaps		%%xmm0, %%xmm1\n"\
       "	mulps		%%xmm1, %%xmm1\n"		/* Dot product (1) X = src.X * src.X, Y = src.Y * src.Y, Z = src.Z * src.Z, W = src.W * src.W	*/\
       "	haddps		%%xmm1, %%xmm1\n"		/*             (2) X = X * X + Y * Y, Y = Z * Z + W * W	*/\
       "	haddps		%%xmm1, %%xmm1\n"		/*             (3) X = X + Y	*/\
       "	rsqrtss		%%xmm1, %%xmm1\n"		/*             (3) 1/sqrt(X)	*/\
       "	shufps		$00, %%xmm1, %%xmm1\n"		/* xmm1.X -> xmm1.[XYZW]	*/\
       "	mulps		%%xmm1, %%xmm0\n"\
       "	movaps		%%xmm0, %[dst]\n"\
\
       : [dst]"=m" (pOut)	/* output operands		*/\
       : [src0]"m" (pIn[0]), [src1]"m" (pIn[1]), [src2]"m" (pIn[2]),  [u]"m" (LU), [v]"m" (LV), [w]"m" (LNUV)	/* input operands		*/\
       : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5"	/* list of clobbered registers	*/\
     );\
} while(0)
#else
#define E3dM_BaryNormalize3_v(pIn,  pU, pV, pNUV,  pOut) do\
{\
	pOut.X = (pIn)[0].X * pNUV + (pIn)[1].X * pU + (pIn)[2].X * pV;\
	pOut.Y = (pIn)[0].Y * pNUV + (pIn)[1].Y * pU + (pIn)[2].Y * pV;\
	pOut.Z = (pIn)[0].Z * pNUV + (pIn)[1].Z * pU + (pIn)[2].Z * pV;\
\
	E3d_Normalize3_v(pOut);\
} while(0)
#endif


static inline void E3d_BaryNormalize3_sp(const void* PStructs,  const int PStructSize, const int PFieldOffset,  const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoordinate PNUV,  E3dCoord3* POut)
{
	const uint8_t*    LStructs = (const uint8_t*)PStructs;
	const E3dCoord3*  LV0 = (const E3dCoord3*)(LStructs + PStructSize * 0 + PFieldOffset);
	const E3dCoord3*  LV1 = (const E3dCoord3*)(LStructs + PStructSize * 1 + PFieldOffset);
	const E3dCoord3*  LV2 = (const E3dCoord3*)(LStructs + PStructSize * 2 + PFieldOffset);

	POut->X = LV0->X * PNUV + LV1->X * PU + LV2->X * PV;
	POut->Y = LV0->Y * PNUV + LV1->Y * PU + LV2->Y * PV;
	POut->Z = LV0->Z * PNUV + LV1->Z * PU + LV2->Z * PV;

	E3d_Normalize3_p(POut);
}


static inline void E3d_BaryFloats_sp(const void* PStructs,  const int PStructSize, const int PFieldOffset, const int PCount,  const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoordinate PNUV,  void* POut)
{
	const uint8_t*  LStructs = (const uint8_t*)PStructs;
	const float*    LV0 = (const float*)(LStructs + PStructSize * 0 + PFieldOffset);
	const float*    LV1 = (const float*)(LStructs + PStructSize * 1 + PFieldOffset);
	const float*    LV2 = (const float*)(LStructs + PStructSize * 2 + PFieldOffset);

	EIndex  LC = 0;float*  LOut = (float*)POut;for(;LC < PCount;LC++)
	{
		LOut[LC] = LV0[LC] * PNUV + LV1[LC] * PU + LV2[LC] * PV;
	}
}


static inline void E3d_BaryNormalize3_pp(const E3dCoord3* P0, const E3dCoord3* P1, const E3dCoord3* P2,  const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoordinate PNUV,  E3dCoord3* POut)
{
	POut->X = P0->X * PNUV + P1->X * PU + P2->X * PV;
	POut->Y = P0->Y * PNUV + P1->Y * PU + P2->Y * PV;
	POut->Z = P0->Z * PNUV + P1->Z * PU + P2->Z * PV;

	E3d_Normalize3_p(POut);
}

static inline E3dCoord3 E3d_BaryNormalize3_p(const E3dCoord3* P0, const E3dCoord3* P1, const E3dCoord3* P2,  const E3dCoordinate PU, const E3dCoordinate PV, const E3dCoordinate PNUV)
{
	E3dCoord3  LOut = E3dCoord3_s(P0->X * PNUV + P1->X * PU + P2->X * PV, P0->Y * PNUV + P1->Y * PU + P2->Y * PV, P0->Z * PNUV + P1->Z * PU + P2->Z * PV);

	E3d_Normalize3_v(LOut);
	return(LOut);
}


#define E3dM_Bary3_afs(pIn, pField,  pOut) do\
{\
	(pOut).X = (pIn[0]).pField.X * LNUV + (pIn[1]).pField.X * LU + (pIn[2]).pField.X * LV;\
	(pOut).Y = (pIn[0]).pField.Y * LNUV + (pIn[1]).pField.Y * LU + (pIn[2]).pField.Y * LV;\
	(pOut).Z = (pIn[0]).pField.Z * LNUV + (pIn[1]).pField.Z * LU + (pIn[2]).pField.Z * LV;\
} while(0)

#define E3dM_Bary3_afp(pIn, pField,  pOut) do\
{\
	(pOut)->X = (pIn[0]).pField.X * LNUV + (pIn[1]).pField.X * LU + (pIn[2]).pField.X * LV;\
	(pOut)->Y = (pIn[0]).pField.Y * LNUV + (pIn[1]).pField.Y * LU + (pIn[2]).pField.Y * LV;\
	(pOut)->Z = (pIn[0]).pField.Z * LNUV + (pIn[1]).pField.Z * LU + (pIn[2]).pField.Z * LV;\
} while(0)


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



static inline E3dCoordinate E3d_Det3x3_sss(E3dCoordinate m11, E3dCoordinate m12, E3dCoordinate m13,
	                  E3dCoordinate m21, E3dCoordinate m22, E3dCoordinate m23,
	                  E3dCoordinate m31, E3dCoordinate m32, E3dCoordinate m33)
{
	return(m11 * (m22 * m33 - m32 * m23) -
	       m21 * (m12 * m33 - m32 * m13) +
	       m31 * (m12 * m23 - m22 * m13));
}


//========================================================
// Determinant of a 3x3 matrix, specified by 3 vectors
//========================================================
static inline E3dCoordinate E3d_Det3x3_vvv(const E3dCoord3 P0, const E3dCoord3 P1, const E3dCoord3 P2)
{
	const E3dCoordinate  LA = P0.X, LB = P0.Y, LC = P0.Z;
	const E3dCoordinate  LD = P1.X, LE = P1.Y, LF = P1.Z;
	const E3dCoordinate  LG = P2.X, LH = P2.Y, LI = P2.Z;

	return(LA * LE * LI + LB * LF * LG - LC * LE * LG - LB * LD * LI - LA * LF * LH);
}


//================================================================================
// 2D point-in-triangle test with barycentric coordinates
//================================================================================
static inline int E3d_PointInTriangle2D(const E3dCoord2* P2Position,  const E3dCoordinate* PVertex0, const E3dCoordinate* PEdge1, const E3dCoordinate* PEdge2)
{
// Begin calculating determinant - also used to calculate U parameter
//
// Cross-product of ray direction and PEdge2
//
	E3dCoord2  LPVector;
	LPVector.X =  PEdge2[1];
	LPVector.Y = -PEdge2[0];

	const E3dCoordinate  LDet = E_Dot2_av(PEdge1, LPVector);
	const E3dCoordinate  LInvDet = 1.0 / LDet;

	E3dCoord3  LQVector;
	E3dCoord2  LTVector;

// Calculate distance from PVertex0 to ray origin
//
	LTVector.X = P2Position->X - PVertex0[0], LTVector.Y = P2Position->Y - PVertex0[1];

// Calculate U parameter and test bounds
//
	{
		const E3dCoordinate  LU = E_Dot2_vv(LTVector, LPVector) * LInvDet;
		if(LU < 0.0 || LU > 1.0) return(FALSE);

// Prepare to test V parameter
//
		E_Cross2_vva(LQVector,  LTVector, PEdge1);

// Calculate V parameter and test bounds
//
		const E3dCoordinate  LV = -LQVector.Z * LInvDet;

		if(LV < 0.0 || LU + LV > 1.0) return(FALSE);
	}

	return(TRUE);
}


//================================================================================
// 2D point-in-triangle test with barycentric coordinates
//================================================================================
static inline int E3d_PointInTriangle2D_UV(const E3dCoord2* P2Position,  const E3dCoordinate* PVertex0, const E3dCoordinate* PEdge1, const E3dCoordinate* PEdge2,  E3dCoordinate* PUV)
{
	E3dCoord2  LPVector;

// Begin calculating determinant - also used to calculate U parameter
//
// Cross-product of ray direction and PEdge2
//
	LPVector.X =  PEdge2[1];
	LPVector.Y = -PEdge2[0];

	const E3dCoordinate  LDet = E_Dot2_av(PEdge1, LPVector);

	if(LDet == 0.0) return(FALSE);	// 0-area triangle

// Calculate distance from PVertex0 to ray origin
//
	E3dCoord2  LTVector;LTVector.X = P2Position->X-PVertex0[0], LTVector.Y = P2Position->Y-PVertex0[1];

// Calculate U parameter and test bounds
//
	{
		const E3dCoordinate  LDetInv = 1.0 / LDet;
		const E3dCoordinate  LU = E_Dot2_vv(LTVector, LPVector) * LDetInv;

		if(LU < 0.0 || LU > 1.0) return(FALSE);

// Prepare to test V parameter
//
		E3dCoord3  LQVector;E_Cross2_vva(LQVector, LTVector, PEdge1);

// Calculate V parameter and test bounds
//
		{
			const E3dCoordinate  LV = -LQVector.Z*LDetInv;

			if(LV < 0.0 || LU + LV > 1.0) return(FALSE);

			PUV[0] = LU;
			PUV[1] = LV;
		}
	}

	return(TRUE);
}


static inline int E3d_PrincipalAxis_v(const E3dCoord3 PAxis)
{
	const E3dCoordinate  LX = E3d_Abs(PAxis.X), LY = E3d_Abs(PAxis.Y), LZ = E3d_Abs(PAxis.Z);

	if(LX >= LY)
	{
		if(LX >= LZ) return(E3dX);
		else return(E3dZ);
	}
	else
	{
		if(LY >= LZ) return(E3dY);
		else return(E3dZ);
	}
}


static inline int E3d_SmallestAxis_v(const E3dCoord3 PAxis)
{
	const E3dCoordinate  LX = E3d_Abs(PAxis.X), LY = E3d_Abs(PAxis.Y), LZ = E3d_Abs(PAxis.Z);

	if(LX <= LY)
	{
		if(LX <= LZ) return(E3dX);
		else return(E3dZ);
	}
	else
	{
		if(LY <= LZ) return(E3dY);
		else return(E3dZ);
	}
}


static inline void E3d_SmallestAndLargestAxis_v(const E3dCoord3 PAxis,  int8_t* PSmallestAxis, int8_t* PLargestAxis)
{
	const E3dCoordinate  LX = E3d_Abs(PAxis.X), LY = E3d_Abs(PAxis.Y), LZ = E3d_Abs(PAxis.Z);

	if(LX <= LY)
	{
		if(LX <= LZ) *PSmallestAxis = E3dX;
		else *PSmallestAxis = E3dZ;
	}
	else
	{
		if(LY <= LZ) *PSmallestAxis = E3dY;
		else *PSmallestAxis = E3dZ;
	}

	if(LX >= LY)
	{
		if(LX >= LZ) *PLargestAxis = E3dX;
		else *PLargestAxis = E3dZ;
	}
	else
	{
		if(LY >= LZ) *PLargestAxis = E3dY;
		else *PLargestAxis = E3dZ;
	}
}


static inline int E3d_PrincipalAxis_p(const E3dCoord3* PAxis)
{
	const E3dCoordinate  LX = E3d_Abs(PAxis->X), LY = E3d_Abs(PAxis->Y), LZ = E3d_Abs(PAxis->Z);

	if(LX >= LY)
	{
		if(LX >= LZ) return(E3dX);
		else return(E3dZ);
	}
	else
	{
		if(LY >= LZ) return(E3dY);
		else return(E3dZ);
	}
}


static inline void E3d_PolarToVector(const E3dCoordinate PPhi, const E3dCoordinate PTheta, E3dCoord3* PVector)
{
	const E3dCoordinate  LSinTh = E3d_Sin(PTheta);

	PVector->X = LSinTh * E3d_Cos(2.0 * PPhi);
	PVector->Y = LSinTh * E3d_Sin(2.0 * PPhi);
	PVector->Z = E3d_Cos(PTheta);
}


static inline E3dCoordinate E3d_HaltonSample(const int pB, int i)
{
	const E3dCoordinate  invb = 1.0 / pB;
	E3dCoordinate        x = 0.0, f = invb;

	while(i)
	{
		x += f * (E3dCoordinate)(i % pB);
		i /= pB;
		f *= invb;
	}
	return(x);
}


//========================================================
// Generate Tangent and Bitangent vectors for a normal
//========================================================
static inline void E3d_NormalComputeTangentAndBitangent(const E3dCoord3* PNormal, E3dCoord3* PTangent, E3dCoord3* PBitangent)
{
	E3dCoordinate  LVal, LMin;
	char           LMinAxis;


// Find the smallest component (minor axis) of the vector
//
	LMin = E3d_Abs(PNormal->X);LMinAxis = E3dX;
	LVal = E3d_Abs(PNormal->Y);if(LVal < LMin) { LMin = LVal;LMinAxis = E3dY; }
	LVal = E3d_Abs(PNormal->Z);if(LVal < LMin) { LMin = LVal;LMinAxis = E3dZ; }

	switch(LMinAxis)
	{
		case E3dX:
			PTangent->X =  0.0;
			PTangent->Y = -PNormal->Z;
			PTangent->Z =  PNormal->Y;
			E3d_Normalize2_pc(PTangent, Y, Z);
		break;

		case E3dY:
			PTangent->X = -PNormal->Z;
			PTangent->Y =  0.0;
			PTangent->Z =  PNormal->X;
			E3d_Normalize2_pc(PTangent, X, Z);
		break;

		case E3dZ:
		default:
			PTangent->X = -PNormal->Y;
			PTangent->Y =  PNormal->X;
			PTangent->Z =  0.0;
			E3d_Normalize2_pc(PTangent, X, Y);
		break;
	}

	E_Cross3_ppp(PTangent, PNormal, PBitangent);
}


//========================================================
// Generate Tangent vector for a normal
//========================================================
static inline void E3d_NormalComputeTangent(const E3dCoord3* PNormal, E3dCoord3* PTangent)
{
	E3dCoordinate  LVal, LMin;
	char           LMinAxis;

// Find the smallest component (minor axis) of the vector
//
	LMin = E3d_Abs(PNormal->X);LMinAxis = E3dX;
	LVal = E3d_Abs(PNormal->Y);if(LVal < LMin) { LMin = LVal;LMinAxis = E3dY; }
	LVal = E3d_Abs(PNormal->Z);if(LVal < LMin) { LMin = LVal;LMinAxis = E3dZ; }

	switch(LMinAxis)
	{
		case E3dX:
			PTangent->X =  0.0;
			PTangent->Y = -PNormal->Z;
			PTangent->Z =  PNormal->Y;
			E3d_Normalize2_pc(PTangent, Y, Z);
		break;

		case E3dY:
			PTangent->X = -PNormal->Z;
			PTangent->Y =  0.0;
			PTangent->Z =  PNormal->X;
			E3d_Normalize2_pc(PTangent, X, Z);
		break;

		case E3dZ:
		default:
			PTangent->X = -PNormal->Y;
			PTangent->Y =  PNormal->X;
			PTangent->Z =  0.0;
			E3d_Normalize2_pc(PTangent, X, Y);
		break;
	}
}


//================================================
// Uniformly sample a disk
//================================================
static inline E3dCoord2 E3d_SampleDisk(const E3dCoordinate PRand1, const E3dCoordinate PRand2)
{
// Sampling a disc is like the cosine-weighted hemisphere sampling, but with leaving Z = 0
//
	const E3dCoordinate  LCosTheta = E3d_Sqrt(1.0 - PRand1), LTheta = E3d_ACos(LCosTheta), LSinTheta = E3d_Sin(LTheta),
	                     LPhi = PRand2 * 2.0 * E3dPI;

	const E3dCoordinate  LTangentX   = E3d_Cos(LPhi) * LSinTheta;
	const E3dCoordinate  LBitangentY = E3d_Sin(LPhi) * LSinTheta;

	E3dCoord2  LPointOnDisk;
	LPointOnDisk.X = LTangentX;
	LPointOnDisk.Y = LBitangentY;

	return(LPointOnDisk);
}


//================================================================================
// Uniformly sample directions on a sphere
//
// Arguments
//  PU            Number between 0.0 and 1.0
//  PV            Number between 0.0 and 1.0
//  PDirection    Pointer to where the result should be stored
//
// Description
//  Returns a unit vector, representing a directon that uniformly samples the
//  surface of a unit sphere, if PU and PV uniformly sample a unit square.
//================================================================================
static inline void E3d_SampleSphere(const E3dCoordinate PU, const E3dCoordinate PV,  E3dCoord3* PDirection)
{
	const E3dCoordinate  LLon = 2.0 * E3d_ACos(E3d_Sqrt(1.0 - PU));
	const E3dCoordinate  LLat = 2.0 * E3dPI * PV;
	const E3dCoordinate  LSinLon = E3d_Sin(LLon);

	PDirection->X = LSinLon * E3d_Cos(LLat);
	PDirection->Y = LSinLon * E3d_Sin(LLat);
	PDirection->Z = E3d_Cos(LLon);
}



//================================================================
// Multiply two complex numbers
//================================================================
static inline void E3d_ComplexMul(const E3dCoord2* PIn0, const E3dCoord2* PIn1, E3dCoord2* POut)
{
	const E3dCoordinate  r = PIn0->X * PIn1->X - PIn0->Y * PIn1->Y, i = PIn0->X * PIn1->Y + PIn0->Y * PIn1->X;

	POut->X = r;		// i^2 = -1, so PIn0->Y * PIn1->Y is a real number...
	POut->Y = i;
}


//================================================================
// Divide two complex numbers
//================================================================
static inline void E3d_ComplexDiv(const E3dCoord2* PNumerator, const E3dCoord2* PDenominator, E3dCoord2* POut)
{
	E3dCoord2  LDenominatorConjugate, L0, L1;

	LDenominatorConjugate.X =  PDenominator->X;
	LDenominatorConjugate.Y = -PDenominator->Y;
	E3d_ComplexMul(PNumerator,   &LDenominatorConjugate, &L0);
	E3d_ComplexMul(PDenominator, &LDenominatorConjugate, &L1);

	{
		const E3dCoordinate	LDenomRealInv = 1.0 / L1.X;

		POut->X = L0.X * LDenomRealInv;
		POut->Y = L0.Y * LDenomRealInv;
	}
}


//================================================================
// Compute square root of a complex number
//================================================================
static inline void E3d_ComplexSqrt(E3dCoord2* PIn, E3dCoord2* POut)
{
	const E3dCoordinate  r = E3d_Sqrt(PIn->X * PIn->X + PIn->Y * PIn->Y), y = E3d_Sqrt((r - PIn->X) * 0.5);

	POut->X = PIn->Y * 0.5 / y;POut->Y = y;
}




#define E3dSQRT3	1.7320508075688772935
#define E3dINV3		(1.0 / 3.0)
#define E3dINV27	(1.0 / 27.0)

enum
{
	E3dCUBIC_EQUATION_3REAL_ROOTS,
	E3dCUBIC_EQUATION_3IDENTICAL_REAL_ROOTS,
	E3dCUBIC_EQUATION_1REAL_ROOT_2COMPLEX_ROOTS
};


//================================================================
// Solve cubic equation of the form:
// a*X^3 + b*X^2 + c * X + d = 0
// The roots may be complex numbers, so we use E3dCoord2s,
// where X is the real part and Y is the imaginary part
//================================================================
static inline int E3d_SolveCubic(const E3dCoordinate a, const E3dCoordinate b, const E3dCoordinate c, const E3dCoordinate d,  E3dCoord2* x1, E3dCoord2* x2, E3dCoord2* x3)
{
// These are very sensitive to precision, so must be at least doubles
// On Athlon-64 with SSE3, which supports 64-bit floats, GCC defaults to SSE, so we need the -mfpmath=387
// compiler flag to force 387 FPU usage, because it stores temporaries in 80 bits.
//
	const double	       f = (3.0 * c / a - b * b / (a * a)) * E3dINV3, g = (2.0 * b * b * b / (a * a * a) - 9.0 * b * c / (a * a) + 27.0 * d / a) * E3dINV27;
	const E3dCoordinate  h = g * g * 0.25 + f * f * f * E3dINV27;


//printf("%s() f=%lf g=%lf h=%lf\n", __FUNCTION__, f, g, h);fflush(stdout);
	if(h > 0.0)
	{
// One real root, two complex roots
//
		const E3dCoordinate  T0 = -(g * 0.5), T1 = sqrt(h),
				R = T0 + T1, S = pow(R, E3dINV3), T = T0-T1, U = T > 0.0 ? pow(T, E3dINV3) : -pow(-T, E3dINV3),  // pow() can't handle x < 0 if the power is not an integer...
				T2 = b / (3.0 * a),
				R0 = -(S + U)*0.5 - T2, I0 = (S - U) * E3dSQRT3 * 0.5;


//printf("%s() R=%lf S=%lf T=%lf U=%lf\n", __FUNCTION__, R, S, T, U);fflush(stdout);

		x1->X = S + U - T2;x1->Y = 0.0;
		x2->X = R0;x2->Y =  I0;
		x3->X = R0;x3->Y = -I0;
		return(E3dCUBIC_EQUATION_1REAL_ROOT_2COMPLEX_ROOTS);
	}
	else if(f == 0.0 && g == 0.0 && h == 0.0)
	{
		const E3dCoordinate  T0 = d / a, X1 = T0 > 0.0 ? -pow(T0, E3dINV3) : pow(-T0, E3dINV3);  // pow() can't handle x < 0 if the power is not an integer...


// All 3 roots are real and equal
//
		x1->X = X1;x1->Y = 0.0;
		x2->X = X1;x2->Y = 0.0;
		x3->X = X1;x3->Y = 0.0;
		return(E3dCUBIC_EQUATION_3IDENTICAL_REAL_ROOTS);
	}
	else
	{
// These are very sensitive to precision, so they should be doubles. Don't change sqrt() to sqrtf()!
//
		const	double  i = sqrt(g * g * 0.25 - h), j = pow(i, E3dINV3);
		const	double  kthird = acos(-g * 0.5 / i) * E3dINV3, l = -j, m = cos(kthird), n = E3dSQRT3 * sin(kthird);
		const	double  p = -b/(3.0 * a);

//printf("%s()-01 i=%f j=%f k=%f l=%f m=%f n=%f p=%f\n", __FUNCTION__, i, j, kthird*3.0, l, m, n, p);fflush(stdout);

		x1->X = 2.0 * j * m + p;x1->Y = 0.0;
		x2->X = l * (m + n) + p;x2->Y = 0.0;
		x3->X = l * (m - n) + p;x3->Y = 0.0;
		return(E3dCUBIC_EQUATION_3REAL_ROOTS);
	}
}



//================================================================
// Solve standard-form quartic equation (A=1 for speed, so you'll need to divide across with A before calling!)
// The roots may be complex numbers, so we use E3dCoord2s,
// where X is the real part and Y is the imaginary part
//================================================================
static inline void E3d_SolveQuartic(const E3dCoordinate b, const E3dCoordinate c, const E3dCoordinate d, const E3dCoordinate e,  E3dCoord2* X1, E3dCoord2* X2, E3dCoord2* X3, E3dCoord2* X4)
{
	const E3dCoordinate  f = c - (3.0 * b * b * 0.125), g = d + (b * b * b * 0.125) - (b * c * 0.5), h = e - (3 * b * b * b * b / 256.0) + (b * b * c / 16.0) - (b * d * 0.25);
	E3dCoord2            Y1, Y2, Y3;


//printf("Coeffs b=%f c=%f d=%f e=%f  temps: f=%f g=%f h=%f\n", b, c, d, e, f, g, h);fflush(stdout);
//printf("Cubic coeffs: %f %f %f %f\n", 1.0, f/2.0, (f*f-4*h)/16.0, -g*g/64.0);fflush(stdout);
	switch(E3d_SolveCubic(1.0, f*0.5, (f*f-4*h)/16.0, -g*g/64.0, &Y1, &Y2, &Y3))
	{
//  case E3dCUBIC_EQUATION_3IDENTICAL_REAL_ROOTS:
//printf("%s() E3dCUBIC_EQUATION_3IDENTICAL_REAL_ROOTS\n", __FUNCTION__);fflush(stdout);
//  break;

		case E3dCUBIC_EQUATION_3REAL_ROOTS:
		default:
			{
				const E3dCoordinate  s = b * 0.25;
				E3dCoordinate        p, q, r;


//printf("%s() E3dCUBIC_EQUATION_3REAL_ROOTS\n", __FUNCTION__);fflush(stdout);
// Let 'p' and 'q' be the square roots of ANY 2 non-zero roots (Y1 Y2 or Y3)
//
				if(Y1.X >= 0.0)
				{
					p = sqrt(Y1.X);

					if(Y2.X >= 0.0)
					{
						q = sqrt(Y2.X);
						r = -g * 0.125 / (p * q);
					}
					else if(Y3.X >= 0.0)
					{
						q = sqrt(Y3.X);
						r = -g * 0.125 / (p * q);
					}
					else q = r = 0.0;
				}
				else if(Y2.X >= 0.0)
				{
					p = sqrt(Y2.X);

					if(Y3.X >= 0.0)
					{
						q = sqrt(Y3.X);
						r = -g * 0.125 / (p * q);
					}
					else q = r = 0.0;
				}
				else
				{
					if(Y3.X >= 0.0) p = sqrt(Y3.X);
					else p = 0.0;

					q = r = 0.0;
				}


				X1->X = p + q + r -s;X1->Y = 0.0;
				X2->X = p - q - r -s;X2->Y = 0.0;
				X3->X = -p + q - r -s;X3->Y = 0.0;
				X4->X = -p - q + r -s;X4->Y = 0.0;
			}
		break;

		case E3dCUBIC_EQUATION_1REAL_ROOT_2COMPLEX_ROOTS:
			{
				const E3dCoordinate	s = b * 0.25;

#define EPSILON	0.000001

//printf("%s() E3dCUBIC_EQUATION_1REAL_ROOT_2COMPLEX_ROOTS\n", __FUNCTION__);fflush(stdout);
// Let 'p' and 'q' be the square roots of ANY 2 non-zero roots (Y1 Y2 or Y3)
// If the cubic equation has 1 real and 2 complex roots, we'll pick the complex roots, unless they are 0
//
				if((E3d_Abs(Y2.X) >= EPSILON) || (E3d_Abs(Y2.Y) >= EPSILON))
				{
					E3dCoord2	p, q, r, pq;

					E3d_ComplexSqrt(&Y2, &p);

					if((E3d_Abs(Y3.X) >= EPSILON) || (E3d_Abs(Y3.Y) >= EPSILON))
					{
						E3d_ComplexSqrt(&Y3, &q);
						E3d_ComplexMul(&p, &q, &pq);
						r.X = -g*0.125;r.Y = 0.0;E3d_ComplexDiv(&r, &pq, &r);
					}
					else
					{
						E3d_ComplexSqrt(&Y3, &q);
						E3d_ComplexMul(&p, &q, &pq);
						r.X = -g*0.125;r.Y = 0.0;E3d_ComplexDiv(&r, &pq, &r);
					}

// Real part of the roots
//
					X1->X = p.X + q.X + r.X -s;
					X2->X = p.X - q.X - r.X -s;
					X3->X = -p.X + q.X - r.X -s;
					X4->X = -p.X - q.X + r.X -s;

// Imaginary part of the roots
//
					X1->Y = p.Y + q.Y + r.Y;	// s is a real number, so we won't add/subtract it from the imaginary part of the roots
					X2->Y = p.Y - q.Y - r.Y;
					X3->Y = -p.Y + q.Y - r.Y;
					X4->Y = -p.Y - q.Y + r.Y;
				}
				else
				{
					const E3dCoordinate  p = sqrt(Y1.X), q = 0.0, r = 0.0;

					X1->X = p + q + r -s;X1->Y = 0.0;
					X2->X = p - q - r -s;X2->Y = 0.0;
					X3->X = -p + q - r -s;X3->Y = 0.0;
					X4->X = -p - q + r -s;X4->Y = 0.0;
				}

#undef EPSILON
			}
		break;
	}
}


static inline E3dCoordinate E3d_ClampMinMax_Coordinate(const E3dCoordinate PX,  const E3dCoordinate PMin, const E3dCoordinate PMax)
{
	return(PX < PMin ? PMin : (PX > PMax ? PMax : PX));
}


extern const E3dCoord3  E3d_UnitBoxPoints[];


//----------------------------------------------------------------
// Function prototypes
//----------------------------------------------------------------
extern int         E3d_PrincipalAxisE(const E3dCoord3* PAxis, const E3dCoordinate PEpsilon);

extern void        E3dBBox_Transform(const E3dBBox* PSrcBBox, const E3dMatrix PMatrix,  E3dBBox* PDstBBox);

extern void        E3d_Slerp3D(const E3dCoord3* P0, const E3dCoord3* P1, const E3dCoordinate PT, E3dCoord3* PResult);

extern void        E3d_TriangleNormal(const E3dCoord3* PPoint0, const E3dCoord3* PPoint1, const E3dCoord3* PPoint2,  E3dCoord3* PNormal);

static inline E3dGLCoord3 E3dGL_TriangleNormal(const E3dGLCoord3* PPoint0, const E3dGLCoord3* PPoint1, const E3dGLCoord3* PPoint2)
{
	const E3dGLCoord3      L0 = *PPoint0;
	const E3dGLCoord3      L1 = *PPoint1;
	const E3dGLCoord3      L2 = *PPoint2;

	const E3dGLCoordinate  LX = -((L2.Y - L1.Y) * (L2.Z - L0.Z) - (L2.Z - L1.Z) * (L2.Y - L0.Y));
	const E3dGLCoordinate  LY =   (L2.X - L1.X) * (L2.Z - L0.Z) - (L2.Z - L1.Z) * (L2.X - L0.X);
	const E3dGLCoordinate  LZ = -((L2.X - L1.X) * (L2.Y - L0.Y) - (L2.Y - L1.Y) * (L2.X - L0.X));

	E3dGLCoord3  LNormal;
	E3d_Normalize3_sv(LX, LY, LZ,  LNormal);
	return(LNormal);
}


#define E3d_DirectionVector3_vvv(pPoint0, pPoint1,  pDst) do\
{\
	(pDst).X = (pPoint1).X - (pPoint0).X;\
	(pDst).Y = (pPoint1).Y - (pPoint0).Y;\
	(pDst).Z = (pPoint1).Z - (pPoint0).Z;\
	E3d_Normalize3_v(pDst);\
} while(0)


#define E3d_Equal3_vv(p0, p1) ((p0).X == (p1).X && (p0).Y == (p1).Y && (p0).Z == (p1).Z)

static inline EBool E3dGLCoord3_Equal(const E3dGLCoord3 PPoint0, const E3dGLCoord3 PPoint1)
{
	return(PPoint0.X == PPoint1.X);
}


extern E3dCoordinate  E3d_GetAngle2D_deg(const E3dCoordinate PXPos, const E3dCoordinate PYPos);
extern E3dCoordinate  E3d_GetAngle2D_rad(const E3dCoordinate PXPos, const E3dCoordinate PYPos);

extern void    E3d_LonLatToXYZ_deg(const E3dCoordinate PLongitude, const E3dCoordinate PLatitude, const E3dCoordinate PDistance, E3dCoord3* P3DPosition);
extern void    E3d_LonLatToXYZ_rad(const E3dCoordinate PLongitude, const E3dCoordinate PLatitude, const E3dCoordinate PDistance, E3dCoord3* P3DPosition);
extern void    E3d_LonLatToXYZUnit_rad(const E3dCoordinate PLongitude, const E3dCoordinate PLatitude, E3dCoord3* P3DPosition);
extern void    E3d_XYZToLonLat_deg(const E3dCoordinate PX, const E3dCoordinate PY, const E3dCoordinate PZ,  E3dCoordinate* PLongitude, E3dCoordinate* PLatitude);
extern void    E3d_XYZToLonLat_rad(const E3dCoordinate PX, const E3dCoordinate PY, const E3dCoordinate PZ,  E3dCoordinate* PLongitude, E3dCoordinate* PLatitude);

extern void    E3d_PolarToXYZ_rad(const E3dCoordinate PPhi, const E3dCoordinate PTheta, const E3dCoordinate PDistance, E3dCoord3* P3DPosition);

extern void    E3d_VectorsGetQuaternion(const E3dCoord3* PVector0, const E3dCoord3* PVector1, E3dCoord4* PQuaternion);
extern void    E3dQuaternion_ToAxisAngle(const E3dCoord4* PQuaternion,  E3dCoord4* PAxisAngle);
extern void    E3d_AxisAngleToQuaternion(const E3dCoord4* PAA, E3dCoord4* PQuaternion);

extern E3dCoord3  E3dQuaternion_GetViewAxis(const E3dCoord4 PQ);


extern E3dCoordinate  E3d_ArcLength(const E3dCoord3* PP0, const E3dCoord3* PP1);


extern E3dCoordinate  E3d_PointDistanceFromLine3D(const E3dCoord3* PPoint, const E3dCoord3 P1, const E3dCoord3 P2);
extern E3dCoordinate  E3d_PointDistanceFromPlane3D(const E3dCoord3* PPoint, const E3dCoord4* PPlane);

extern EBool          E3d_Colinear2D(const E3dCoord2* PP1, const E3dCoord2* PP2, const E3dCoord2* PP3);
extern EBool          E3d_Intersect2D(const E3dCoordinate PP1X, E3dCoordinate PP1Y, const E3dCoordinate PP2X, const E3dCoordinate PP2Y, const E3dCoordinate PQ1X, const E3dCoordinate PQ1Y, const E3dCoordinate PQ2X, const E3dCoordinate PQ2Y);
extern int            E3d_2DIntersection(const E3dCoord2 PP1, const E3dCoord2 PP2, const E3dCoord2 PQ1, const E3dCoord2 PQ2, E3dCoord2* PIntersection);

extern void           E3d_Complexmult(E3dCoord2* PIn0, E3dCoord2* PIn1, E3dCoord2* POut);

extern int            E3d_Coord3ToAxisID(const E3dCoordinate PX, const E3dCoordinate PY, const E3dCoordinate PZ);


extern E3dCoordinate  E3d_OuterCircle(const E3dCoord2* PPosA, const E3dCoord2* PPosB, const E3dCoord2* PPosC, E3dCoord2* PCircleCenterPtr);

extern E3dCoord2**    E3d_ConvexHull2D(E3dCoord2* PPoints, const EIndex PCount, EIndex* PNumOfPointsRet);
extern E3dCoordinate  E3d_MinimalEnclosingCircle(const E3dCoord2* P2DPoints, const EIndex PNumOfVertices, E3dCoord2* PMECCenterPtr);
extern E3dCoordinate  E3d_MinimalEnclosingSphere(const E3dCoord3* PPoints, const EIndex PNumOfPoints, E3dCoord3* PMESCenterPtr);

extern void           E3d_FibonacciSphere(const EIndex PNPoints, const E3dCoordinate PRadius, E3dCoord3* PPoints);
extern E3dGLCoordinate*  E3d_RectangleSetPoints(E3dCoordinate PX1, E3dCoordinate PY1,  E3dCoordinate PX2, E3dCoordinate PY2,  const E3dCoordinate PZ,  const E3dCoordinate PCornerRadius, const E3dCoordinate PCornerRadiusMin, const EIndex PCornerArcSteps,  E3dGLCoordinate* PPoints, EIndex* PNPoints);

extern int            E3d_SolveQuintic(const double dd[6], const double xstart,  double sol[5], double soli[5]);


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

#endif	// _E3D_Math_h
