Infinity Engine v0.6.20
C++ API Documentation
Loading...
Searching...
No Matches
Random Number Generation and Distributions

The Infinity Engine provides a comprehensive random number generation system built on PCG (Permuted Congruential Generator) algorithms with platform-independent probability distributions. This tutorial covers everything from basic random values to sophisticated probability distributions for professional procedural generation.

Why Quality Randomness Matters

Procedural generation depends on high-quality randomness that is:

  • Reproducible: Same seed produces identical results across platforms
  • High Quality: Excellent statistical properties for natural-looking generation
  • Fast: Efficient enough for real-time generation
  • Flexible: Support for complex probability distributions
  • Cross-Platform: Identical results on Windows, Linux, and macOS

The PRNG Class

Basic Usage

#include <Infinity/Engine/PRNG.hpp>
#include <iostream>
void basicPRNGUsage() {
using namespace Infinity::Engine;
// Create with automatic seed
PRNG rng1;
// Create with specific seed for reproducible results
PRNG rng2(12345);
// Create with seed and stream for independent sequences
PRNG rng3(12345, 1); // Same seed, different stream = different sequence
// Generate basic random values
uint32_t randomInt = rng2(); // Full uint32 range
int signedInt = rng2.nextInt(); // Full int range
float randomFloat = rng2.nextFloat(); // [0, 1)
std::cout << "Random uint32: " << randomInt << std::endl;
std::cout << "Random int: " << signedInt << std::endl;
std::cout << "Random float: " << randomFloat << std::endl;
}
Pseudo-random number generator for procedural generation.
Definition PRNG.hpp:48
Definition BaseException.hpp:9

Bounded Random Values

void boundedRandomValues() {
using namespace Infinity::Engine;
PRNG rng(54321);
// Bounded integer ranges (inclusive)
int diceRoll = rng.nextIntBounded(1, 6); // 1 to 6
int gridX = rng.nextIntBounded(0, 99); // 0 to 99
// Bounded float ranges (inclusive)
float temperature = rng.nextFloatBounded(-10.0f, 35.0f); // -10.0 to 35.0
float position = rng.nextFloatBounded(-1.0f, 1.0f); // -1.0 to 1.0
// Bounded uint32 ranges (inclusive)
uint32_t index = rng.nextBounded(0, 255); // 0 to 255
std::cout << "Dice roll: " << diceRoll << std::endl;
std::cout << "Grid position: (" << gridX << ", " << rng.nextIntBounded(0, 99) << ")" << std::endl;
std::cout << "Temperature: " << temperature << "°C" << std::endl;
std::cout << "Normalized position: " << position << std::endl;
}

Reproducible Generation

void demonstrateReproducibility() {
using namespace Infinity::Engine;
const uint64_t seed = 98765;
// Generate sequence 1
PRNG rng1(seed);
std::vector<float> sequence1;
for (int i = 0; i < 10; ++i) {
sequence1.push_back(rng1.nextFloat());
}
// Generate sequence 2 with same seed
PRNG rng2(seed);
std::vector<float> sequence2;
for (int i = 0; i < 10; ++i) {
sequence2.push_back(rng2.nextFloat());
}
// Verify they're identical
bool identical = (sequence1 == sequence2);
std::cout << "Sequences are " << (identical ? "identical" : "different") << std::endl;
// Show first few values
std::cout << "First 5 values: ";
for (int i = 0; i < 5; ++i) {
std::cout << sequence1[i] << " ";
}
std::cout << std::endl;
}
float nextFloat()
Generates a random float value in the range [0, 1).

Independent Streams

void independentStreams() {
using namespace Infinity::Engine;
const uint64_t masterSeed = 12345;
// Create independent streams for different systems
PRNG terrainRNG(masterSeed, 0); // Stream 0 for terrain
PRNG vegetationRNG(masterSeed, 1); // Stream 1 for vegetation
PRNG weatherRNG(masterSeed, 2); // Stream 2 for weather
std::cout << "Terrain values: ";
for (int i = 0; i < 3; ++i) {
std::cout << terrainRNG.nextFloat() << " ";
}
std::cout << std::endl;
std::cout << "Vegetation values: ";
for (int i = 0; i < 3; ++i) {
std::cout << vegetationRNG.nextFloat() << " ";
}
std::cout << std::endl;
// Streams are completely independent - vegetation generation
// doesn't affect terrain reproducibility
}

Probability Distributions

The Infinity Engine includes many probability distributions for sophisticated generation patterns.

Continuous Distributions

Uniform Distribution

void uniformDistributions() {
using namespace Infinity::Engine;
PRNG rng(12345);
// Real uniform distribution
UniformRealDistribution<float> positionDist(-50.0f, 50.0f);
UniformRealDistribution<double> preciseAngle(0.0, 6.28318530718); // 2*PI
// Integer uniform distribution
UniformIntDistribution<int> levelDist(1, 100);
UniformIntDistribution<uint32_t> colorChannel(0, 255);
std::cout << "Object positions:" << std::endl;
for (int i = 0; i < 5; ++i) {
float x = positionDist(rng);
float z = positionDist(rng);
double angle = preciseAngle(rng);
std::cout << " Position: (" << x << ", " << z << "), Angle: " << angle << std::endl;
}
std::cout << "Game levels: ";
for (int i = 0; i < 3; ++i) {
std::cout << levelDist(rng) << " ";
}
std::cout << std::endl;
}
Platform-independent uniform integer distribution.
Definition PRNGDistribution.hpp:280
Platform-independent uniform real distribution.
Definition PRNGDistribution.hpp:55

Normal (Gaussian) Distribution

void normalDistribution() {
using namespace Infinity::Engine;
PRNG rng(54321);
// Standard normal (mean=0, stddev=1)
// Custom normal for height variation
NormalDistribution<float> heights(170.0f, 10.0f); // mean=170cm, stddev=10cm
// Terrain elevation variation
NormalDistribution<double> elevation(100.0, 15.0);
std::cout << "Character heights (cm): ";
for (int i = 0; i < 5; ++i) {
float height = heights(rng);
std::cout << static_cast<int>(height) << " ";
}
std::cout << std::endl;
std::cout << "Terrain elevation variation: ";
for (int i = 0; i < 3; ++i) {
double elev = elevation(rng);
std::cout << elev << " ";
}
std::cout << std::endl;
}
Platform-independent normal (Gaussian) distribution.
Definition PRNGDistribution.hpp:524

Exponential Distribution

void exponentialDistribution() {
using namespace Infinity::Engine;
PRNG rng(11111);
// Time between events (lambda = 0.5 events per unit time)
// Component failure rates
ExponentialDistribution<double> failureTime(0.001); // Very reliable
std::cout << "Event intervals: ";
float totalTime = 0.0f;
for (int i = 0; i < 5; ++i) {
float interval = eventTimes(rng);
totalTime += interval;
std::cout << interval << " ";
}
std::cout << "(Total: " << totalTime << ")" << std::endl;
std::cout << "Component lifetime: " << failureTime(rng) << " hours" << std::endl;
}
Platform-independent exponential distribution.
Definition PRNGDistribution.hpp:1949

Discrete Distributions

Poisson Distribution

void poissonDistribution() {
using namespace Infinity::Engine;
PRNG rng(22222);
// Number of enemies spawning per area (mean=3.5)
PoissonDistribution<int> enemySpawns(3.5);
// Resource nodes per region (mean=8.0)
PoissonDistribution<int> resources(8.0);
std::cout << "Enemy spawns per area: ";
for (int i = 0; i < 8; ++i) {
std::cout << enemySpawns(rng) << " ";
}
std::cout << std::endl;
std::cout << "Resource nodes per region: ";
for (int i = 0; i < 5; ++i) {
std::cout << resources(rng) << " ";
}
std::cout << std::endl;
}
Platform-independent Poisson distribution.
Definition PRNGDistribution.hpp:3083

Discrete Custom Distribution

void discreteCustomDistribution() {
using namespace Infinity::Engine;
PRNG rng(33333);
// Item rarity system: Common, Uncommon, Rare, Epic, Legendary
std::vector<double> rarityWeights = {50.0, 30.0, 15.0, 4.5, 0.5};
DiscreteDistribution<int> itemRarity(rarityWeights.begin(), rarityWeights.end());
// Terrain type selection with custom probabilities
DiscreteDistribution<int> terrainType({40.0, 25.0, 20.0, 10.0, 5.0});
// 0=Plains(40%), 1=Forest(25%), 2=Mountain(20%), 3=Desert(10%), 4=Swamp(5%)
std::cout << "Item drops (0=Common, 4=Legendary): ";
for (int i = 0; i < 10; ++i) {
std::cout << itemRarity(rng) << " ";
}
std::cout << std::endl;
std::cout << "Terrain types: ";
for (int i = 0; i < 8; ++i) {
std::cout << terrainType(rng) << " ";
}
std::cout << std::endl;
}
Platform-independent discrete distribution with arbitrary probabilities.
Definition PRNGDistribution.hpp:3542

Using PRNG in Components

Component Integration

// In your ProceduralComponent subclass
class RandomTerrainComponent : public ProceduralComponent
{
public:
RandomTerrainComponent(const ProceduralComponentDescription& desc)
: ProceduralComponent(desc) { }
void execute() override {
float roughness = getIn<float>("Roughness");
int size = getIn<int>("Size");
// Use component's built-in PRNG
// Note that in real-world use-cases, we strongly encourage using the Infinity::Types::Procedural::Noise type instead
generateTerrain(size, roughness);
}
private:
void generateTerrain(int size, float roughness) {
using namespace Infinity::Engine;
using namespace Infinity::Types::Containers;
Array2D<float> heightmap(size, size);
// Normal distribution for elevation variation
NormalDistribution<float> elevationNoise(0.0f, roughness);
// Poisson distribution for feature placement
PoissonDistribution<int> featureCount(5.0);
PRNG positionRng;
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
// Generate base elevation using component PRNG
float elevation = elevationNoise(prng);
// Add some deterministic noise based on position
uint64_t positionSeed = (static_cast<uint64_t>(x) << 32) | static_cast<uint64_t>(y);
positionRng.setSeed(positionSeed + prng.getSeed());
elevation += positionRng.nextFloatBounded(-0.1f, 0.1f);
heightmap(x, y) = elevation;
}
}
// Place features using Poisson distribution
int numFeatures = featureCount(prng);
logInfo("Placing " + std::to_string(numFeatures) + " terrain features");
setOut<Array2D<float>>("Heightmap", std::move(heightmap));
setOut<int>("FeatureCount", numFeatures);
}
};
float nextFloatBounded(float l, float u)
Generates a random float value within the specified inclusive range.
void setSeed(uint64_t seed, uint64_t stream=0)
Sets the seed and stream for the random number generator.
Two-dimensional array container for grid-based data in procedural generation.
Definition Array2D.hpp:82
Definition Array.hpp:25

Advanced Pattern: Layered Generation

void layeredTerrainGeneration() {
using namespace Infinity::Engine;
PRNG masterRNG(12345);
// Create independent generators for different layers
PRNG baseTerrainRNG(masterRNG(), 0);
PRNG detailRNG(masterRNG(), 1);
PRNG vegetationRNG(masterRNG(), 2);
// Base terrain with normal distribution
NormalDistribution<float> baseElevation(50.0f, 15.0f);
// Detail layer with smaller variation
NormalDistribution<float> detailNoise(0.0f, 3.0f);
// Vegetation density using gamma distribution
GammaDistribution<float> vegetationDensity(2.0f, 0.3f);
const int size = 64;
std::cout << "Generating " << size << "x" << size << " terrain with layered approach..." << std::endl;
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
// Each layer contributes independently
float base = baseElevation(baseTerrainRNG);
float detail = detailNoise(detailRNG);
float vegetation = vegetationDensity(vegetationRNG);
float finalElevation = base + detail;
// Use vegetation value for placement decisions...
}
}
std::cout << "Layered generation complete" << std::endl;
}
Platform-independent gamma distribution.
Definition PRNGDistribution.hpp:1098

Advanced Distribution Techniques

Piecewise Distributions

void piecewiseDistributions() {
using namespace Infinity::Engine;
PRNG rng(44444);
// Piecewise constant: different probabilities in different regions
std::vector<float> boundaries = {0.0f, 0.3f, 0.7f, 1.0f};
std::vector<double> densities = {0.5, 2.0, 0.8}; // Middle region more likely
boundaries.begin(), boundaries.end(),
densities.begin()
);
// Piecewise linear: smooth transitions
auto elevationFunction = [](double x) {
return std::exp(-x * x / 0.5); // Gaussian-like shape
};
20, 0.0, 1.0, elevationFunction
);
std::cout << "Biome positions: ";
for (int i = 0; i < 10; ++i) {
float pos = biomeDist(rng);
std::cout << std::fixed << std::setprecision(2) << pos << " ";
}
std::cout << std::endl;
std::cout << "Elevation samples: ";
for (int i = 0; i < 5; ++i) {
double elev = elevationDist(rng);
std::cout << std::fixed << std::setprecision(3) << elev << " ";
}
std::cout << std::endl;
}
Platform-independent piecewise constant distribution.
Definition PRNGDistribution.hpp:3859
Platform-independent piecewise linear distribution.
Definition PRNGDistribution.hpp:4254

Statistical Distributions

void statisticalDistributions() {
using namespace Infinity::Engine;
PRNG rng(55555);
// Gamma distribution for clustering effects
GammaDistribution<float> clusterSize(2.0f, 1.5f);
// Weibull distribution for lifetime modeling
WeibullDistribution<float> componentLife(1.5f, 1000.0f);
// Chi-squared for variance modeling
ChiSquaredDistribution<float> varianceModel(5.0f);
std::cout << "Cluster sizes: ";
for (int i = 0; i < 5; ++i) {
float size = clusterSize(rng);
std::cout << std::fixed << std::setprecision(1) << size << " ";
}
std::cout << std::endl;
std::cout << "Component lifetimes: ";
for (int i = 0; i < 3; ++i) {
float lifetime = componentLife(rng);
std::cout << std::fixed << std::setprecision(0) << lifetime << " ";
}
std::cout << std::endl;
}
Platform-independent chi-squared distribution.
Definition PRNGDistribution.hpp:1332
Platform-independent Weibull distribution.
Definition PRNGDistribution.hpp:2124

Practical Patterns

Weighted Random Selection

template<typename T>
class WeightedSelector {
private:
std::vector<T> items_;
public:
void addItem(const T& item, double weight) {
items_.push_back(item);
std::vector<double> weights(items_.size());
// Rebuild weights (in production, optimize this)
// This is simplified for demonstration
weights.back() = weight;
weights.begin(), weights.end()
);
}
const T& select(Infinity::Engine::PRNG& rng) {
int index = distribution_(rng);
return items_[index];
}
};
void weightedSelectionExample() {
using namespace Infinity::Engine;
PRNG rng(66666);
// Create weighted tree selector
WeightedSelector<std::string> treeSelector;
treeSelector.addItem("Oak", 40.0); // Common
treeSelector.addItem("Pine", 30.0); // Common
treeSelector.addItem("Birch", 20.0); // Less common
treeSelector.addItem("Willow", 8.0); // Rare
treeSelector.addItem("Redwood", 2.0); // Very rare
std::map<std::string, int> counts;
// Generate 1000 trees
for (int i = 0; i < 1000; ++i) {
const auto& tree = treeSelector.select(rng);
counts[tree]++;
}
std::cout << "Tree generation results:" << std::endl;
for (const auto& pair : counts) {
std::cout << " " << pair.first << ": " << pair.second << std::endl;
}
}
template class INFINITY_API_PUBLIC DiscreteDistribution< int >
Definition PRNGDistribution.cpp:61

Spatial Distribution

struct Point2D {
float x, y;
Point2D(float x_, float y_) : x(x_), y(y_) {}
};
*
void spatialDistribution() {
using namespace Infinity::Engine;
PRNG rng(77777);
// Uniform spatial distribution
UniformRealDistribution<float> xDist(0.0f, 100.0f);
UniformRealDistribution<float> yDist(0.0f, 100.0f);
// Clustered distribution using normal
NormalDistribution<float> clusterX(50.0f, 15.0f);
NormalDistribution<float> clusterY(50.0f, 15.0f);
std::vector<Point2D> uniformPoints;
std::vector<Point2D> clusteredPoints;
// Generate points
for (int i = 0; i < 20; ++i) {
// Uniform distribution
uniformPoints.emplace_back(xDist(rng), yDist(rng));
// Clustered around center
clusteredPoints.emplace_back(
std::clamp(clusterX(rng), 0.0f, 100.0f),
std::clamp(clusterY(rng), 0.0f, 100.0f)
);
}
std::cout << "Uniform points (first 5):" << std::endl;
for (int i = 0; i < 5; ++i) {
const auto& p = uniformPoints[i];
std::cout << " (" << std::fixed << std::setprecision(1)
<< p.x << ", " << p.y << ")" << std::endl;
}
std::cout << "Clustered points (first 5):" << std::endl;
for (int i = 0; i < 5; ++i) {
const auto& p = clusteredPoints[i];
std::cout << " (" << std::fixed << std::setprecision(1)
<< p.x << ", " << p.y << ")" << std::endl;
}
}

Best Practices

1. Seed Management

class ProceduralGenerator {
private:
uint64_t baseSeed_;
public:
ProceduralGenerator(uint64_t seed)
: baseSeed_(seed), masterRNG_(seed) {}
void generateWorld() {
// Create derived seeds for different systems
uint64_t terrainSeed = masterRNG_();
uint64_t vegSeed = masterRNG_();
uint64_t weatherSeed = masterRNG_();
generateTerrain(terrainSeed);
generateVegetation(vegSeed);
generateWeather(weatherSeed);
}
void regenerateWithNewSeed(uint64_t newSeed) {
baseSeed_ = newSeed;
masterRNG_.setSeed(newSeed);
generateWorld();
}
};

2. Distribution Caching

class DistributionCache {
private:
mutable std::unordered_map<std::string,
std::unique_ptr<Infinity::Engine::NormalDistribution<float>>> normalCache_;
public:
const auto& getNormalDistribution(const std::string& name,
float mean, float stddev) const {
auto it = normalCache_.find(name);
if (it == normalCache_.end()) {
auto dist = std::make_unique<Infinity::Engine::NormalDistribution<float>>(mean, stddev);
auto* ptr = dist.get();
normalCache_[name] = std::move(dist);
return *ptr;
}
return *it->second;
}
};

3. Component PRNG Usage

void MyComponent::execute() {
// ALWAYS use component's PRNG for reproducibility
float value = prng.nextFloat();
// For distributions, pass component PRNG
NormalDistribution<float> dist(0.0f, 1.0f);
float normalValue = dist(prng);
// For deterministic variation based on position
Vector3 position = getIn<Vector3>("Position");
uint64_t positionSeed = hashPosition(position);
// Create temporary PRNG for position-based randomness
PRNG posRNG(prng.getSeed() ^ positionSeed);
float positionVariation = posRNG.nextFloatBounded(-1.0f, 1.0f);
}

Troubleshooting

Non-reproducible results:

  • Ensure you're using the component's PRNG, not std::random
  • Check that seeds are being set consistently
  • Verify distribution parameters are identical
  • Watch for threading issues affecting shared state

Poor distribution quality:

  • Use appropriate distribution types for your needs
  • Consider multiple octaves for noise-like effects
  • Validate distribution parameters are reasonable
  • Test with different seeds to ensure variety

Performance issues:

  • Cache distributions when possible
  • Use simpler distributions for high-frequency generation
  • Consider lookup tables for complex custom distributions
  • Profile distribution usage in hot paths

Summary

The Infinity Engine PRNG system provides:

  • High-quality PCG-based random generation with excellent statistical properties
  • Cross-platform reproducibility ensuring identical results everywhere
  • Independent streams for parallel and modular generation
  • Comprehensive distribution library covering common and specialized use cases
  • Component integration with automatic seed management

Master these tools to create sophisticated, reproducible procedural generation systems that produce high-quality, varied content while maintaining complete control over randomness.

Next Steps

  • Experiment with different distributions for various generation tasks
  • Combine multiple distributions for complex effects
  • Study probability theory to understand when to use specific distributions
  • Profile your generation code to optimize distribution usage
  • Explore advanced techniques like rejection sampling and inverse transform sampling