
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include "cg_noise.h"

#define nvSRand srand48
#define nvRand drand48
#define NVSR_ASSERT assert

#ifdef WIN32

static double drand48(void) 
 { 
  return (double)rand()/(double)RAND_MAX; 
 } 

static void srand48(unsigned int seed) 
 { 
  srand(seed);
 } 

#endif 

double nvFloatRand(double min, double max)
 {
  return(nvRand() * (max - min) + min);
 }

int nvIntRand(int min, int max)
 {
  return((int)nvFloatRand((double)min, (double)max));
 }

/******************************************************************************/
/*** External Global Data                                                   ***/
/******************************************************************************/

#define REPEAT_INTERVAL 4096

const int RepeatInterval = REPEAT_INTERVAL;
const int Mask = REPEAT_INTERVAL - 1;

/******************************************************************************/
/*** Static Global Data                                                     ***/
/******************************************************************************/

static int unsigned Seed = 69;

static int Initialized = 0;
static double Lattice1D[REPEAT_INTERVAL];
static double Lattice2D[REPEAT_INTERVAL][2];
static double Lattice3D[REPEAT_INTERVAL][3];
static int FoldTable[REPEAT_INTERVAL];
static double PhaseOffsetTable[REPEAT_INTERVAL];

/******************************************************************************/
/*** Internal Functions                                                     ***/
/******************************************************************************/

static void Normalize2D(double *x, double *y)
 {
  double Mag = sqrt(*x * *x + *y * *y);
  *x /= Mag;
  *y /= Mag;
 }

static void Normalize3D(double *x, double *y, double *z)
 {
  double Mag = sqrt(*x * *x + *y * *y + *z * *z);
  *x /= Mag;
  *y /= Mag;
  *z /= Mag;
 }

static void Initialize(void)
 {
  const int NSwaps = RepeatInterval * 8;
  int i;

  if(Initialized)
   return;

  nvSRand(Seed);

  /************************/
  /* Build the fold table */
  /************************/

  for(i=0; i < RepeatInterval; ++i)
   FoldTable[i] = i;

  for(i=0; i < NSwaps; ++i)
   {
    int Index0 = nvIntRand(0, RepeatInterval - 1);
    int Index1 = nvIntRand(0, RepeatInterval - 1);
    int TempVal = FoldTable[Index0];
    FoldTable[Index0] = FoldTable[Index1];
    FoldTable[Index1] = TempVal;
   }

  /******************************/
  /* Build the 1D lattice table */
  /******************************/

  for(i=0; i < RepeatInterval; ++i)
   Lattice1D[i] = nvFloatRand(0.0, 1.0);

  /******************************/
  /* Build the 2D lattice table */
  /******************************/

  for(i=0; i < RepeatInterval; ++i)
   {
    Lattice2D[i][0] = nvFloatRand(-1.0, 1.0);
    Lattice2D[i][1] = nvFloatRand(-1.0, 1.0);

    Normalize2D(&Lattice2D[i][0], &Lattice2D[i][1]);
   }

  /******************************/
  /* Build the 3D lattice table */
  /******************************/

  for(i=0; i < RepeatInterval; ++i)
   {
    Lattice3D[i][0] = nvFloatRand(-1.0, 1.0);
    Lattice3D[i][1] = nvFloatRand(-1.0, 1.0);
    Lattice3D[i][2] = nvFloatRand(-1.0, 1.0);

    Normalize3D(&Lattice3D[i][0], &Lattice3D[i][1], &Lattice3D[i][2]);
   }


  /********************************/
  /* Build the Phase Offset Table */
  /********************************/

  for(i=0; i < RepeatInterval; ++i)
   PhaseOffsetTable[i] = nvFloatRand(0.0, RepeatInterval);

  Initialized = 1;
 }

static int MaskedIndex(int index) 
 {
  if(index < 0)
    {
     index = Mask & -index;
     if(index)
      index = RepeatInterval - index;
    }
   else 
    index &= Mask;

  return(index);
 }

static double LatticeVal(int x) 
 {
  return(Lattice1D[MaskedIndex(x)]);
 }

static double *LatticeVal2(int x, int y) 
 {
  int Index = FoldTable[MaskedIndex(x)];
  Index = FoldTable[(Index + y) & Mask];
  return(&Lattice2D[Index][0]);
 }

static double *LatticeVal3(int x, int y, int z) 
 {
  int Index = FoldTable[MaskedIndex(x)];
  Index = FoldTable[(Index + y) & Mask];
  Index = FoldTable[(Index + z) & Mask];
  return(&Lattice3D[Index][0]);
 }

static double WeightSmooth(double t) 
 {
  return(t * t * (3 - 2 * t));
 }

static double Lerp(double t, double a, double b) 
 {
  return((1.0 - t) * a + t * b);
 }

/******************************************************************************/
/*** Noise Functions                                                        ***/
/******************************************************************************/

double cgNoise1(double t)
 {
  int T0 = (int)floor(t);
  double TParam = WeightSmooth(t - (double)T0);

  Initialize();

  return(Lerp(TParam, LatticeVal(T0), LatticeVal(T0 + 1)));
 }

double cgNoise2(double u, double v)
 {
  double Ret;
  double InvUParam, InvVParam;
  double *G00, *G10, *G01, *G11;
  double F00, F10, F01, F11;

  int U0 = (int)floor(u);
  int U1 = U0 + 1;
  double UParam = u - (double)U0;

  int V0 = (int)floor(v);
  int V1 = V0 + 1;
  double VParam = v - (double)V0;

  Initialize();


  UParam = WeightSmooth(UParam);
  VParam = WeightSmooth(VParam);

  InvUParam = 1.0 - UParam;
  InvVParam = 1.0 - VParam;

  G00 = LatticeVal2(U0, V0);
  G10 = LatticeVal2(U1, V0);
  G01 = LatticeVal2(U0, V1);
  G11 = LatticeVal2(U1, V1);

  F00 = UParam * G00[0] + VParam * G00[1];
  F10 = (UParam - 1.0) * G10[0] + VParam * G10[1];
  F01 = UParam * G01[0] + (VParam - 1.0) * G01[1];
  F11 = (UParam - 1.0) * G11[0] + (VParam - 1.0) * G11[1];

  Ret = InvUParam * (InvVParam * F00 + VParam * F01) +
           UParam * (InvVParam * F10 + VParam * F11);

  return(0.5 * (Ret + 1.0));
 }

double cgNoise3(double u, double v, double w)
 {
  double Ret;
  double InvUParam, InvVParam, InvWParam;
  double *G000, *G100, *G010, *G110;
  double *G001, *G101, *G011, *G111;
  double F000, F100, F010, F110;
  double F001, F101, F011, F111;

  int U0 = (int)floor(u);
  int U1 = U0 + 1;
  double UParam = u - (double)U0;

  int V0 = (int)floor(v);
  int V1 = V0 + 1;
  double VParam = v - (double)V0;

  int W0 = (int)floor(w);
  int W1 = W0 + 1;
  double WParam = v - (double)W0;

  Initialize();

  UParam = WeightSmooth(UParam);
  VParam = WeightSmooth(VParam);
  WParam = WeightSmooth(WParam);

  InvUParam = 1.0 - UParam;
  InvVParam = 1.0 - VParam;
  InvWParam = 1.0 - WParam;

  G000 = LatticeVal3(U0, V0, W0);
  G100 = LatticeVal3(U1, V0, W0);
  G010 = LatticeVal3(U0, V1, W0);
  G110 = LatticeVal3(U1, V1, W0);
  G001 = LatticeVal3(U0, V0, W1);
  G101 = LatticeVal3(U1, V0, W1);
  G011 = LatticeVal3(U0, V1, W1);
  G111 = LatticeVal3(U1, V1, W1);


  F000 = UParam * G000[0] + VParam * G000[1] * WParam * G000[2];
  F100 = InvUParam * G100[0] + VParam * G100[1] * WParam * G000[2];
  F010 = UParam * G010[0] + InvVParam * G010[1] * WParam * G000[2];
  F110 = InvUParam * G110[0] + InvVParam * G110[1] * WParam * G000[2];
  F001 = UParam * G001[0] + VParam * G001[1] * WParam * G001[2];
  F101 = InvUParam * G101[0] + VParam * G101[1] * WParam * G001[2];
  F011 = UParam * G011[0] + InvVParam * G011[1] * WParam * G001[2];
  F111 = InvUParam * G111[0] + InvVParam * G111[1] * WParam * G001[2];

  Ret = InvWParam * (InvUParam * (InvVParam * F000 + VParam * F010) +
                        UParam * (InvVParam * F100 + VParam * F110)) + 
           WParam * (InvUParam * (InvVParam * F001 + VParam * F011) +
                        UParam * (InvVParam * F101 + VParam * F111));

  return(0.5 * (Ret + 1.0));
 }

/******************************************************************************/
/*** Turbulence Functions                                                   ***/
/******************************************************************************/

double cgFbm1(double t, int octaves, double lambda, double omega)
 {
  double Amp = 1.0, Freq = 1.0;
  double TotalAmp = 0.0;
  double Ret = 0.0;
  int i;

  NVSR_ASSERT(octaves > 0);
  for(i=0; i < octaves; ++i)
   {
    Ret += Amp * cgNoise1(Freq * t + PhaseOffsetTable[i]);
    TotalAmp += Amp;
    Amp *= lambda;
    Freq *= omega;
   }

  return(Ret / TotalAmp);
 }

double cgFbm2(double u, double v, int octaves, double lambda, double omega)
 {
  double Amp = 1.0, Freq = 1.0;
  double TotalAmp = 0.0;
  double Ret = 0.0;
  int i;

  NVSR_ASSERT(octaves > 0);
  for(i=0; i < octaves; ++i)
   {
    Ret += Amp * cgNoise2(Freq * u + PhaseOffsetTable[i],
                           Freq * v + PhaseOffsetTable[i + 1]);
    TotalAmp += Amp;
    Amp *= lambda;
    Freq *= omega;
   }

  return(Ret / TotalAmp);
 }

double cgFbm3(double u, double v, double w, int octaves, double lambda, 
               double omega)
 {
  double Amp = 1.0, Freq = 1.0;
  double TotalAmp = 0.0;
  double Ret = 0.0;
  int i;

  NVSR_ASSERT(octaves > 0);
  for(i=0; i < octaves; ++i)
   {
    Ret += Amp * cgNoise3(Freq * u + PhaseOffsetTable[i],
                           Freq * v + PhaseOffsetTable[i + 1],
                           Freq * w + PhaseOffsetTable[i + 2]);
    TotalAmp += Amp;
    Amp *= lambda;
    Freq *= omega;
   }

  return(Ret / TotalAmp);
 }



