Use a better hash function in the Perlin noise generator.

develop
Alexander Gavrilov 2013-10-02 18:55:41 +04:00
parent 864baa2241
commit 33469f5bb2
2 changed files with 52 additions and 68 deletions

@ -13,7 +13,11 @@ namespace Random {
template<class T> template<class T>
inline T s_curve(T t) inline T s_curve(T t)
{ {
return t * t * (3 - 2*t); // Classical function
//return t * t * (3 - 2*t);
// 2002 version from http://mrl.nyu.edu/~perlin/paper445.pdf
return t * t * t * (t * (t * 6 - 15) + 10);
} }
template<class T> template<class T>
@ -22,50 +26,46 @@ inline T lerp(T s, T a, T b)
return a + s * (b-a); return a + s * (b-a);
} }
// Dot product of VSIZE vectors pointed by pa, pb
template<class T, unsigned i>
struct DotProduct {
static inline T eval(T *pa, T *pb);
};
template<class T>
struct DotProduct<T,0> {
static inline T eval(T *pa, T *pb) { return pa[0]*pb[0]; }
};
template<class T, unsigned i>
inline T DotProduct<T,i>::eval(T *pa, T *pb) {
return DotProduct<T,i-1>::eval(pa, pb) + pa[i]*pb[i];
}
// Templates used to force unrolling and inlining of the loops // Templates used to force unrolling and inlining of the loops
template<class T, unsigned VSIZE, unsigned BITS, class IDXT> template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask> template<unsigned mask>
struct PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,0> { struct PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,-1> {
typedef typename PerlinNoise<T,VSIZE,BITS,IDXT>::Temp Temp; typedef typename PerlinNoise<T,VSIZE,BITS,IDXT>::Temp Temp;
static inline T dot(T *pa, T *pb); static inline void setup(PerlinNoise<T,VSIZE,BITS,IDXT> *, const T *, Temp *) {}
static inline void setup(const T *pv, Temp *pt);
static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq); static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq);
}; };
// Dot product of VSIZE vectors pointed by pa, pb
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,0>::dot(T *pa, T *pb)
{
return pa[0] * pb[0];
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask, unsigned i>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::dot(T *pa, T *pb)
{
return Impl<mask,i-1>::dot(pa, pb) + pa[i] * pb[i];
}
// Initialization of the temporaries from input coordinates // Initialization of the temporaries from input coordinates
template<class T, unsigned VSIZE, unsigned BITS, class IDXT> template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask> template<unsigned mask, int i>
inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,0>::setup(const T *pv, Temp *pt) inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::setup(
{ PerlinNoise<T,VSIZE,BITS,IDXT> *self, const T *pv, Temp *pt
T t = std::floor(*pv); ) {
pt->s = s_curve(pt->r0 = *pv - t); Impl<mask,i-1>::setup(self, pv, pt);
pt->b0 = unsigned(int32_t(t)) & mask;
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT> T t = std::floor(pv[i]);
template<unsigned mask, unsigned i> pt[i].s = s_curve(pt[i].r0 = pv[i] - t);
inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::setup(const T *pv, Temp *pt)
{ unsigned b = unsigned(int32_t(t));
Impl<mask,i-1>::setup(pv,pt); pt[i].b0 = self->idxmap[i][b & mask];
Impl<mask,0>::setup(pv+i,pt+i); pt[i].b1 = self->idxmap[i][(b+1) & mask];
} }
// Main recursion. Uses tables from self and pt. // Main recursion. Uses tables from self and pt.
@ -73,32 +73,22 @@ inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::setup(const T *pv, Tem
template<class T, unsigned VSIZE, unsigned BITS, class IDXT> template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask> template<unsigned mask>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask, 0>::eval( inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask, -1>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) { ) {
pq[0] = pt[0].r0; return DotProduct<T,VSIZE-1>::eval(pq, self->gradients[idx]);
idx += pt[0].b0;
T u = Impl<mask,VSIZE-1>::dot(pq, self->gradients[idx]);
pq[0] -= 1;
idx += 1;
T v = Impl<mask,VSIZE-1>::dot(pq, self->gradients[idx]);
return lerp(pt[0].s, u, v);
} }
template<class T, unsigned VSIZE, unsigned BITS, class IDXT> template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask, unsigned i> template<unsigned mask, int i>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::eval( inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) { ) {
pq[i] = pt[i].r0; pq[i] = pt[i].r0;
idx += pt[i].b0; T u = Impl<mask,i-1>::eval(self, pt, idx ^ pt[i].b0, pq);
T u = Impl<mask,i-1>::eval(self, pt, self->idxmap[idx], pq);
pq[i] -= 1; pq[i] -= 1;
idx += 1; T v = Impl<mask,i-1>::eval(self, pt, idx ^ pt[i].b1, pq);
T v = Impl<mask,i-1>::eval(self, pt, self->idxmap[idx], pq);
return lerp(pt[i].s, u, v); return lerp(pt[i].s, u, v);
} }
@ -114,19 +104,13 @@ void PerlinNoise<T,VSIZE,BITS,IDXT>::init(MersenneRNG &rng)
for (unsigned i = 0; i < TSIZE; i++) for (unsigned i = 0; i < TSIZE; i++)
rng.unitvector(gradients[i], VSIZE); rng.unitvector(gradients[i], VSIZE);
// Random permutation table // Random permutation tables
for (unsigned i = 0; i < TSIZE; i++)
idxmap[i] = i;
rng.permute(idxmap, TSIZE);
// Extended part of the table to avoid doing bitwise ops
for (unsigned i = TSIZE; i < TSIZE_EXT; i++)
{
for (unsigned j = 0; j < VSIZE; j++) for (unsigned j = 0; j < VSIZE; j++)
gradients[i][j] = gradients[i-TSIZE][j]; {
for (unsigned i = 0; i < TSIZE; i++)
idxmap[j][i] = i;
idxmap[i] = idxmap[i-TSIZE]; rng.permute(idxmap[j], TSIZE);
} }
} }
@ -138,7 +122,7 @@ T PerlinNoise<T,VSIZE,BITS,IDXT>::eval(const T coords[VSIZE])
// Temporary used to build the current offset vector // Temporary used to build the current offset vector
T q[VSIZE]; T q[VSIZE];
Impl<TSIZE-1,VSIZE-1>::setup(coords, tmp); Impl<TSIZE-1,VSIZE-1>::setup(this, coords, tmp);
return Impl<TSIZE-1,VSIZE-1>::eval(this, tmp, 0, q); return Impl<TSIZE-1,VSIZE-1>::eval(this, tmp, 0, q);
} }

@ -109,6 +109,9 @@ namespace Random
/* /*
* Classical Perlin noise function in template form. * Classical Perlin noise function in template form.
* http://mrl.nyu.edu/~perlin/doc/oscar.html#noise * http://mrl.nyu.edu/~perlin/doc/oscar.html#noise
*
* Using an improved hash function from:
* http://www.cs.utah.edu/~aek/research/noise.pdf
*/ */
template<class T, unsigned VSIZE, unsigned BITS = 8, class IDXT = uint8_t> template<class T, unsigned VSIZE, unsigned BITS = 8, class IDXT = uint8_t>
@ -116,21 +119,18 @@ namespace Random
{ {
// Size of randomness tables // Size of randomness tables
static const unsigned TSIZE = 1<<BITS; static const unsigned TSIZE = 1<<BITS;
// Extended size with repeated data to avoid bitwise masking
static const unsigned TSIZE_EXT = 2*(TSIZE+1);
T gradients[TSIZE_EXT][VSIZE]; T gradients[TSIZE][VSIZE];
IDXT idxmap[TSIZE_EXT]; IDXT idxmap[VSIZE][TSIZE];
// Templates used to unwind and inline recursion and loops // Templates used to unwind and inline recursion and loops
struct Temp { struct Temp {
T r0, s; T r0, s;
unsigned b0; unsigned b0, b1;
}; };
template<unsigned mask, unsigned i> template<unsigned mask, int i>
struct Impl { struct Impl {
static inline T dot(T *pa, T *pb); static inline void setup(PerlinNoise<T,VSIZE,BITS,IDXT> *self, const T *pv, Temp *pt);
static inline void setup(const T *pv, Temp *pt);
static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq); static inline T eval(PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq);
}; };