// perlin.cc
// This source code file (perlin.cc) originated from libnoise (noisegen.cpp).
// It was drastically hacked/reduced/optimized by Jim Brooks [jimb].
// Although faster, not as generalized as the original code.
//
//------------------------------------------------------------------------------
//
// noisegen.cpp
//
// Copyright (C) 2003, 2004 Jason Bevins
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
// License (COPYING.txt) for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// The developer's email is jlbezigvins@gmzigail.com (for great email, take
// off every 'zig'.)
//
//------------------------------------------------------------------------------

#include "libnoise.hh"
#include "perlin.hh"
#include "interp.hh"
#include "vectortable.hh"

namespace noise {

// These constants control certain parameters that all coherent-noise functions require.
// Constants used by the current version of libnoise.
const int X_NOISE_GEN = 1619;
const int Y_NOISE_GEN = 31337;
const int Z_NOISE_GEN = 6971;
const int SEED_NOISE_GEN = 1013;
const int SHIFT_NOISE_GEN = 8;
const int NOISE_SEED = 0xa3b7cf9e;
const int SEED_NOISE_GEN_x_seed = SEED_NOISE_GEN * NOISE_SEED;

// tweak: Noise coords are stored temporarily in globals
// to avoid repassing them.  Code must be fast, regardless.
static float nx, ny, nz;

/*****************************************************************************
 * NOTE tweak: Originally (fx,fy,fz) were passed repeatedly by PerlinNoise().
 * Now the noise coords are stored in temp globals (nx,ny,nz) for speed.
 * ...........................................................................
 * Generates a gradient-noise value from the coordinates of a
 * three-dimensional input value and the integer coordinates of a
 * nearby three-dimensional value.
 *
 * @param fx The floating-point @a x coordinate of the input value.
 * @param fy The floating-point @a y coordinate of the input value.
 * @param fz The floating-point @a z coordinate of the input value.
 * @param ix The integer @a x coordinate of a nearby value.
 * @param iy The integer @a y coordinate of a nearby value.
 * @param iz The integer @a z coordinate of a nearby value.
 * @param seed The random number seed.
 *
 * @returns The generated gradient-noise value.
 * @pre The difference between @a fx and @a ix must be less than or equal to one.
 * @pre The difference between @a fy and @a iy must be less than or equal to one.
 * @pre The difference between @a fz and @a iz must be less than or equal to one.
 *
 * A <i>gradient</i>-noise function generates better-quality noise than a
 * <i>value</i>-noise function.  Most noise modules use gradient noise for
 * this reason, although it takes much longer to calculate.
 *
 * The return value ranges from -1.0 to +1.0.
 *
 * This function generates a gradient-noise value by performing the
 * following steps:
 * - It first calculates a random normalized vector based on the
 *   nearby integer value passed to this function.
 * - It then calculates a new value by adding this vector to the
 *   nearby integer value passed to this function.
 * - It then calculates the dot product of the above-generated value
 *   and the floating-point input value passed to this function.
 *
 * A noise function differs from a random-number generator because it
 * always returns the same output value if the same input value is passed to it.
 *****************************************************************************/
static inline float
GradientNoise3D( int ix, int iy, int iz )
{
  // Randomly generate a gradient vector given the integer coordinates of the
  // input value.  This implementation generates a random number and uses it
  // as an index into a normalized-vector lookup table.
  int vectorIndex = X_NOISE_GEN * ix
                  + Y_NOISE_GEN * iy
                  + Z_NOISE_GEN * iz
                  + SEED_NOISE_GEN_x_seed;

//vectorIndex &= 0xffffffff;  // [jimb] necessary for CPU with 64-bit word?
  vectorIndex ^= (vectorIndex >> SHIFT_NOISE_GEN);
  vectorIndex &= 0xff;
  const uint vectorIndex4 = vectorIndex << 2;
  const float& xvGradient = g_randomVectors[vectorIndex4  ];
  const float& yvGradient = g_randomVectors[vectorIndex4+1];
  const float& zvGradient = g_randomVectors[vectorIndex4+2];

  // Set up us another vector equal to the distance between
  // the two vectors passed to this function.
  // Now compute the dot product of the gradient vector with the distance
  // vector.  The resulting value is gradient noise.  Apply a scaling value
  // so that this noise value ranges from -1.0 to 1.0.
  return ((xvGradient * (nx - float(ix)))  // (nx,ny,nz) are temp globals (see comments)
        + (yvGradient * (ny - float(iy)))
        + (zvGradient * (nz - float(iz)))) * 2;  //2.12;  // tweak: multiply by 2
}

/*****************************************************************************
 * See header file.
 * Originally named GradientCoherentNoise3D().
 * Optimization note: turning (x,y,z) to float[3] wasn't faster on Intel Pentium 4 Xeon.
 *****************************************************************************/
float PerlinNoise( float x, float y, float z )
{
  nx = x;  // tweak: avoid repassing (x,y,z) to GradientNoise3D()
  ny = y;
  nz = z;

  // Create a unit-length cube aligned along an integer boundary.
  // This cube surrounds the input point.
  int x0 = int(x);
  int y0 = int(y);
  int z0 = int(z);
  int x1 = x0 + 1;
  int y1 = y0 + 1;
  int z1 = z0 + 1;

  // Map the difference between the coordinates of the input value and the
  // coordinates of the cube's outer-lower-left vertex onto an S-curve.
  // tweak: one_minus avoids recomputing (1.0-a) in LinearInterp().
#if defined(LIBNOISE_QUALITY_FAST)
  #define GRADIENT_DIFF(X,X0) ( (X) - float(X0) )
#elif defined(LIBNOISE_QUALITY_STD)
  #define GRADIENT_DIFF(X,X0) ( SCurve3( (X) - float((X0)) ) )
#elif defined(LIBNOISE_QUALITY_BEST)
  #define GRADIENT_DIFF(X,X0) ( SCurve5( (X) - float((X0)) ) )
#endif
  // tweak: keep computed results consecutive as x87 FPU has limited regs.
  float xs = GRADIENT_DIFF(x,x0); float one_minus_xs = 1.0 - xs;
  float ys = GRADIENT_DIFF(y,y0); float one_minus_ys = 1.0 - ys;
  float zs = GRADIENT_DIFF(z,z0); float one_minus_zs = 1.0 - zs;
  #undef GRADIENT_DIFF

  // Now calculate the noise values at each vertex of the cube.  To generate
  // the coherent-noise value at the input point, interpolate these eight
  // noise values using the S-curve value as the interpolant (trilinear interpolation.)
  float n0, n1, ix0, ix1, iy0, iy1;
  n0   = GradientNoise3D( x0, y0, z0 );
  n1   = GradientNoise3D( x1, y0, z0 );
  ix0  = LinearInterp( n0, n1, xs, one_minus_xs );
  n0   = GradientNoise3D( x0, y1, z0 );
  n1   = GradientNoise3D( x1, y1, z0 );
  ix1  = LinearInterp(  n0,  n1, xs, one_minus_xs );
  iy0  = LinearInterp( ix0, ix1, ys, one_minus_ys );
  n0   = GradientNoise3D( x0, y0, z1 );
  n1   = GradientNoise3D( x1, y0, z1 );
  ix0  = LinearInterp( n0, n1, xs, one_minus_xs );
  n0   = GradientNoise3D( x0, y1, z1 );
  n1   = GradientNoise3D( x1, y1, z1 );
  ix1  = LinearInterp( n0,   n1, xs, one_minus_xs );
  iy1  = LinearInterp( ix0, ix1, ys, one_minus_ys );
  return LinearInterp( iy0, iy1, zs, one_minus_zs );
}

} // namespace noise
