This is pretty cool. Out of curiosity, did you solve your issues with the gas flows being weird (in particular, with them being biased in the direction of the loop over the tiles)? I ask because I tried to write a meteorological simulator once for a fantasy world using some similar ideas, and I ran into so many problems I eventually just stopped trying. I was trying to generate weather patterns based on land masses and incident sunlight, but my attempts to connect temperature, pressure and so on either led to unstable simulations that exploded as pressure flowed wildly back and forth between cells or other issues like the pressure flowing in one direction more strongly than it should have.
A two step process like you mentioned might have helped, but I never tried it for performance reasons. I think what I was trying to do was probably infeasible.
No I haven't managed to solve that yet. Though, if your pressure flow is unstable it may be due to very small pressure differences between tiles. I set a minimum pressure difference between tiles for flow to occur, so that the sim isn't infinitely pushing tiny amounts of gas. Before I added mixtures in I attempted to move over to a two step process. The result was amusing, it looked very similar to a wave diffraction simulation for whatever reason. I failed to recreate the effect now to get a gif of it sadly. Anyways, I couldn't get it to behave correctly, so I tabled it for later as I wanted to move onto adding mixtures and heat flow to the sim. If the way I was doing the two step method worked it shouldn't have been any more performance intensive than before, though perhaps it wasn't a valid method.
It turns out that when I added mixtures I broke everything horribly. I didn't realize how much until I tested it out more. The pressure simulation still had a direction bias and was less stable, and heat flow had halted completely. Before I decided to move over to Unity(still debating the switch) I had the heat sim partially working again, and I was still fixing the pressure flow. I think that part of the reason I've been having so many issues with bugs is just bad code. For my tile and gas mix classes I had most of the member variables as public, and was accessing them directly. While I was cleaning up code I made many of the members private and made functions to access them, that way there is only one point of failure instead of me forgetting to set a variable or two on occasion. Below, I'll do my best to explain how my pressure and heat flow worked before adding mixtures, as it will be simpler to describe and everything was functioning at that point. These systems are still prototypes so any input is welcome. I'm no expert in this by any means.
I set up a tick system to allow the simulation speed to be changed on the fly. At each frame the delta time would be pushed to the atmos code, and if enough time had passed for a tick (I used 10ms as my default) then the pressure for the whole map would be updated. Keep in mind that the heat flow sim had this as well and I just locked it to the same rate as the pressure sim. The thought behind this was that the update rate could be lowered for slower computers if necessary.
The pressure simulation is really basic. It doesn't keep track of the momentum or velocity of the gas in each tile. The important values tracked for each tile are pressure, temperature, amount of gas (in moles), whether the tile is solid, and whether or not it's a "space" tile. Each update cycle the function UpdateAtmosphere() is called. It loops through the entire map and checks if each tile should flow gas to its surrounding tiles. If the tile is solid for example it won't flow, as it has no pressure. The effect of a tile being space is configurable depending on performance needs. I currently simulate space tiles, but it could ignore them if more speed was needed. If the tile is determined suitable for pressure flow then Flow(int Tile1, int Tile2) is called for the tile and all of it's surrounding tiles. With Tile1 being the index of the tile in question and Tile2 being the index of the tile which gas is being moved to. This is called once for each adjacent tile.
Here's the code for Flow(), simplified with the assumption that space tiles are being simulated:
void Atmosphere::Flow(int Tile1, int Tile2)
{
if (!TileCache->at(Tile2).Solid) //Don't flow to Tile2 if it's solid
{
if (PressureDifference(Tile1, Tile2) > MinimumFlowPressure)//If difference < 0 then no flow. Goes from high P to low P. MinimumFlowPressureCheck for stability.
{
double MolChange = 0;
if (!TileCache->at(Tile1).Space)
{
//The less flowable tiles there are the more of a fraction of the gas to flow to Tile2. This means tight hallways have higher flow rates.
int FlowableTiles = AdjacentFlowableTiles(Tile1);
if (FlowableTiles != 0)
{
MolChange = (1.00 / FlowableTiles) * TileCache->at(Tile1).Amount;
}
}
else
{
int FlowableTiles = AdjacentFlowableTiles(Tile1);
MolChange = (1.00 / (FlowableTiles + 2)) * TileCache->at(Tile1).Amount; //Add 2 more flowable tiles to space to signify ceiling and floor (up/down) flow.
}
TileCache->at(Tile1).Amount -= MolChange;
TileCache->at(Tile1).UpdatePressure(); //Ideal gas law PV=nRT -> P = nRT / V ... Assuming constant tile volume of V = 2m^2 with tiles being 1x1x2m
if (TileCache->at(Tile1).Amount < MinimumMol) //In the newer code Amount is a private variable and a function changes it and checks for incorrect values.
{
TileCache->at(Tile1).Amount = MinimumMol;
}
TileCache->at(Tile2).Amount += MolChange;
TileCache->at(Tile2).UpdatePressure();
}
}
}
Things worth noting are that flow does not occur if Tile2 is a solid, and the amount of flow to Tile2 is dependent on the amount of adjacent flowable tiles. A flowable tile is simply defined as a non solid tile. It also considers map borders, so a tile on the border of the map can only have 5 adjacent flowable tiles maximum. One issue I see now is that if there is only one adjacent flowable tile, then the entirety of the gas on Tile1 will be moved to Tile2. I should probably set up a minimum flow ratio variable to avoid this. A benefit of checking the number of adjacent flowable tiles is that a small hallways or gap should have a higher flowrate than a larger one. Another thing to note is that I made the minimum pressure zero throughout my code as it is absolute pressure not relative. I started with this manner for pressure flow as I couldn't find any equations to define the movement rates of gases in this scenario during the first pass of my research. Any suggestions on that are welcome.
The heat flow simulation worked in a similar manner. Each tick the sim would loop through every tile, and flow heat from it to all adjacent tiles. Below I'll derive the equation I use to calculate temperature change for each tile, from what I can tell it's fairly accurate, though some assumptions are made for the sake of performance / simplicity.
Our first equation, from the 1st law of thermodynamics, assuming the work done by the system is zero:
ΔQ = mcΔT = mc(T2 - T1) [1] (c is the specific heat of the object (J / kg * K), m is the mass in kg, T is the temperature in kelvin)
Next, with T
1,a and T
1,b being the initial temperatures of tile a and tile b (1 and 2) respectively:
ΔQ = Δt * (T1,a - T1,b) / Rtotal [2] (Δt is the change in time is seconds, T
1,a and T
1,b are the initial temperatures of tiles a and b respectively)
With R
total is the total thermal resistance of the system, in this case I'm assuming a system with conductive and convective resistance acting in series. Parallel might be a better way to do this but I made the assumption of series for now to keep it simple. If you'd like some more info on thermal resistance this guide is pretty good:
https://neutrium.net/heat_transfer/thermal-resistance/ Note:
Rconduction = L / kA [3] (L = conduction length (m), k = thermal conductivity (W / m * K), A = conduction area (m^2))
Rconvection = 1 / hA [4] (h = heat transfer coefficient (W / m^2 * K), A = convection area (m^2))
Rtotal = Rconduction + Rconvection [5] (Assuming series thermal resistance. R
convection is zero if neither tile has gas present)
Note that I am assuming a constant value for heat transfer coefficient for each gas. IIRC heat transfer coefficient is highly variable dependent on the gas type, pressure, and temperature. So this is a bad way to do it. I will have to look back as my thermodynamics class notes and read up a bit on approximating the value. If worst comes to worst I could toss one of the charts into excel and try to form an equation for the value with regression, then just have the json files for each gas take a regular expression for the heat transfer coefficient value. We'll see though, a constant value could work well enough for our purposes. Anyways, the final equation is formed by setting (1) equal to (2) and solving for T
2,a, like so:
ΔQ = maca(T2,a - T1,a) = t * (T1,a - T1,b) / RtotalBy solving for T
2,a you get our final equation:
T2,a = [t * (T1,a - T1,b) / (macaRtotal)] + T1,a [6]From here you can easily calculate the change in energy of the system and the final temperature of tile b using equation 1. That's quite the info dump and there are likely a few errors I missed. I wrote it in two sessions so there might be conflicting info between the pressure and heat portions of the post. An update on the project. I've been working with Unity more, playing around with character movement and map gen, and getting a handle on the system. Honestly still debating the switch as there are merits to sticking with c++. I'll make a final decision at some point. Let me know what you think or if you have more questions / see some mistakes.
Edit: I ignored radiative resistance and heat transfer for now for performance reasons. I might add it later for certain situations, such as if you wanted to cool your gases by sticking a radiator out into space filled with them or perhaps having heat radiate from hot objects in space in certain instances. Perhaps could result in a dumped nuclear core being dangerous at range even in vacuum or other fun ideas.