//========================================================================
// libE2D
//
// - Perlin noise / fractal noise for procedural image generation
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Aug-17 03:44:46
//
// Copyright (C) 2014-2026 by Gabor Nagy. All rights reserved.
//========================================================================
#ifndef _EpPerlinNoise_h
#define _EpPerlinNoise_h

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


#define EpPNUSE_ERANDOM  1


#define EpPNMAX_DIMENSIONS    4    // Maximum number of dimensions in a noise object
#define EpPNMAX_OCTAVES     128    // Maximum # of octaves in an fBm object

#define EpPNDELTA         1e-6f    // Small number for comparing floating point numbers

#define EpPNSWAP(a, b, t)  t = a; a = b; b = t

inline int    Floor(float a)          { return ((int)a - (a < 0 && a != (int)a)); }
inline float  Cubic(float a)          { return a * a * (3 - 2*a); }
inline float  Min(float a, float b)   { return (a < b ? a : b); }
inline float  Max(float a, float b)   { return (a > b ? a : b); }
inline float  Clamp(float a, float b, float x)  { return (x < a ? a : (x > b ? b : x)); }
inline float  Lerp(float a, float b, float x)   { return a + x * (b - a); }


//================================================================================================
// Perlin noise class.
// Initialize with the number of dimensions (1 to 4) and a random seed.
// The source for the first 3 dimensions is from "Texturing & Modeling: A Procedural Approach".
//================================================================================================
typedef struct
{
  int      NDimensions;                        // Number of dimensions used by this object
  uint8_t  NMap[256];                          // Randomized map of indexes into buffer
  float    NBuffer[256][EpPNMAX_DIMENSIONS];    // Random n-dimensional buffer
} EpPerlinNoise;


typedef struct
{
	EpPerlinNoise  PerlinNoise;
	int            NDimensions;

	float  H;
	float  Lacunarity;
	float  Exponent[EpPNMAX_OCTAVES];
} EpFractal;


static inline void EpPerlinNoise_Normalize(float* PF, const int PN)
{
	float  LMagnitude = 0.0;

	{ int  LI = 0;for(;LI < PN;LI++) LMagnitude += PF[LI] * PF[LI]; }
	LMagnitude = 1.0 / sqrtf(LMagnitude);
	{ int  LI = 0;for(;LI < PN;LI++) PF[LI] *= LMagnitude; }
}


static inline void ERandom_Init(ERandom* PRandom, unsigned int PSeed)
{
#if EpPNUSE_ERANDOM
	E_RandomInit(PSeed, PRandom->RandomState);
#else
	srand(PSeed);
#endif
}


static inline double ERandom_Random(ERandom* PRandom)
{
#if EpPNUSE_ERANDOM
	return(E_RandomD(PRandom->RandomState));
#else
	return((double)rand() / (double)RAND_MAX);
#endif
}

static inline double ERandom_RandomD(ERandom* PRandom,  const double PMin, const double PMax)
{
	const double  LInterval = PMax - PMin;
	const double  LD = LInterval * ERandom_Random(PRandom);
	return(PMin + MIN(LD, LInterval));
}

static inline unsigned int ERandom_RandomI(ERandom* PRandom,  const unsigned int PMin, const unsigned int PMax)
{
	const unsigned int  LInterval = PMax - PMin;
	const unsigned int  LIV = (unsigned int)((LInterval + 1.0) * ERandom_Random(PRandom));
	return(PMin + MIN(LIV, LInterval));
}


static inline float EpPerlinNoise_Lattice1D(const EpPerlinNoise* PNoise,  const int PIX, const float PFX)
{
	int    n[] = { PIX };
	float  f[] = { PFX };
	int    LIndex = 0;

	const int       LNDimensions = PNoise->NDimensions;
	const uint8_t*  LNMap = PNoise->NMap;
	{ int  LD = 0;for(;LD < LNDimensions;LD++) LIndex = LNMap[(LIndex + n[LD]) & 0xFF]; }
	{ int  LD = 0;float  LValue = 0;for(;LD < LNDimensions;LD++) LValue += PNoise->NBuffer[LIndex][LD] * f[LD];return LValue; }
}


static inline float EpPerlinNoise_Lattice2D(const EpPerlinNoise* PNoise,  const int PIX, const float PFX,  const int PIY, const float PFY)
{
	int    n[4] = { PIX, PIY };
	float  f[4] = { PFX, PFY };
	int    LIndex = 0;

	const int       LNDimensions = PNoise->NDimensions;
	const uint8_t*  LNMap = PNoise->NMap;
	{ int  LD = 0;for(;LD < LNDimensions;LD++) LIndex = LNMap[(LIndex + n[LD]) & 0xFF]; }
	{ int  LD = 0;float  LValue = 0;for(;LD < LNDimensions;LD++) LValue += PNoise->NBuffer[LIndex][LD] * f[LD];return LValue; }
}


static inline float EpPerlinNoise_Lattice3D(const EpPerlinNoise* PNoise,  const int PIX, const float PFX,  const int PIY, const float PFY,
 const int PIZ, const float PFZ)
{
	int    n[4] = { PIX, PIY, PIZ };
	float  f[4] = { PFX, PFY, PFZ};
	int    LIndex = 0;

	const int       LNDimensions = PNoise->NDimensions;
	const uint8_t*  LNMap = PNoise->NMap;
	{ int  LD = 0;for(;LD < LNDimensions;LD++) LIndex = LNMap[(LIndex + n[LD]) & 0xFF]; }
	{ int  LD = 0;float  LValue = 0;for(;LD < LNDimensions;LD++) LValue += PNoise->NBuffer[LIndex][LD] * f[LD];return LValue; }
}


static inline float EpPerlinNoise_Lattice4D(const EpPerlinNoise* PNoise,  const int PIX, const float PFX,  const int PIY, const float PFY,
 const int PIZ, const float PFZ,  const int PIW, const float PFW)
{
	int    n[4] = { PIX, PIY, PIZ, PIW };
	float  f[4] = { PFX, PFY, PFZ, PFW };
	int    LIndex = 0;

	const int       LNDimensions = PNoise->NDimensions;
	const uint8_t*  LNMap = PNoise->NMap;
	{ int  LD = 0;for(;LD < LNDimensions;LD++) LIndex = LNMap[(LIndex + n[LD]) & 0xFF]; }
	{ int  LD = 0;float  LValue = 0;for(;LD < LNDimensions;LD++) LValue += PNoise->NBuffer[LIndex][LD] * f[LD];return LValue; }
}


static inline float EpPerlinNoise_Noise(EpPerlinNoise* PNoise, const float* f)
{
	int    n[EpPNMAX_DIMENSIONS];  // Indexes to pass to lattice function
	float  r[EpPNMAX_DIMENSIONS];  // Remainders to pass to lattice function
	float  w[EpPNMAX_DIMENSIONS];  // Cubic values to pass to interpolation function

	const int  LNDimensions = PNoise->NDimensions;

	for(int LI = 0;LI < LNDimensions;LI++)
	{
		n[LI] = Floor(f[LI]);
		r[LI] = f[LI] - n[LI];
		w[LI] = Cubic(r[LI]);
	}

	float  LValue = 0.0;
	switch(LNDimensions)
	{
		case 1:
			LValue = Lerp(EpPerlinNoise_Lattice1D(PNoise,  n[0], r[0]), EpPerlinNoise_Lattice1D(PNoise,  n[0]+1, r[0]-1), w[0]);
		break;

		case 2:
			LValue = Lerp(Lerp(EpPerlinNoise_Lattice2D(PNoise, n[0], r[0], n[1], r[1]),
			 EpPerlinNoise_Lattice2D(PNoise, n[0]+1, r[0]-1, n[1], r[1]),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice2D(PNoise, n[0], r[0], n[1]+1, r[1]-1),
			 EpPerlinNoise_Lattice2D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1),
			 w[0]),
			 w[1]);
		break;

		case 3:
			LValue = Lerp(Lerp(Lerp(EpPerlinNoise_Lattice3D(PNoise, n[0], r[0], n[1], r[1], n[2], r[2]),
			 EpPerlinNoise_Lattice3D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2], r[2]),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice3D(PNoise, n[0], r[0], n[1]+1, r[1]-1, n[2], r[2]),
			 EpPerlinNoise_Lattice3D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2], r[2]),
			 w[0]),
			 w[1]),
			 Lerp(Lerp(EpPerlinNoise_Lattice3D(PNoise, n[0], r[0], n[1], r[1], n[2]+1, r[2]-1),
			 EpPerlinNoise_Lattice3D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2]+1, r[2]-1),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice3D(PNoise, n[0], r[0], n[1]+1, r[1]-1, n[2]+1, r[2]-1),
			 EpPerlinNoise_Lattice3D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2]+1, r[2]-1),
			 w[0]),
			 w[1]),
			 w[2]);
		break;

		case 4:
			LValue = Lerp(Lerp(Lerp(Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1], r[1], n[2], r[2], n[3], r[3]),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2], r[2], n[3], r[3]),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1]+1, r[1]-1, n[2], r[2], n[3], r[3]),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2], r[2], n[3], r[3]),
			 w[0]),
			 w[1]),
			 Lerp(Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1], r[1], n[2]+1, r[2]-1, n[3], r[3]),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2]+1, r[2]-1, n[3], r[3]),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice3D(PNoise,  n[0], r[0],  n[1]+1, r[1]-1,  n[2]+1, r[2]-1),
			 EpPerlinNoise_Lattice4D(PNoise,  n[0]+1, r[0]-1,  n[1]+1, r[1]-1,  n[2]+1, r[2]-1,  n[3], r[3]),
			 w[0]),
			 w[1]),
			 w[2]),
			 Lerp(Lerp(Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1], r[1], n[2], r[2], n[3]+1, r[3]-1),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2], r[2], n[3]+1, r[3]-1),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1]+1, r[1]-1, n[2], r[2], n[3]+1, r[3]-1),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2], r[2], n[3]+1, r[3]-1),
			 w[0]),
			 w[1]),
			 Lerp(Lerp(EpPerlinNoise_Lattice4D(PNoise, n[0], r[0], n[1], r[1], n[2]+1, r[2]-1, n[3]+1, r[3]-1),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1], r[1], n[2]+1, r[2]-1, n[3]+1, r[3]-1),
			 w[0]),
			 Lerp(EpPerlinNoise_Lattice3D(PNoise,  n[0], r[0],  n[1]+1, r[1]-1,  n[2]+1, r[2]-1),
			 EpPerlinNoise_Lattice4D(PNoise, n[0]+1, r[0]-1, n[1]+1, r[1]-1, n[2]+1, r[2]-1, n[3]+1, r[3]-1),
			 w[0]),
			 w[1]),
			 w[2]),
			 w[3]);
    break;
  }

//	return CLAMP(-0.99999f, 0.99999f, LValue);
  return LValue * 1.33333f;
}


extern void EpPerlinNoise_Init(EpPerlinNoise* PNoise,  const int PNDimensions, const unsigned int PSeed);



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


#endif // _EpPerlinNoise_h
