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