Friday, March 19, 2021

Creating the climate simulation

Developing this system was quite the journey, I've been working on it for a few months now and I'm still ironing it out, but it's finished for the most part.

Right now I'm simulation surface water, ground water, clouds, salt/mineral, wind, air temperature and ground temperature.
But I didn't start with all that, at first I was afraid I couldn't make the climate simulation fast enough to be simulated during the game - so I designed it so the climate will be simulated in the map editor and remain static during the actual gameplay.

I started with generating a humidity map and a salinity map for the map, I based it on procedural noise and started adding stuff to it, the main thing I did was go over each hex and if it's part of a waterbody or a river I made it increase the humidity value of the nearby area, to get some intelligent results I ran a D* algorithm on the map (using the A* grid I use for navigation) from each hex to get the hexes it effects and the travel distance to each hex, I made going up in elevation very expensive and going down to be very cheap - that gave me quite the decent flood plane.

Building on that I did the same for the salt map, only using different values for how far it reaches and how strong it is.
I gave the waterbodies a bool for fresh or salty and if the hex is part of a waterbody marked as salty or part of a river coming out of a salty waterbody it's gonna run the algorithm and add salt to whatever it reaches.



That was the basis for the climate, I knew it wasn't good yet, but couldn't think of any way to improve it.. one thing that got my attention is Catlike again, in his tutorial he's simulating a water cycle to paint the terrain pretty much like I wanted to do (and other stuff not relevant to me, but go check him out if your interested in that), I started doing the same but made some changes to the water movement logic.

Right now I had humidity and salinity, I added clouds too to bring it into line with Catlike.
I started playing around with it and it became clearer and clearer that one major thing missing in my sim is wind, so I started looking into ways to do that.

In Catlike's tutorial what he's doing is deciding on a global wind angle that is perfectly aligned to one of the six hexagonal directions (0, 60, 120, 180, 240, 300) and have a constant global strength, I did not like that part at all. (not to trash Catlike or anything, the tutorial is excellent - just not what I need here)

I wanted to make the wind more organic, simulate that too so it'll appear to have some validity to the terrain.
I looked for tutorials, libraries, or anything to help me out with this - nothing fruitful came up on my radar, so I started creating math for this.. (oh geez!)

I need ways to represent both angle and strength separately, and I wanted both of them to be normalized to a 0-1 range so I can later use them however I want.
I need to be able to know what hex neighbors a given direction is pointing towards and the ratios between them (a direction can point at only 2 hex edges at a time)
I need to know how the terrain effects the wind.
And I need a way to add/move the wind from one hex to another - how the hell do you that that with straight up angles? let's just create a function that converts an angle to a vector and do some simple and familiar vector math.

This surprisingly took a shorter time than I thought, I got it down in 3 days, but my brain was done for, math is not my strongest side, had to take some time off to recover mentally.

Coming back to it I started creating the wind simulation, what I did is generate the wind before the water cycle starts, and it was then used as conveyor belt for the clouds.
Finally the clouds aren't just hanging around water, they started to be effected by the wind - major improvement.



One other thing that was annoying me is the water flow, at this point humidity was pretty close to what's on Catlike's tutorial, it evaporates into clouds (and precipitate back), flows to neighbors (faster if they are lower elevation, slower if they are higher elevation) and carries salt when it flows (leaving it behind when evaporating).

That made all of the hexes that have a neighbor lower than them significantly drier than the surrounding environment.
I tried playing around with the values for way too long and no combination gave me the results I wanted.
I tried blurring the final map, created an algorithm that does gaussian-style blur on a hex texture, but that only took care of around 30% of the problem... oh well, at least I got that blur thingy, that'll be useful sometime.

The solution I came up with is to separate humidity into two "layers", surface water and ground water.
Surface water takes the role humidity currently plays, moves exactly the same way, evaporates (and receive precipitation) and carries salt.
Ground water acts as a capacitor of sorts, they flows to rules very similar to that of the surface water but much slower and they do not evaporate or carry salt.
The way they interact with each other is by osmosis, they try and reach a "biased equilibrium" ( 20% surface and 60% ground is "equal", for example - ratio is editable)

That worked great, made the water cycle much more stable.
At this point I started working on other stuff, like the building system and plant system, but they'll get their own development post, this one is about the climate.

After playing around with the climate system for a while I saw that it wasn't good enough..
The way you create terrain, generate wind and then water+salt works in a very unfriendly way to the user, and while that is part of the map editor - I still want as much people as possible to use it.
But the biggest issue I have with this whole system is that it's static, nothing you do in game has any effect of the climate, and that was part of the point to the game - sure, the plant and animal system are much more prevailing here gameplay-wise, but the climate itself is an integral part of the whole environment.
If you terraform field into a mound and build a castle wall over it, it should have implications to the climate in the long run, and it's just not feasible to run the climate baking algorithm every time there is a change to the map, it also means that every changes takes effect instantly - that's not gonna do at all!

After doing some thinking, I decided to make the climate dynamic in game.
This was a risky step to take, I wasn't sure at all I could pull it off in it's current state and I still had stuff I wanted to add to this system - but without this I don't think the game can be what I want it to be, so time to be a little crazy with my decision making!
I made some big changes to how the algorithm works, I tore the wind logic and the water/salt cycle logic out of their separate functions and created a new system that alternates between iterating both of them.
I don't mean to make it sound easy, it took a couple days to figure out how to structure it and then debug all the new errors.

The magic number I came up with for one whole iteration time is 37.5 seconds(on X1 game speed), a game day is 900 seconds long, and the climate ticks one time each game hour (900/24=37.5).
Now that's all fine and lovely - but how much does the climate simulation actually take? well, it's not that simple.. that depends both on the hardware it's running on and the map size, and we're still not done with adding stuff so I'll get into some actual numbers later on this post. (spoiler alert - we're good to go!)



Okay, so by this point I've got a climate system I'm happy with, it solved all the previously mentioned problems.
Players no longer need to fiddle around with 2 messy "baking" systems in the map editor - they just unpause the game time(or hit a button that simulates as fast as possible to get a new map to simulate quickly), and now if you do any sort of terraforming, build big buildings, consume or dump significant amounts of water/salt into the simulation you're gonna effect the whole climate, and it's gonna slowly work its way to accommodate changes, one iteration after another.

And to top it off it made the visuals much nicer - on the previous version both the wind and the clouds were static during gameplay, now both of them move and change over time, creating some great organic visual effects, I just need to find a better way to draw them, right now I'm just drawing a white hexagon with the GL library above each hex with alpha(transparency) by the cloud value, and that's fine, but it could be much nicer.
One thing I'm considering is sticking to my current style but give the clouds some serious geometry.
Another thing I want to experiment with is creating volumetric fog to visualize the clouds, but doing that kind of stuff is not my strongest side... best to hold off on that for now.



Now, the only thing that is missing is temperature, temperature plays a quite the role in the simulation and it's split to 2 layers in a similar way to water, air temperature and ground temperature.
High temperature increase evaporation rate, low temperature decreases evaporation rate.
Evaporation causes cooling, precipitation causes heating.
Freezing ground temperatures stops water from moving, trapping water on cold mountain tops.
Air temperature difference creates wind, wind carries air temperature just like clouds.
Ground temperature moves with ground water movement.
Osmosis happens between air and ground, like surface and ground water but this time it's a true equilibrium regarding the levels but the ground has much more thermal capacity - one hex with 100% ground temperature has the same "kinetic energy" as 42(by default, editable) hexes with 100% air temperature.
So if a hex is subject only to osmosis, ground temperature is at 100% and air temperature is at 0%, they won't meet at 50%, they'll meet at around 98%, if ground is at 0 and air is at 100 they'll meet at around 2%.

This addition was the final piece of the climate puzzle, now all that's left is to fine-tune and optimize it.

This is around when I started recording my development, I made a little video showing how the air temperature and wind interact.
It's a hydrothermal vent in an empty world, so you see a central point that is being heated.
You can see it on youtube



The major problem I encountered is salt taking over everything in certain maps..
Let me explain more clearly, the way water and salt enters the system is though the waterbodies, they add surface water and salt directly to the simulation, if a waterbody is fresh and not salty it removes from the simulation.
If we're trying to simulate a map with a large shoreline what ends up happening is the shore just keeps getting bigger and bigger, eventually taking over the whole map, I had to find someway to control it.
The first thing I tried was making the salt decay over time, that did the job, but created another problem - salt planes started disappearing (how did I not see this coming?).
After a little think what I came up with is to destroy a little salt when clouds precipitate(rain) - shores are always gonna have some amount of precipitation, even if the wind is blowing directly from inland out to sea 99% of the time, and a dry salt plane doesn't get rain so it won't get bothered.

I've made a few timelapses of a the simulation at this point, and you can see the difference visually.
Version 1 - when salt was just destroyed over time
Version 2 - removed salt destruction over time & changed how clouds are rendered
Version 3 - continuing the map from version 2, rain is now destroying salt.



About the terrain textures, they indicate the climate conditions.
it goes from sand to earth to grass based on water, high salt levels move it back to sand.
it's combined with a rock texture based on the terrain hardness, soft (no rock), medium ( 1/3 rock) and hard ( 2/3 rock)
if temperature is low enough and there is enough ground water you get snow on top of the texture.

You can see a video showcase of all different texture on youtube



About the iteration times, like I've said it depends both on the hardware and on the map.
This currently runs on the CPU, but I'm planning on doing all this work on the GPU once I figure out compute shaders, that should improve time significantly.
On the map side it depends mainly on the map size but the terrain complexity also plays a role here.
The numbers listed here are on my machine (intel i-7 7700K) and are for "worst case" map (highest complexity), and I'll also take this opportunity to list the map sizes I have planned.

Mini (35x35) - 700ms
Small (50x50) - 1800ms
Medium (70x70) - 3600ms
Large (100x100) - 7300ms
XLarge (135x135) - 13000ms

Those would be the common map sizes, but you can do any size you want and you can use different sizes for each axis (75x110, for example)

Creating a smaller map (10x10-20x20) can be useful to play with the climate values and see it iterate fast before starting it up on a big map.

Creating bigger maps is possible but requires a lot of RAM, the limit for 4GB is around 50x50, for 8GB is around 80x80 and for 16GB is around 140x140
I can't say for more RAM seeing as I only have 16GB.
Those aren't exact numbers either, seeing as not all the system ram is available to me and I need to also do other stuff with it, I was able to create a 200x200 map on my 16GB machine, but I had to drop the draw distance all the way down and it crashed after trying to generate the other stuff on the world (plants, animals, buildings)

And like I've said before - my target iteration time is 37.5 seconds (37500ms) on 1X game speed, judging from these numbers the available game speed in the world can be up to X10 on stronger machines and smaller maps, and at least X2/3 on weaker machines and bigger maps.
I think that's perfectly acceptable.



And that's about it for now..
Wow, this post is way longer than I thought it would be, thank you for reading!

No comments:

Post a Comment