Core library

This is the TerraProc.Core library powering the TerraProc terrain generator. You can use it to build your own server or even modify the generation pipeline.

Features

  • Composable and easily extendable generation pipeline
  • Heightmap and material generation
  • Multithreaded generation with configurable concurrency
  • Coalescing of requests
  • Cancellation of requests
  • Multiple noise sources
  • Custom generation strategies
  • Type safe and ergonomical APIs

Usage

The main thing this lib provides is a chunk provider, via the IChunkProvider interface. It is the entry point to the generation pipeline responsible for providing you with requested chunks.

You can use the ProviderFactory to construct a new instance easily, or just manually assemble the pipeline yourself, starting with the provider implementations in TerraProc.Core/Provider.


Model

The world is represented as a 2D grid of chunks. A Chunk is a fixed-sized square of tiles making up the world. It contains ChunkData - the actual data describing the chunk. For now it is a heightmap, material map and potentially more in the future.
Tiles are the individual squares making up a chunk - with a height and a material.

Constants

The chunk sizes and other important layour constants are all layed out in the GridLayout class.

The currently used value is 64x64 tile chunks.

The values are not guaranteed to be stable - they could change in the future.

Data types

For better type safety, wrappers are used for some of the primitive data types.

Height is the tile height in the heightmap, inside being a ushort.
Material is an enum with the possible materials, constrained to a byte.
Each Tile thus has size of 3 B.

Seed is a simple wrapper for the uint seeds used in terrain generation.

All of the wrappers provide extensive conversion API to make working with them as easy as possible while still avoiding most mistakes from confusing types (for this reasons, some implicit conversions are avoided).

Coordinates

To better distinguish between easily confusable tile and chunk coordinates (former being in tile units, while latter is in much coarser chunk units), distinct wrappers are provided for these.
In short, TileCoords are ChunkCoords times the size of the chunk (side of it). Conversions included take this into account to safely convert between the types, and most of the library requires you to specify the right coordinate type to avoid mistakes.
Please note that there is more nuance to TileCoords - there is a difference between global (in the world) and local (inside a single chunk, i.e. bounded to chunk size) coordinates. There are not separate wrappers for these, both are covered by TileCoords, but there are included conversions in some directions.
The local coordinates are checked in most APIs, but take care to use the right type.

The underlying primitive types are not guaranteed to be stable and could change (along with the gRPC contracts) in the future, which is why you are advised to use the wrappers.


Architecture overview

The generation pipeline is heavily built around the decorator pattern.

IChunkProvider

Interface with the GetAsync method which retrieves a chunk based on its coordinates, asynchronously.

The main concrete implementation is the BoundedParallelChunkProvider, a provider that can handle requests in parallel and also limit the maximum number of workers used.

There is also the CoalescingChunkProvider decorator, which is able to coalesce multiple requests for the same chunk to a single one to the underlyting IChunkProvider.

ITerrainGenerator

Interface representing a terrain generator. Terrain generators encapsulate the generation strategy - this is where you can make your own terrain style and just swap it with the provided generators in the pipeline.

The main difference from an IChunkProvider is that ITerrainGenerators are always synchronous, without dealing with the details of execution.
It is done using the Generate method providing the resulting chunk data, which is blocking and can be often costly to call.

The library provides the NoiseTerrainGenerator, which uses a source of noise to generate the terrain data using a custom strategy.

You can write a generator using any source of randomness for its strategy (or even no randomness at all), but some kind of a noise is usually used for procedural terrain generation. The included generators receive their noise sources in the constructor.

INoiseSource

Interface for sources of noise to be used in the terrain generators. It can be sampled via the Sample method at double 2D coordinates with a double output. This is makes it easy to use in the generators.

Useful extensions methods are provided in NoiseProviderExtensions. Namely, you can easily sample any noise source at a specified frequency and amplitude via SampleBand, and also compute a sum of samples at different octaves via SampleOctave.
Both of these are frequently used techniques in procedural terrain generation.

The library provides PerlinNoise and ValueNoise implementations, but you are free to write your own. If the generator you use needs a noise source, thanks to this interface it can be easily swapped for a different one, which allows for easy customization.

Default generation pipeline

The default generation pipeline used by ProviderFactory is this:
CoalescingChunkProvider -> BoundedParallelChunkProvider -> NoiseTerrainGenerator -> PerlinNoise

TODO:

  • caching provider decorator
  • Edit this page
In this article
Back to top Generated by DocFX