From e175efa689680a061282842f92bd6c147edb2579 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 1 Oct 2013 18:58:04 +0400 Subject: [PATCH] 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. --- Lua API.rst | 6 + library/CMakeLists.txt | 2 +- library/LuaApi.cpp | 63 +++++++++- library/include/Export.h | 9 ++ library/include/LuaTools.h | 5 + library/include/modules/PerlinNoise.inc | 146 ++++++++++++++++++++++++ library/include/modules/Random.h | 72 +++++++++++- library/modules/Random.cpp | 7 ++ 8 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 library/include/modules/PerlinNoise.inc diff --git a/Lua API.rst b/Lua API.rst index 42d9953e8..dde47c8d2 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -761,6 +761,12 @@ Random number generation uniformly distributed over the corresponding sphere surface. 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 ===================== diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6df69fe27..2ecbbfa42 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -362,7 +362,7 @@ if(BUILD_DEVEL) # without the '/', the directory itself is installed install(DIRECTORY include/ DESTINATION ${DFHACK_INCLUDES_DESTINATION} - FILES_MATCHING PATTERN "*.h" ) #linux: include + FILES_MATCHING PATTERN "*.h" PATTERN "*.inc" ) #linux: include # Building the docs IF(BUILD_DOXYGEN) add_subdirectory (doc) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3b9a8d6d6..3f93e912d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -94,6 +94,9 @@ using namespace DFHack::LuaWrapper; using Screen::Pen; using Random::MersenneRNG; +using Random::PerlinNoise1D; +using Random::PerlinNoise2D; +using Random::PerlinNoise3D; 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; } - void *pdata = lua_newuserdata(L, sizeof(Pen)); + new (L) Pen(info); lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); lua_setmetatable(L, -2); - - new (pdata) Pen(info); } 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) { - void *pdata = lua_newuserdata(L, sizeof(MersenneRNG)); + new (L) MersenneRNG(); lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN); lua_setmetatable(L, -2); - new (pdata) MersenneRNG(); - lua_insert(L, 1); return dfhack_random_init(L); } @@ -1157,6 +1156,57 @@ static int dfhack_random_unitvector(lua_State *L) return size; } +static int eval_perlin_1(lua_State *L) +{ + auto &gen = *(PerlinNoise1D*)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*)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*)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(); + pdata->init(*prng); + lua_pushcclosure(L, eval_perlin_1, 1); + break; + } + case 2: { + auto pdata = new (L) PerlinNoise2D(); + pdata->init(*prng); + lua_pushcclosure(L, eval_perlin_2, 1); + break; + } + case 3: { + auto pdata = new (L) PerlinNoise3D(); + 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[] = { { "new", dfhack_random_new }, { "init", dfhack_random_init }, @@ -1166,6 +1216,7 @@ static const luaL_Reg dfhack_random_funcs[] = { { "drandom1", dfhack_random_drandom1 }, { "unitrandom", dfhack_random_unitrandom }, { "unitvector", dfhack_random_unitvector }, + { "perlin", dfhack_random_perlin }, { NULL, NULL } }; diff --git a/library/include/Export.h b/library/include/Export.h index c3d007117..87d38b45f 100644 --- a/library/include/Export.h +++ b/library/include/Export.h @@ -29,15 +29,24 @@ distribution. #ifndef DFHACK_EXPORT #define DFHACK_EXPORT __attribute__ ((visibility("default"))) #endif + #ifndef DFHACK_IMPORT + #define DFHACK_IMPORT DFHACK_EXPORT + #endif #else #ifdef BUILD_DFHACK_LIB #ifndef DFHACK_EXPORT #define DFHACK_EXPORT __declspec(dllexport) #endif + #ifndef DFHACK_IMPORT + #define DFHACK_IMPORT + #endif #else #ifndef DFHACK_EXPORT #define DFHACK_EXPORT __declspec(dllimport) #endif + #ifndef DFHACK_IMPORT + #define DFHACK_IMPORT DFHACK_EXPORT + #endif #endif #endif diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 3c15ebaac..d70e3ee33 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -34,6 +34,11 @@ distribution. #include #include +/// 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 { class function_identity_base; struct MaterialInfo; diff --git a/library/include/modules/PerlinNoise.inc b/library/include/modules/PerlinNoise.inc new file mode 100644 index 000000000..573968a95 --- /dev/null +++ b/library/include/modules/PerlinNoise.inc @@ -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 +inline T s_curve(T t) +{ + return t * t * (3 - 2*t); +} + +template +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 +template +struct PerlinNoise::Impl { + typedef typename PerlinNoise::Temp Temp; + static inline T dot(T *pa, T *pb); + static inline void setup(const T *pv, Temp *pt); + static inline T eval(PerlinNoise *self, Temp *pt, unsigned idx, T *pq); +}; + +// Dot product of VSIZE vectors pointed by pa, pb + +template +template +inline T PerlinNoise::Impl::dot(T *pa, T *pb) +{ + return pa[0] * pb[0]; +} + +template +template +inline T PerlinNoise::Impl::dot(T *pa, T *pb) +{ + return Impl::dot(pa, pb) + pa[i] * pb[i]; +} + +// Initialization of the temporaries from input coordinates + +template +template +inline void PerlinNoise::Impl::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 +template +inline void PerlinNoise::Impl::setup(const T *pv, Temp *pt) +{ + Impl::setup(pv,pt); + Impl::setup(pv+i,pt+i); +} + +// Main recursion. Uses tables from self and pt. +// Recursion changes current index idx, and current offset vector pq. + +template +template +inline T PerlinNoise::Impl::eval( + PerlinNoise *self, Temp *pt, unsigned idx, T *pq +) { + pq[0] = pt[0].r0; + idx += pt[0].b0; + T u = Impl::dot(pq, self->gradients[idx]); + + pq[0] -= 1; + idx += 1; + T v = Impl::dot(pq, self->gradients[idx]); + + return lerp(pt[0].s, u, v); +} + +template +template +inline T PerlinNoise::Impl::eval( + PerlinNoise *self, Temp *pt, unsigned idx, T *pq +) { + pq[i] = pt[i].r0; + idx += pt[i].b0; + T u = Impl::eval(self, pt, self->idxmap[idx], pq); + + pq[i] -= 1; + idx += 1; + T v = Impl::eval(self, pt, self->idxmap[idx], pq); + + return lerp(pt[i].s, u, v); +} + +// Actual methods of the object + +template +void PerlinNoise::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 +T PerlinNoise::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::setup(coords, tmp); + + return Impl::eval(this, tmp, 0, q); +} + +}} // namespace diff --git a/library/include/modules/Random.h b/library/include/modules/Random.h index 114336cd6..67a9f904b 100644 --- a/library/include/modules/Random.h +++ b/library/include/modules/Random.h @@ -26,7 +26,7 @@ distribution. #ifndef 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 */ @@ -51,6 +51,8 @@ namespace Random void prefill(unsigned step, int twist_cnt); public: + /* No constructor or destructor - safe to treat as data */ + void init(const uint32_t *pseed, unsigned cnt, int twist_cnt = 1); void init(); // uses time @@ -104,6 +106,74 @@ namespace Random extern template void MersenneRNG::unitvector(double *p, int size); #endif + /* + * Classical Perlin noise function in template form. + * http://mrl.nyu.edu/~perlin/doc/oscar.html#noise + */ + + template + class PerlinNoise + { + // Size of randomness tables + static const unsigned TSIZE = 1< + struct Impl { + static inline T dot(T *pa, T *pb); + static inline void setup(const T *pv, Temp *pt); + static inline T eval(PerlinNoise *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; + extern template class DFHACK_IMPORT PerlinNoise; + extern template class DFHACK_IMPORT PerlinNoise; +#endif + + template + class PerlinNoise1D : public PerlinNoise + { + public: + T operator() (T x) { return this->eval(&x); } + }; + + template + class PerlinNoise2D : public PerlinNoise + { + public: + T operator() (T x, T y) { + T tmp[2] = { x, y }; + return this->eval(tmp); + } + }; + + template + class PerlinNoise3D : public PerlinNoise + { + public: + T operator() (T x, T y, T z) { + T tmp[3] = { x, y, z }; + return this->eval(tmp); + } + }; } } #endif diff --git a/library/modules/Random.cpp b/library/modules/Random.cpp index 06c885cad..5fedb74f5 100644 --- a/library/modules/Random.cpp +++ b/library/modules/Random.cpp @@ -38,6 +38,7 @@ using namespace std; #include "ModuleFactory.h" #include "Core.h" #include "Error.h" +#include "VTableInterpose.h" #include @@ -147,3 +148,9 @@ void MersenneRNG::unitvector(T *p, int size) template void MersenneRNG::unitvector(float *p, int size); template void MersenneRNG::unitvector(double *p, int size); + +#include "modules/PerlinNoise.inc" + +template class DFHACK_EXPORT PerlinNoise; +template class DFHACK_EXPORT PerlinNoise; +template class DFHACK_EXPORT PerlinNoise;