Skip to main content

Aurora Terrain Generation

Aurora provides a comprehensive terrain generation system with support for procedural heightmaps, biome classification, and tessellated rendering. It includes multiple noise algorithms and terrain generators for creating realistic landscapes.

Terrain Class

The main Terrain class renders tessellated terrain surfaces from heightmaps or procedural generators. include/aurora/terrain.h:147
class Terrain : public GameObject {
public:
    // Construction
    Terrain(Resource heightmapResource);  // From heightmap file
    
    template<typename T>
    requires std::is_base_of<TerrainGenerator, T>::value
    Terrain(T generator, int width = 512, int height = 512);  // Procedural
    
    // Initialization
    void initialize() override;
    void render(float dt, std::shared_ptr<opal::CommandBuffer> commandBuffer,
               bool updatePipeline = false) override;
    
    // Biome management
    void addBiome(const Biome& biome);
    
    // Transform
    void setPosition(const Position3d& newPosition) override;
    void setRotation(const Rotation3d& newRotation) override;
    void setScale(const Scale3d& newScale) override;
    void move(const Position3d& deltaPosition) override;
    void rotate(const Rotation3d& deltaRotation) override;
    
    // Properties
    int width = 0;           // Vertices along X axis
    int height = 0;          // Vertices along Z axis
    unsigned int resolution = 20;  // LOD tessellation density
    
    float maxPeak = 48.f;    // Maximum elevation
    float seaLevel = 16.f;   // Water surface height
    
    Position3d position;
    Rotation3d rotation;
    Scale3d scale = {1.0, 1.0, 1.0};
    
    std::vector<Biome> biomes;
};
Example - Heightmap Terrain:
// Create terrain from heightmap image
Resource heightmap = Workspace::get().getResource("heightmap.png");
Terrain terrain(heightmap);
terrain.initialize();

// Configure elevation
terrain.maxPeak = 100.0f;
terrain.seaLevel = 20.0f;

// Add to scene
window.addObject(&terrain);
Example - Procedural Terrain:
// Create procedural terrain with mountain generator
MountainGenerator mountains(10.0f, 120.0f, 6, 0.6f);
Terrain terrain(mountains, 512, 512);
terrain.resolution = 25;  // Higher tessellation
terrain.initialize();

// Add biomes
Biome snow("Snow", Color::white());
snow.minHeight = 80.0f;
terrain.addBiome(snow);

Biome grass("Grass", Color(0.3, 0.6, 0.2, 1.0));
grass.minHeight = 20.0f;
grass.maxHeight = 80.0f;
terrain.addBiome(grass);

window.addObject(&terrain);

Biome System

Biomes define visual regions based on height, moisture, and temperature. include/aurora/terrain.h:51
class Biome {
public:
    std::string name;
    Texture texture;
    Color color = {1.0, 1.0, 1.0, 1.0};
    bool useTexture = false;
    
    // Thresholds
    float minHeight = -1.0f;
    float maxHeight = -1.0f;
    float minMoisture = -1.0f;
    float maxMoisture = -1.0f;
    float minTemperature = -1.0f;
    float maxTemperature = -1.0f;
    
    // Constructors
    Biome(const std::string& biomeName = "",
          const Texture& biomeTexture = Texture(),
          const Color& biomeColor = Color());
    
    Biome(const std::string& biomeName = "",
          const Color& biomeColor = Color());
    
    // Methods
    void attachTexture(const Texture& tex);
    
    // Callback executed before rasterization
    BiomeFunction condition = [](Biome&) { return; };
};
Example - Complex Biome Configuration:
// Desert biome with texture and height constraints
Biome desert("Desert");
desert.minHeight = 0.0f;
desert.maxHeight = 30.0f;
desert.minMoisture = 0.0f;
desert.maxMoisture = 0.3f;
desert.minTemperature = 0.6f;
desert.attachTexture(Texture::fromResourceName("Sand"));
desert.condition = [](Biome& b) {
    // Custom setup before rendering
    b.color = Color(0.9, 0.85, 0.7, 1.0);
};

// Forest biome
Biome forest("Forest", Color(0.2, 0.5, 0.2, 1.0));
forest.minHeight = 10.0f;
forest.maxHeight = 60.0f;
forest.minMoisture = 0.5f;
forest.minTemperature = 0.3f;
forest.maxTemperature = 0.8f;

// Snow-capped peaks
Biome snow("Snow", Color::white());
snow.minHeight = 80.0f;
snow.minTemperature = 0.0f;
snow.maxTemperature = 0.2f;

terrain.addBiome(desert);
terrain.addBiome(forest);
terrain.addBiome(snow);

Procedural Generation

Aurora includes several noise algorithms and terrain generators.

Noise Algorithms

include/aurora/procedural.h:29

PerlinNoise

Classic gradient noise for smooth, natural-looking terrain.
struct PerlinNoise {
    PerlinNoise(unsigned int seed = 0);
    float noise(float x, float y) const;  // Returns [-1, 1]
};
Example:
PerlinNoise perlin(12345);  // Seeded for reproducibility

for (int z = 0; z < gridSize; z++) {
    for (int x = 0; x < gridSize; x++) {
        float height = perlin.noise(x * 0.1f, z * 0.1f);
        heightmap[z][x] = (height + 1.0f) * 0.5f * 50.0f;  // Scale to [0, 50]
    }
}

SimplexNoise

2D Simplex noise with sharper ridges than Perlin.
class SimplexNoise {
public:
    static float noise(float xin, float yin);  // Returns ~[-1, 1]
};

WorleyNoise

Cellular noise for island and crater shapes.
class WorleyNoise {
public:
    WorleyNoise(int numPoints, unsigned int seed = 0);
    float noise(float x, float y) const;
};
Example:
WorleyNoise worley(50, 98765);  // 50 feature points

for (int y = 0; y < size; y++) {
    for (int x = 0; x < size; x++) {
        float distance = worley.noise(x * 0.01f, y * 0.01f);
        // Use distance for island-like features
        heightmap[y][x] = std::pow(1.0f - distance, 2.0f) * 100.0f;
    }
}

FractalNoise

Multi-octave Perlin noise for complex mountainous terrain.
class FractalNoise {
public:
    FractalNoise(int octaves, float persistence);
    float noise(float x, float y) const;
};
Example:
FractalNoise fractal(6, 0.5f);  // 6 octaves, 0.5 persistence

for (int z = 0; z < gridSize; z++) {
    for (int x = 0; x < gridSize; x++) {
        float height = fractal.noise(x * 0.01f, z * 0.01f);
        heightmap[z][x] = height * 100.0f;  // Scale to world units
    }
}

Noise Convenience Wrapper

include/aurora/procedural.h:147
class Noise {
public:
    static float perlin(float x, float y);
    static float simplex(float x, float y);
    static float worley(float x, float y);
    static float fractal(float x, float y, int octaves, float persistence);
    
    static float seed;
    static bool initializedSeed;
    static bool useSeed;
};
Example:
// Set global seed for all noise functions
Noise::useSeed = true;
Noise::seed = 42.0f;

// Sample different noise types
float p = Noise::perlin(x, y);
float s = Noise::simplex(x, y);
float w = Noise::worley(x, y);
float f = Noise::fractal(x, y, 5, 0.6f);

Terrain Generators

Abstract interface for procedural height generation. include/aurora/procedural.h:180
class TerrainGenerator {
public:
    virtual ~TerrainGenerator() = default;
    virtual float generateHeight(float x, float y) = 0;
    virtual void applyTo(Terrain&) const {};
};

HillGenerator

Gentle rolling hills using low-frequency Perlin noise.
class HillGenerator : public TerrainGenerator {
public:
    HillGenerator(float scale = 0.01f, float amplitude = 10.0f);
    float generateHeight(float x, float y) override;
};
Example:
HillGenerator hills(0.02f, 15.0f);  // Scale: 0.02, Max height: 15
Terrain terrain(hills, 512, 512);
terrain.initialize();

MountainGenerator

Rugged mountains using fractal noise.
class MountainGenerator : public TerrainGenerator {
public:
    MountainGenerator(float scale = 10.f,
                     float amplitude = 100.0f,
                     int octaves = 5,
                     float persistence = 0.5f);
    float generateHeight(float x, float y) override;
};
Example:
MountainGenerator mountains(8.0f, 120.0f, 6, 0.6f);
Terrain terrain(mountains, 1024, 1024);
terrain.maxPeak = 150.0f;
terrain.initialize();

PlainGenerator

Subtle undulating plains.
class PlainGenerator : public TerrainGenerator {
public:
    PlainGenerator(float scale = 0.02f, float amplitude = 2.0f);
    float generateHeight(float x, float y) override;
};

IslandGenerator

Coastline islands using Worley noise.
class IslandGenerator : public TerrainGenerator {
public:
    IslandGenerator(int numFeatures = 10, float scale = 0.01f);
    float generateHeight(float x, float y) override;
};

CompoundGenerator

Combines multiple generators by summing their contributions.
class CompoundGenerator : public TerrainGenerator {
public:
    template<typename T>
    requires std::is_base_of<TerrainGenerator, T>::value
    void addGenerator(T gen);
    
    float generateHeight(float x, float y) override;
};
Example - Layered Terrain:
CompoundGenerator combined;

// Base plains
combined.addGenerator(PlainGenerator(0.03f, 3.0f));

// Add rolling hills
combined.addGenerator(HillGenerator(0.01f, 20.0f));

// Add mountain ranges
combined.addGenerator(MountainGenerator(10.0f, 80.0f, 5, 0.5f));

// Create terrain
Terrain terrain(combined, 1024, 1024);
terrain.initialize();

// Can also sample directly
float heightValue = combined.generateHeight(100.0f, 50.0f);

Utility Functions

include/aurora/terrain.h:357
namespace aurora {
    float computeSlope(const uint8_t* heightMap,
                      int width, int height,
                      int x, int y);
}
Computes the gradient magnitude at a heightmap point, useful for biome classification based on steepness. Example:
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        float slope = aurora::computeSlope(heightmapData, width, height, x, y);
        
        if (slope > 0.7f) {
            // Steep cliff - use rock texture
            applyRockBiome(x, y);
        } else if (slope < 0.2f) {
            // Flat area - use grass texture
            applyGrassBiome(x, y);
        }
    }
}

Complete Example

Here’s a complete example creating a realistic terrain with multiple biomes:
#include <aurora/terrain.h>
#include <aurora/procedural.h>

// Create compound terrain generator
CompoundGenerator terrainGen;
terrainGen.addGenerator(PlainGenerator(0.05f, 5.0f));
terrainGen.addGenerator(HillGenerator(0.02f, 25.0f));
terrainGen.addGenerator(MountainGenerator(12.0f, 100.0f, 6, 0.55f));

// Create terrain
Terrain terrain(terrainGen, 512, 512);
terrain.resolution = 20;
terrain.maxPeak = 120.0f;
terrain.seaLevel = 15.0f;
terrain.setScale(Scale3d(2.0f, 1.5f, 2.0f));

// Load textures
Workspace::get().createResource("textures/grass.png", "Grass", ResourceType::Image);
Workspace::get().createResource("textures/rock.png", "Rock", ResourceType::Image);
Workspace::get().createResource("textures/snow.png", "Snow", ResourceType::Image);

// Configure biomes
Biome water("Water", Color(0.1, 0.3, 0.6, 0.8));
water.maxHeight = 15.0f;

Biome grass("Grass");
grass.minHeight = 15.0f;
grass.maxHeight = 60.0f;
grass.minMoisture = 0.3f;
grass.attachTexture(Texture::fromResourceName("Grass"));

Biome rock("Rock");
rock.minHeight = 60.0f;
rock.maxHeight = 100.0f;
rock.attachTexture(Texture::fromResourceName("Rock"));

Biome snow("Snow");
snow.minHeight = 100.0f;
snow.maxTemperature = 0.2f;
snow.attachTexture(Texture::fromResourceName("Snow"));

// Add all biomes
terrain.addBiome(water);
terrain.addBiome(grass);
terrain.addBiome(rock);
terrain.addBiome(snow);

// Initialize and add to scene
terrain.initialize();
window.addObject(&terrain);

Best Practices

Performance Tips:
  • Use lower resolution values (10-20) for large terrains
  • Start with 256x256 or 512x512 grid sizes
  • Combine generators instead of creating multiple terrain objects
  • Cache procedural noise results when possible
Visual Quality:
  • Use CompoundGenerator to layer different landscape features
  • Combine height, moisture, and temperature thresholds for realistic biomes
  • Add texture variation using biome conditions
  • Adjust maxPeak and seaLevel to control elevation range
Common Issues:
  • Always call terrain.initialize() before adding to scene
  • Biome thresholds should not overlap excessively
  • High resolution values (>30) can impact performance significantly
  • Procedural terrain requires width and height parameters

Build docs developers (and LLMs) love