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;