Major update on Cascaded World Maps, River Network, Elevation and Terrain generation, and Character
Welcome back to another development update for Littlecube Valley! This update will focus on the changes to our cascaded world map generation with respect to rivers and elevation. I will also discuss a little bit on the technical design for the character animations. These were all done during the last six months.
Firstly, we had to resolve the problems we encountered during our previous world generation solution. The first goal was to eliminate the use of Perlin Noise for elevation generation, because I found that being too dependent on noise solutions can produce unpredictable results.
To do this, I had decided to make use of existing Voronoi-based solutions to drastically handle the vast majority of the procedural generation. For example, the jagged shape of the world only uses 1D noise to determine whether its land or water.
Previously, we had to rely on an existing height field to determine the general terrain category that each Voronoi-cell should be part of. We eliminate this dependency by using randomised cells and do Voronoi-cell flooding from its nearest neighbours. We do this for all terrain categories, such as islands, mountains, badlands, hills, plains, lakes and coasts.
This would also address some of feedback on shapes of the world continent, resembling those like Holy Roman Empire and Phoenician Empire. Suffice to say, the current solution will allow us to generate those specific shapes, but for now, we will focus on the Holy Roman Empire.
This gives us a lot more control with respect to the shape of the world continents. There is also no need to inspect the height field from Perlin Noise solutions to determine whether it is land or water.
Next, we had some feedback on our aquifers that they were confusing. We spilt aquifers into four categories (mountain-lake, surface-pond, surface-lake, underground). Only surface-lakes will fill the size of the Voronoi-cells that are part of it, while the remaining aquifer categories only use them to guide their location.
The image below shows a mixture of these categories with rivers flowing between them.
As for elevation, we split elevation into two categories. Major elevation starts from randomised mountain peaks. Voronoi-cell flooding is used to determine the overall extent of the mountain, while each subsequent flood of the nearest neighbour seeks to reduce the elevation to its local minimum.
For example, major elevation looks like this for the root level.
We cascade this elevation as a minimum and maximum range throughout the solution, so that we will eventually get the elevation range that roughly reflects the overall elevation for that tiny spot on the world map.
Additionally, we have also refactored how the world and biome conditions. Previously, a mistake was made in cooling the temperature twice and was rectified. Drainage, rainfall and moisture are now used where appropriate. For example, drainage is best used where there is variety of values within low lying regions. On the other hand, moisture tends to be more varied in regions with higher altitude.
Next, we refactored the entire river generation solution that uses A* Search, so as to improve its accuracy and robustness. I realised that there were a number of mistakes made for the previous iteration of the solution.
We also took the opportunity to expand on the solution by adding river widths, branching and flow directions. The solution also makes use of multi-sampling in order to determine the most suitable river path among a set of generated ones. We also use a bi-directional approach to generating rivers from parent ones and choose the path that has visited more Voronoi-cells.
The implementation of river branches has also resulted in braided-looking river networks. The solution checks for suitably long river paths and spreads the river forks at locations that are not close to each other. River widths are incremented as the cascade deepens. New rivers always start at a width of 1, including branched rivers.
The image below shows a combination of varying river widths and braided rivers at cascade level 2. We leave out aquifers in this image as it can be confusing.
And here is the image with just the rivers, water and aquifers. Most of the aquifers here are located underground, and regions that surface-ponds would occur (but not restricted to the absolute area of the Voronoi-cells).
Here are the images for cascade level 1.
Here are some of the images for cascade level 2, which is the top-left quadrant of cascade level 1.
The next step was to resolve the issue on minor elevation. Previously, we simply just use the output from 2D noise perlin solutions and it resulted in a number of serious unpredictabilities and had a lack of artistic design.
An academic from Eastern Europe was lurking within Discord and he was kind enough to provide me a working solution which I had adapted it for usage in generating a height field using numerical integration. Many thanks to him, although his solution was not perfect. I was also told not to share his solution around. So sorry about that.
Our current solution which uses numerical integration has allowed us to eliminate the dependency on noise solutions. The generated height field is only of 72x72 dimension, so we also do not need to explicitly match each minute voxel column to each noise value. This solution generally uses repeated iterations of averaging and scaling in order to smooth out height values with large disparities.
You should be able to get an output that resembles something like this below. However, this solution had a disadvantage in that the process requires information from their nearest neighbours, so using it explicitly is not desirable, as our near-infinite terrain solution requires implicit generation.
Instead, we combine this solution with pre-fabricated voxel-based points, and use Constructive Solid Geometry solutions to merge and subtract points to form coherent contours that resemble that generated from 2D perlin noise solutions. This also allows us to mix artistic geometry with procedural generation, such as having specific and sufficiently accurate topologies for hilly terrain and mountainous terrain.
The image below shows some samples of our pre-fabricated voxel-based points when they are built in MagicaVoxel. This is for low lying plains terrain. For each piece of terrain below, we simply called it a tile.
Before the CSG operations can occur, we need to add terrain clusters within our working region of 64x64 tiles. This allows us to add additional variation within the height field, without the need to stretch the dimension of the height field to 2048 x 2048.
For plains-looking terrain, we first discretise the height field into two sections, with green regions representing higher elevation. We also use this discontinuity to mark out sloping regions. Each pixel below represents one tile of voxel-points as previously shown above.
After that, we generate cluster seed locations randomly and allow the clusters to grow and absorb nearby regions. Next, we determine the various discontinuities in order to mark out sloping regions between clusters.
The image below shows the amount of discontinuities generated from our algorithm. First, the bright green outlines represent the slopes between the discretised height field (red-green regions).
For the lower elevation regions of the height field, the discontinuities between clusters allow for additional up-slope inclinations (shown in the image above as dark green regions).
In contrast, for the higher elevation regions of the height field, the discontinuities between clusters allow for additional down-slope inclinations
(shown in the image above as dark cyan regions).
After that, we conduct CSG operations to merge or subtract suitable pieces of terrain tiles over one another. The result of this solution allows for generating plausible contours similar to those of 2D perlin noise solutions.
The image below shows a convex corner for a procedurally generated slope.
The image below shows another variation for a convex corner.
The image below shows a concave corner for a procedurally generated slope.
The image below shows another section for our procedurally generated slopes, in which the slope is inclined downwards behind the character.
The image below shows an elevated region that represents steep slopes. Our solution can currently handle such disparities in elevation.
The image below shows another elevated region.
Besides world map and terrain generation, we have also being working on our character animation over the last several months. The video linked at the top of this page shows you some of our combat and running animations.
Here, I intend to discuss a little bit on our character animation system. The voxel-based animations are simulated from a group of optimised geometry meshes. The inspirations came from Voxatron and MagicaVoxel's older client which it has animation key-framing.
Basically, it is a keyframe-based system that uses programmable vertex displacement and bitwise operations to eliminate non-essential parts of the character geometry when they are not considered visible. So the character is essentially made up of many different parts, with each part unique to each animation stage of the character.
Generally, the solution tries to reuse as much geometry as possible, however, there will be a little bloat because each group of animations is limited to the amount of bits an integer can have. The total amount of animation states will vastly exceed that limit. However, this can be controlled by the amount of characters on screen and how much animation groups each character should have.
Currently, we have walking, running, walk-run transitions, crouching, picking up objects, guarding, prowling, defending and attacking animations.
Additionally, the facial animations also operate independently within each character animation. We should be able to extend this functionality for simulating wind-blown hair for a female character later.
This character animation system allows us to get rid of the need for skinned mesh and skeletal animation, in which other voxel-based games like Cube World and Hytale are still using.
The edge rendering also gave us implicit anti-aliasing, giving us a high quality pre-filtered output. Lastly, this animation system also would not have been possible without a little help from Apple's graphics/GPGPU engineers over in the developers' forum, with regards to the usage of Grand Central Dispatch and Metal shader bindings.
In conclusion, we have integrated the world map generation system into our custom game engine and resolved all various outstanding issues since the last update. We have also developed a hybrid-procedural solution for artistic terrain generation. Lastly, we end this update by completing our character animation system and getting it to run various navigation and combat animations.
Our next update is to revisit the world map generation and determine the locations of various civilisation and their road networks. After that, we can attempt to convert the world map into something more visually appealing such as the maps seen in World of Warcraft and Warhammer Online.
Alternatively, we could also focus on adding visual content to the game world, such as flora and fauna. And a starting farming village to go along with it. But this is dependent on the road network as the road will be part of the village. After we have long grass and weeds working, we can attempt to do the voxel destruction.
EDIT: I realised that there is a need to work on water related features in-game, such as oceans, shorelines, rivers, lakes and ponds. This is because these features affects the terrain through more CSG operations.
Thank you for reading!