Implement a template-based classical Perlin noise generator.

The mask argument of the Impl template is there because apparently
an inner template cannot be fully specialized, so there needs to be
some argument besides i.
develop
Alexander Gavrilov 2013-10-01 18:58:04 +04:00
parent 599af0a4d9
commit e175efa689
8 changed files with 302 additions and 8 deletions

@ -761,6 +761,12 @@ Random number generation
uniformly distributed over the corresponding sphere surface. uniformly distributed over the corresponding sphere surface.
The default size is 3. The default size is 3.
* ``fn = rng:perlin([dim]); fn(x[,y[,z]])``
Returns a closure that computes a classical Perlin noise function
of dimension *dim*, initialized from this random generator.
Dimension may be 1, 2 or 3 (default).
C++ function wrappers C++ function wrappers
===================== =====================

@ -362,7 +362,7 @@ if(BUILD_DEVEL)
# without the '/', the directory itself is installed # without the '/', the directory itself is installed
install(DIRECTORY include/ install(DIRECTORY include/
DESTINATION ${DFHACK_INCLUDES_DESTINATION} DESTINATION ${DFHACK_INCLUDES_DESTINATION}
FILES_MATCHING PATTERN "*.h" ) #linux: include FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" ) #linux: include
# Building the docs # Building the docs
IF(BUILD_DOXYGEN) IF(BUILD_DOXYGEN)
add_subdirectory (doc) add_subdirectory (doc)

@ -94,6 +94,9 @@ using namespace DFHack::LuaWrapper;
using Screen::Pen; using Screen::Pen;
using Random::MersenneRNG; using Random::MersenneRNG;
using Random::PerlinNoise1D;
using Random::PerlinNoise2D;
using Random::PerlinNoise3D;
void dfhack_printerr(lua_State *S, const std::string &str); void dfhack_printerr(lua_State *S, const std::string &str);
@ -741,12 +744,10 @@ void Lua::Push(lua_State *L, const Screen::Pen &info)
return; return;
} }
void *pdata = lua_newuserdata(L, sizeof(Pen)); new (L) Pen(info);
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
new (pdata) Pen(info);
} }
static Pen *check_pen_native(lua_State *L, int index) static Pen *check_pen_native(lua_State *L, int index)
@ -1098,13 +1099,11 @@ static int dfhack_random_init(lua_State *L)
static int dfhack_random_new(lua_State *L) static int dfhack_random_new(lua_State *L)
{ {
void *pdata = lua_newuserdata(L, sizeof(MersenneRNG)); new (L) MersenneRNG();
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN); lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN);
lua_setmetatable(L, -2); lua_setmetatable(L, -2);
new (pdata) MersenneRNG();
lua_insert(L, 1); lua_insert(L, 1);
return dfhack_random_init(L); return dfhack_random_init(L);
} }
@ -1157,6 +1156,57 @@ static int dfhack_random_unitvector(lua_State *L)
return size; return size;
} }
static int eval_perlin_1(lua_State *L)
{
auto &gen = *(PerlinNoise1D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1)));
return 1;
}
static int eval_perlin_2(lua_State *L)
{
auto &gen = *(PerlinNoise2D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
return 1;
}
static int eval_perlin_3(lua_State *L)
{
auto &gen = *(PerlinNoise3D<float>*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushnumber(L, gen(luaL_checknumber(L, 1), luaL_checknumber(L, 2), luaL_checknumber(L, 3)));
return 1;
}
static int dfhack_random_perlin(lua_State *L)
{
MersenneRNG *prng = check_random_native(L, 1);
int size = luaL_optint(L, 2, 3);
switch (size)
{
case 1: {
auto pdata = new (L) PerlinNoise1D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_1, 1);
break;
}
case 2: {
auto pdata = new (L) PerlinNoise2D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_2, 1);
break;
}
case 3: {
auto pdata = new (L) PerlinNoise3D<float>();
pdata->init(*prng);
lua_pushcclosure(L, eval_perlin_3, 1);
break;
}
default:
luaL_argerror(L, 2, "perlin noise dimension must be 1, 2 or 3");
}
return 1;
}
static const luaL_Reg dfhack_random_funcs[] = { static const luaL_Reg dfhack_random_funcs[] = {
{ "new", dfhack_random_new }, { "new", dfhack_random_new },
{ "init", dfhack_random_init }, { "init", dfhack_random_init },
@ -1166,6 +1216,7 @@ static const luaL_Reg dfhack_random_funcs[] = {
{ "drandom1", dfhack_random_drandom1 }, { "drandom1", dfhack_random_drandom1 },
{ "unitrandom", dfhack_random_unitrandom }, { "unitrandom", dfhack_random_unitrandom },
{ "unitvector", dfhack_random_unitvector }, { "unitvector", dfhack_random_unitvector },
{ "perlin", dfhack_random_perlin },
{ NULL, NULL } { NULL, NULL }
}; };

@ -29,15 +29,24 @@ distribution.
#ifndef DFHACK_EXPORT #ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __attribute__ ((visibility("default"))) #define DFHACK_EXPORT __attribute__ ((visibility("default")))
#endif #endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT DFHACK_EXPORT
#endif
#else #else
#ifdef BUILD_DFHACK_LIB #ifdef BUILD_DFHACK_LIB
#ifndef DFHACK_EXPORT #ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __declspec(dllexport) #define DFHACK_EXPORT __declspec(dllexport)
#endif #endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT
#endif
#else #else
#ifndef DFHACK_EXPORT #ifndef DFHACK_EXPORT
#define DFHACK_EXPORT __declspec(dllimport) #define DFHACK_EXPORT __declspec(dllimport)
#endif #endif
#ifndef DFHACK_IMPORT
#define DFHACK_IMPORT DFHACK_EXPORT
#endif
#endif #endif
#endif #endif

@ -34,6 +34,11 @@ distribution.
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
/// Allocate a new user data object and push it on the stack
inline void *operator new (std::size_t size, lua_State *L) {
return lua_newuserdata(L, size);
}
namespace DFHack { namespace DFHack {
class function_identity_base; class function_identity_base;
struct MaterialInfo; struct MaterialInfo;

@ -0,0 +1,146 @@
#pragma once
namespace DFHack {
namespace Random {
/*
* A good explanation:
* http://webstaff.itn.liu.se/~stegu/TNM022-2005/perlinnoiselinks/perlin-noise-math-faq.html
*/
// Interpolation functions
template<class T>
inline T s_curve(T t)
{
return t * t * (3 - 2*t);
}
template<class T>
inline T lerp(T s, T a, T b)
{
return a + s * (b-a);
}
// Templates used to force unrolling and inlining of the loops
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
struct PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,0> {
typedef typename PerlinNoise<T,VSIZE,BITS,IDXT>::Temp Temp;
static inline T dot(T *pa, T *pb);
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);
};
// 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
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,0>::setup(const T *pv, Temp *pt)
{
T t = std::floor(*pv);
pt->s = s_curve(pt->r0 = *pv - t);
pt->b0 = unsigned(int32_t(t)) & mask;
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask, unsigned i>
inline void PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::setup(const T *pv, Temp *pt)
{
Impl<mask,i-1>::setup(pv,pt);
Impl<mask,0>::setup(pv+i,pt+i);
}
// Main recursion. Uses tables from self and pt.
// Recursion changes current index idx, and current offset vector pq.
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
template<unsigned mask>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask, 0>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) {
pq[0] = pt[0].r0;
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<unsigned mask, unsigned i>
inline T PerlinNoise<T,VSIZE,BITS,IDXT>::Impl<mask,i>::eval(
PerlinNoise<T,VSIZE,BITS,IDXT> *self, Temp *pt, unsigned idx, T *pq
) {
pq[i] = pt[i].r0;
idx += pt[i].b0;
T u = Impl<mask,i-1>::eval(self, pt, self->idxmap[idx], pq);
pq[i] -= 1;
idx += 1;
T v = Impl<mask,i-1>::eval(self, pt, self->idxmap[idx], pq);
return lerp(pt[i].s, u, v);
}
// Actual methods of the object
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
void PerlinNoise<T,VSIZE,BITS,IDXT>::init(MersenneRNG &rng)
{
STATIC_ASSERT(VSIZE > 0 && BITS <= 8*sizeof(IDXT));
// Random unit gradient vectors
for (unsigned i = 0; i < TSIZE; i++)
rng.unitvector(gradients[i], VSIZE);
// Random permutation table
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++)
gradients[i][j] = gradients[i-TSIZE][j];
idxmap[i] = idxmap[i-TSIZE];
}
}
template<class T, unsigned VSIZE, unsigned BITS, class IDXT>
T PerlinNoise<T,VSIZE,BITS,IDXT>::eval(const T coords[VSIZE])
{
// Precomputed properties from the coordinates
Temp tmp[VSIZE];
// Temporary used to build the current offset vector
T q[VSIZE];
Impl<TSIZE-1,VSIZE-1>::setup(coords, tmp);
return Impl<TSIZE-1,VSIZE-1>::eval(this, tmp, 0, q);
}
}} // namespace

@ -26,7 +26,7 @@ distribution.
#ifndef CL_MOD_RANDOM #ifndef CL_MOD_RANDOM
#define CL_MOD_RANDOM #define CL_MOD_RANDOM
/** /**
* \defgroup grp_translation Translation: DF word tables and name translation/reading * \defgroup grp_random Random: Random number and noise generation
* @ingroup grp_modules * @ingroup grp_modules
*/ */
@ -51,6 +51,8 @@ namespace Random
void prefill(unsigned step, int twist_cnt); void prefill(unsigned step, int twist_cnt);
public: public:
/* No constructor or destructor - safe to treat as data */
void init(const uint32_t *pseed, unsigned cnt, int twist_cnt = 1); void init(const uint32_t *pseed, unsigned cnt, int twist_cnt = 1);
void init(); // uses time void init(); // uses time
@ -104,6 +106,74 @@ namespace Random
extern template void MersenneRNG::unitvector<double>(double *p, int size); extern template void MersenneRNG::unitvector<double>(double *p, int size);
#endif #endif
/*
* Classical Perlin noise function in template form.
* http://mrl.nyu.edu/~perlin/doc/oscar.html#noise
*/
template<class T, unsigned VSIZE, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise
{
// Size of randomness tables
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];
IDXT idxmap[TSIZE_EXT];
// Templates used to unwind and inline recursion and loops
struct Temp {
T r0, s;
unsigned b0;
};
template<unsigned mask, unsigned i>
struct Impl {
static inline T dot(T *pa, T *pb);
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);
};
public:
/* No constructor or destructor - safe to treat as data */
void init(MersenneRNG &rng);
T eval(const T coords[VSIZE]);
};
#ifndef DFHACK_RANDOM_CPP
extern template class DFHACK_IMPORT PerlinNoise<float, 1>;
extern template class DFHACK_IMPORT PerlinNoise<float, 2>;
extern template class DFHACK_IMPORT PerlinNoise<float, 3>;
#endif
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise1D : public PerlinNoise<T, 1, BITS, IDXT>
{
public:
T operator() (T x) { return this->eval(&x); }
};
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise2D : public PerlinNoise<T, 2, BITS, IDXT>
{
public:
T operator() (T x, T y) {
T tmp[2] = { x, y };
return this->eval(tmp);
}
};
template<class T, unsigned BITS = 8, class IDXT = uint8_t>
class PerlinNoise3D : public PerlinNoise<T, 3, BITS, IDXT>
{
public:
T operator() (T x, T y, T z) {
T tmp[3] = { x, y, z };
return this->eval(tmp);
}
};
} }
} }
#endif #endif

@ -38,6 +38,7 @@ using namespace std;
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "Core.h" #include "Core.h"
#include "Error.h" #include "Error.h"
#include "VTableInterpose.h"
#include <cmath> #include <cmath>
@ -147,3 +148,9 @@ void MersenneRNG::unitvector(T *p, int size)
template void MersenneRNG::unitvector<float>(float *p, int size); template void MersenneRNG::unitvector<float>(float *p, int size);
template void MersenneRNG::unitvector<double>(double *p, int size); template void MersenneRNG::unitvector<double>(double *p, int size);
#include "modules/PerlinNoise.inc"
template class DFHACK_EXPORT PerlinNoise<float, 1>;
template class DFHACK_EXPORT PerlinNoise<float, 2>;
template class DFHACK_EXPORT PerlinNoise<float, 3>;