A Better Variant of Smooth Terrain for Infinite Procedural Worlds

As a Roblox developer, it is currently impossible to create both performant and immersive procedural infinite worlds using smooth terrain.

The reason behind this proposal is to fix these 2 major issues:

  • The only reliable way of generating large regions is through the WriteVoxels method; however, its setup is far from efficient, with the 3-dimensional tables being a major bottleneck.
  • The terrain replicates to everyone, instead of just those in its close vicinity, resulting floating islands at large distances and wasted network and CPU performance.

My proposal is a new terrain-like instance with the following API:

<number> PriorityGenerationRadius
<number> LazyGenerationRadius
<number> Resolution
<Vector3> ChunkSize
<function> GenerationFunction ( (<Player>, <Vector3>ChunkCorner) ->(<array>Materials, <array>Occupancy) )

(<void>) :SetCenterForPlayer (<Player>, <Vector3>)

PriorityGenerationRadius and LazyGenerationRadius describe the distance at which chunks are being generated. The difference is that chunks within the prioritized radius will generate no matter what, while chunks within the lazy radius will only load if the frametimes allow it.

Resolution and ChunkSize are self explanatory, with resolution specifying how many studs a voxel occupies, while chunk size is a Vector3 that tells how many voxels a chunk contains in each dimension.

GenerationFunction is a function that is called every time a chunk is about to be generated. It accepts 2 arguments, with the first one being the player around who’s center the chunk is generated and the second one being the chunk’s (0,0,0) corner. The returned values are 2 arrays representing the materials and occupancy; however, instead of being multi-dimensional arrays, they are row-major order arrays (I don’t see a better way of dealing with this, so here’s a link with a better explanation).

Finally, SetCenterForPlayer is a method that specifies where each player is located. Once called, terrain loads/unloads chunks. It is also used to determine whether chunks should be replicated to someone or not.