Deformable Terrain in C++
The Idea
I had a really hard time choosing what to do for my specialization but after having closer to 10 different ideas I eventually settled on deformable voxel terrain.
My idea starting out was to write a simple voxel engine similar to one I've done previously in unity with C# but far more stripped down. The hope being to make something a little larger seeing how I was told the time I had to work with was very limited.
A Promising Start
I wanted to try making the project in the C++ engine we we're making for the other TGA projects as a challenge both to myself and to try something different from the unity project I already had. You know, learn something new every day and all that.
Initial progress was fast and smooth and i quickly got procedurally generated planes working with some vertex offsets!
I then quickly moved on to implement the marching cubes algorithm. After some initial problems adapting it from my C# version as some adjustments needed to be made to more efficiently manage the memory(seeing how C# just dumps everything to heap) it worked perfectly.
Life in a Custom Engine
I then implemented a generalized system for chunks and chunk-loaders to allow for an infinite, robust and more performant world.
The first problems started when I tried spawning more than one chunk at a time. Every chunk looked the same. At first i logically assumed the noise sampling code must simply be wrong and not accounting for the chunks position in the world. I checked my code but all seemed well, and after break-pointing with the debugger the voxel coordinates generated where indeed correct so clearly it worked. Then i tried offsetting the noise manually to see if the chunks reacted at all. They did offset but they all still looked like the same offset chunk.
After looking over all the code for a long while it dawned on me that the engine recently got instanced rendering! After quickly looking into the rendering system it got obvious that the one who implemented it did the model batching by hashing the model names and grouping them by the hash.
Since all my models where programmatically generated from code and i simply named them all "Terrain". This caused the renderer to batch them as sharing their model. The chunks had perfectly valid meshes but the renderer just took the first model in the batch which due to how i looped over the chunks made it the chunk at [0,0,0]...
After realizing this i took a deep breath and quickly rewrote it to batch them based on the model pointer as a "hash" so that all renderers who use the same model simply get in the same batch. After this, my code automagically worked perfectly...
In classic programmer fashion a 3 hour problem had a 5 minute solution. *sigh*...
The Performance Problem
With the previous problem solved it all worked great but it was admittedly still lagging a bit as everything was done on the main thread. To solve this I made a queue each for the DataGeneration, Meshing, and Presentation stages of the chunk generation and made each chunk keep track of their current stage with an enum.
All chunks now start out in the "HasNothing" stage. Based on that fact it gets appended to the "DataGeneration" queue and the act of being appended also updates the chunk to the "WaitingForData" stage. From there, a worker thread can pick up the queued task and compute the data for the chunk which again updates the state... By this process the chunks slowly progress through the generation stages and since it's all built on this linear progression of tasks I can make the last task just queue the chunk for the next generation stage, effectively making the entire chunk event-based and removing any need to tick/update it every frame.
The reason for having separate queues for each generation stage is so that i can alter what´s most important to process on the go. I could prefer meshing chunks over generating new ones and so on to improve responsiveness during terrain deformations and so on...
The chunk state pipeline also means that if I want to alter a chunks data later(for say, terrain editing) i can merely set the chunk to the "HasData" state and the system will append the chunk to the right queue to start rebuilding it back up though the pipeline from that stage. this makes it very flexible for adding new stages and makes it robust to a chunk suddenly "falling down" to a lower state.
My chunks are now also closer to a pure data structure than say a component or entity in the engine. Instead, the chunks simply take a entity and renderer from a pool to represent it visually during the "Present" stage.
I also took a deep dive into constexpr among other things to try do more work at compile time to varying success. I found the weird rules of the C++ optimizer annoying as I looked at code vs assembly output to try figure out the most neat and yet most performant code.
With all this in place I can now process chunks efficiently, robustly, and in parallel for maximum performance and maintainability.
I call this piece "Damnit! I indexed with the wrong lookup table again!"
Final Result
At the end i had no time to start doing any larger optimizations like meshing chunks on the GPU, caching chunks or to add any deformation tools.
I would've liked to polish the project far more but i simply did not have the time.
Conclusion
I'm not particularly happy with the result.
I did manage to make the voxel generation and meshing work but i wanted to get so much further than i did...
Even though i knew i only had 5 weeks at 50% to work on it, it still went by in a flash, especially as that time included making a CV, PB and this entire website which all took a lot of time. That combined with that i helped other programmers at times with their own projects meant i had little time to actually get things done.
Considering how stressed this entire project made me i can't imagine how stressed I'd have felt if i had chosen a subject i knew nothing about. Especially since i usually don't get stressed about anything. So that decision at least i feel was correct....
On the bright side i learned a great deal of new information surrounding C++ itself in my attempts at optimizing. Like effective use of constexpr, noexept and the concept keyword.
Perhaps I'm hard on myself considering the limitations present but i can't help but feel i should be able to do better and that i could work more and harder and that I'm merely lazy for not making it perfect.