Skip to content

Commit

Permalink
Add support for the PCG32 PRNG algo (and associated script APIs)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwolekr committed Mar 22, 2015
1 parent 7679396 commit 3993093
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 171 deletions.
16 changes: 15 additions & 1 deletion doc/lua_api.txt
Expand Up @@ -2515,7 +2515,8 @@ an itemstring, a table or `nil`.
Returns taken `ItemStack`.

### `PseudoRandom`
A pseudorandom number generator.
A 16-bit pseudorandom number generator.
Uses a well-known LCG algorithm introduced by K&R.

It can be created via `PseudoRandom(seed)`.

Expand All @@ -2525,6 +2526,19 @@ It can be created via `PseudoRandom(seed)`.
* `((max - min) == 32767) or ((max-min) <= 6553))` must be true
due to the simple implementation making bad distribution otherwise.

### `PcgRandom`
A 32-bit pseudorandom number generator.
Uses PCG32, an algorithm of the permuted congruential generator family, offering very strong randomness.

It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`.

#### Methods
* `next()`: return next integer random number [`-2147483648`...`2147483647`]
* `next(min, max)`: return next integer random number [`min`...`max`]
* `rand_normal_dist(min, max, num_trials=6)`: return normally distributed random number [`min`...`max`]
* This is only a rough approximation of a normal distribution with mean=(max-min)/2 and variance=1
* Increasing num_trials improves accuracy of the approximation

### `PerlinNoise`
A perlin noise generator.
It can be created via `PerlinNoise(seed, octaves, persistence, scale)`
Expand Down
10 changes: 3 additions & 7 deletions src/mapgen.cpp
Expand Up @@ -515,14 +515,10 @@ void MapgenParams::load(const Settings &settings)
std::string seed_str;
const char *seed_name = (&settings == g_settings) ? "fixed_map_seed" : "seed";

if (settings.getNoEx(seed_name, seed_str) && !seed_str.empty()) {
if (settings.getNoEx(seed_name, seed_str) && !seed_str.empty())
seed = read_seed(seed_str.c_str());
} else {
seed = ((u64)(myrand() & 0xFFFF) << 0) |
((u64)(myrand() & 0xFFFF) << 16) |
((u64)(myrand() & 0xFFFF) << 32) |
((u64)(myrand() & 0xFFFF) << 48);
}
else
myrand_bytes(&seed, sizeof(seed));

settings.getNoEx("mg_name", mg_name);
settings.getS16NoEx("water_level", water_level);
Expand Down
2 changes: 1 addition & 1 deletion src/mg_ore.cpp
Expand Up @@ -308,7 +308,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
}

// randval ranges from -1..1
float randval = (float)pr.next() / (PSEUDORANDOM_MAX / 2) - 1.f;
float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f;
float noiseval = contour(noise->result[index]);
float noiseval2 = contour(noise2->result[index]);
if (noiseval * noiseval2 + randval * random_factor < nthresh)
Expand Down
101 changes: 101 additions & 0 deletions src/noise.cpp
Expand Up @@ -62,6 +62,107 @@ FlagDesc flagdesc_noiseparams[] = {

///////////////////////////////////////////////////////////////////////////////

PcgRandom::PcgRandom(u64 state, u64 seq)
{
seed(state, seq);
}

void PcgRandom::seed(u64 state, u64 seq)
{
m_state = 0U;
m_inc = (seq << 1u) | 1u;
next();
m_state += state;
next();
}


u32 PcgRandom::next()
{
u64 oldstate = m_state;
m_state = oldstate * 6364136223846793005ULL + m_inc;

u32 xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
u32 rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}


u32 PcgRandom::range(u32 bound)
{
/*
If the bound is not a multiple of the RNG's range, it may cause bias,
e.g. a RNG has a range from 0 to 3 and we take want a number 0 to 2.
Using rand() % 3, the number 0 would be twice as likely to appear.
With a very large RNG range, the effect becomes less prevalent but
still present. This can be solved by modifying the range of the RNG
to become a multiple of bound by dropping values above the a threshhold.
In our example, threshhold == 4 - 3 = 1 % 3 == 1, so reject 0, thus
making the range 3 with no bias.
This loop looks dangerous, but will always terminate due to the
RNG's property of uniformity.
*/
u32 threshhold = -bound % bound;
u32 r;

while ((r = next()) < threshhold);

return r % bound;
}


s32 PcgRandom::range(s32 min, s32 max)
{
assert(max >= min);
u32 bound = max - min + 1;
return range(bound) + min;
}


void PcgRandom::bytes(void *out, size_t len)
{
u32 r;
u8 *outb = (u8 *)out;

size_t len_alignment = (uintptr_t)out % sizeof(u32);
if (len_alignment) {
r = next();
while (len_alignment--) {
*outb = r & 0xFF;
outb++;
r >>= 8;
}
}

size_t len_dwords = len / sizeof(u32);
while (len_dwords--) {
r = next();
*(u32 *)outb = next();
outb += sizeof(u32);
}

size_t len_remaining = len % sizeof(u32);
if (len_remaining) {
r = next();
while (len_remaining--) {
*outb = r & 0xFF;
outb++;
r >>= 8;
}
}
}


s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials)
{
u32 accum = 0;
for (int i = 0; i != num_trials; i++)
accum += range(min, max);
return ((float)accum / num_trials) + 0.5f;
}

///////////////////////////////////////////////////////////////////////////////

float noise2d(int x, int y, int seed)
{
Expand Down
71 changes: 46 additions & 25 deletions src/noise.h
Expand Up @@ -30,47 +30,67 @@
#include "irr_v3d.h"
#include "util/string.h"

#define PSEUDORANDOM_MAX 32767

extern FlagDesc flagdesc_noiseparams[];

class PseudoRandom
{
// Note: this class is not polymorphic so that its high level of
// optimizability may be preserved in the common use case
class PseudoRandom {
public:
PseudoRandom(): m_next(0)
{
}
PseudoRandom(int seed): m_next(seed)
const static u32 RANDOM_RANGE = 32767;

inline PseudoRandom(int seed=0):
m_next(seed)
{
}
void seed(int seed)

inline void seed(int seed)
{
m_next = seed;
}
// Returns 0...PSEUDORANDOM_MAX
int next()

inline int next()
{
m_next = m_next * 1103515245 + 12345;
return((unsigned)(m_next/65536) % (PSEUDORANDOM_MAX + 1));
return (unsigned)(m_next / 65536) % (RANDOM_RANGE + 1);
}
int range(int min, int max)

inline int range(int min, int max)
{
if (max-min > (PSEUDORANDOM_MAX + 1) / 10)
{
//dstream<<"WARNING: PseudoRandom::range: max > 32767"<<std::endl;
assert("Something wrong with random number" == NULL);
}
if(min > max)
{
assert("Something wrong with random number" == NULL);
//return max;
}
return (next()%(max-min+1))+min;
assert(max >= min);
/*
Here, we ensure the range is not too large relative to RANDOM_MAX,
as otherwise the effects of bias would become noticable. Unlike
PcgRandom, we cannot modify this RNG's range as it would change the
output of this RNG for reverse compatibility.
*/
assert((u32)(max - min) <= (RANDOM_RANGE + 1) / 10);

return (next() % (max - min + 1)) + min;
}

private:
int m_next;
};

class PcgRandom {
public:
const static s32 RANDOM_MIN = -0x7fffffff - 1;
const static s32 RANDOM_MAX = 0x7fffffff;
const static u32 RANDOM_RANGE = 0xffffffff;

PcgRandom(u64 state=0x853c49e6748fea9bULL, u64 seq=0xda3e39cb94b95bdbULL);
void seed(u64 state, u64 seq=0xda3e39cb94b95bdbULL);
u32 next();
u32 range(u32 bound);
s32 range(s32 min, s32 max);
void bytes(void *out, size_t len);
s32 randNormalDist(s32 min, s32 max, int num_trials=6);

private:
u64 m_state;
u64 m_inc;
};

#define NOISE_FLAG_DEFAULTS 0x01
#define NOISE_FLAG_EASED 0x02
#define NOISE_FLAG_ABSVALUE 0x04
Expand All @@ -89,7 +109,8 @@ struct NoiseParams {
float lacunarity;
u32 flags;

NoiseParams() {
NoiseParams()
{
offset = 0.0f;
scale = 1.0f;
spread = v3f(250, 250, 250);
Expand Down

0 comments on commit 3993093

Please sign in to comment.