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() {
uint32_t randomInt = rng2();
int signedInt = rng2.nextInt();
float randomFloat = rng2.nextFloat();
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() {
int diceRoll = rng.nextIntBounded(1, 6);
int gridX = rng.nextIntBounded(0, 99);
float temperature = rng.nextFloatBounded(-10.0f, 35.0f);
float position = rng.nextFloatBounded(-1.0f, 1.0f);
uint32_t index = rng.nextBounded(0, 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() {
const uint64_t seed = 98765;
std::vector<float> sequence1;
for (int i = 0; i < 10; ++i) {
}
std::vector<float> sequence2;
for (int i = 0; i < 10; ++i) {
sequence2.push_back(rng2.nextFloat());
}
bool identical = (sequence1 == sequence2);
std::cout << "Sequences are " << (identical ? "identical" : "different") << std::endl;
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() {
const uint64_t masterSeed = 12345;
PRNG terrainRNG(masterSeed, 0);
PRNG vegetationRNG(masterSeed, 1);
PRNG weatherRNG(masterSeed, 2);
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;
}
Probability Distributions
The Infinity Engine includes many probability distributions for sophisticated generation patterns.
Continuous Distributions
Uniform Distribution
void uniformDistributions() {
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;
}
Normal (Gaussian) Distribution
void normalDistribution() {
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() {
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() {
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() {
std::vector<double> rarityWeights = {50.0, 30.0, 15.0, 4.5, 0.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
class RandomTerrainComponent : public ProceduralComponent
{
public:
RandomTerrainComponent(const ProceduralComponentDescription& desc)
: ProceduralComponent(desc) { }
void execute() override {
float roughness = getIn<float>("Roughness");
int size = getIn<int>("Size");
generateTerrain(size, roughness);
}
private:
void generateTerrain(int size, float roughness) {
for (int y = 0; y < size; ++y) {
for (int x = 0; x < size; ++x) {
float elevation = elevationNoise(prng);
uint64_t positionSeed = (static_cast<uint64_t>(x) << 32) | static_cast<uint64_t>(y);
positionRng.
setSeed(positionSeed + prng.getSeed());
heightmap(x, y) = elevation;
}
}
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
Advanced Pattern: Layered Generation
void layeredTerrainGeneration() {
PRNG baseTerrainRNG(masterRNG(), 0);
PRNG detailRNG(masterRNG(), 1);
PRNG vegetationRNG(masterRNG(), 2);
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) {
float base = baseElevation(baseTerrainRNG);
float detail = detailNoise(detailRNG);
float vegetation = vegetationDensity(vegetationRNG);
float finalElevation = base + detail;
}
}
std::cout << "Layered generation complete" << std::endl;
}
Platform-independent gamma distribution.
Definition PRNGDistribution.hpp:1098
Advanced Distribution Techniques
Piecewise Distributions
void piecewiseDistributions() {
std::vector<float> boundaries = {0.0f, 0.3f, 0.7f, 1.0f};
std::vector<double> densities = {0.5, 2.0, 0.8};
boundaries.begin(), boundaries.end(),
densities.begin()
);
auto elevationFunction = [](double x) {
return std::exp(-x * x / 0.5);
};
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() {
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());
weights.back() = weight;
weights.begin(), weights.end()
);
}
int index = distribution_(rng);
return items_[index];
}
};
void weightedSelectionExample() {
WeightedSelector<std::string> treeSelector;
treeSelector.addItem("Oak", 40.0);
treeSelector.addItem("Pine", 30.0);
treeSelector.addItem("Birch", 20.0);
treeSelector.addItem("Willow", 8.0);
treeSelector.addItem("Redwood", 2.0);
std::map<std::string, int> counts;
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() {
std::vector<Point2D> uniformPoints;
std::vector<Point2D> clusteredPoints;
for (int i = 0; i < 20; ++i) {
uniformPoints.emplace_back(xDist(rng), yDist(rng));
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() {
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;
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() {
float value = prng.nextFloat();
float normalValue = dist(prng);
Vector3 position = getIn<Vector3>("Position");
uint64_t positionSeed = hashPosition(position);
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