I created a system similar to the one you're after in 3D. I have a short video demonstrating the simple mechanics of it here and a blog post here.
Here's a little gif I made of the pressure mechanics behind an invisible wall (played at high speed):
Let me explain the data involved, to give an idea of some of the features of the system. In the current system, each block of water contains the following in 2 Bytes:
//|0 |0 |000 |000 | |0 |0 |000 |000 |
Height is the amount of water in the cube, similar to your pressure, but my system just has 8 levels.
Direction is the direction the flow is going. When deciding where the water will flow next, it's more likely to continue in its current direction. This is also used to quickly back trace a flow back up to its source cube when needed.
IsSource indicates if this cube is a source cube, meaning it never runs out of water. Used for the source of rivers, springs, etc. The cube on the left in the gif above is a source cube, for example.
HasSource indicates if this cube is connected to a source cube. When connected to a source, cubes will try to tap the source for more water before seeking other "fuller" non-source cubes.
Largest tells this cube what the largest flow between it and its source cube is. This means if water is flowing through a narrow gap, it limits the flow to this cube.
Active is a counter. When this cube has active flow going through it, to it, or from it, active gets incremented. Otherwise active is randomly decremented. Once active hits zero (meaning not active), the amount of water will start to be reduced in this cube. This kind of acts like evaporation or soaking into the ground. (If you have flow, you should have ebb!)
FlowOut indicates if this cube is connected to a cube that's on the edge of the world. Once a path to the edge of the world is made, water tends to choose that path over any other.
Extra is an extra bit for future use.
Now that we know the data, lets look at a high level overview of the algorithm. The basic idea of the system is to prioritize flowing down and out. As I explain in the video, I work from the bottom up. Each layer of water is processed one level at a time in the y axis. The cubes for each level are processed randomly, each cube will attempt to pull water from its source on each iteration.
Flow cubes pull water from their source by following their flow direction back up until they reach a source cube or a flow cube with no parent. Storing the flow direction in each cube makes following the path to the source as easy as traversing a linked list.
The pseudo code for the algorithm is as follows:
for i = 0 to topOfWorld //from the bottom to the top
while flowouts[i].hasitems() //while this layer has flow outs
flowout = removeRandom(flowouts[i]) //select one randomly
srcpath = getPathToParent(flowout) //get the path to its parent
//set cubes as active and update their "largest" value
//also removes flow from the source for this flow cycle
//now we deal with regular flow
for i = 0 to topOfWorld //from the bottom to the top
while activeflows[i].hasitems() //while this layer has water
flowcube = removeRandom(activeflows[i]) //select one randomly
//if the current cube is already full, try to distribute to immediate neighbors
flowamt = 0
flowamt = flowcube.settleToSurrounding
srcpath = getPathToParent(flowcube) //get the path to its parent
flowamt = srcpath.setActiveAndFlux()
//if we didn't end up moving any flow this iteration, reduce the activity
//if activity is 0 already, use a small random chance of removing flow
if flowamt == 0
The basic rules for expanding a flow where (ordered by priority):
- If cube below has less water, flow down
- If adjacent cube on same level has less water, flow laterally.
- If cube above has less water AND source cube is higher than the cube above, flow up.
I know, that's pretty high level. But it's hard to get into more detail without getting way into detail.
This system works pretty well. I can easily fill up pits of water, which overflow to continue outward. I can fill up U shaped tunnels as you see in the gif above. However, as I said, the system is incomplete and I haven't worked everything out yet. I haven't worked on the flow system in a long time (I decided it wasn't needed for alpha and I'd put it on hold). However, the issues I was dealing with when I put it on hold where:
Pools. When getting a large pool of water, the pointers from child to parent are like a crazy mess of whatever random cube was selected to flow whatever direction. Like filling a bathtub with silly string. When you want to drain the tub, should you follow the path of the silly string back to its source? Or should you just take whatever is closest? So in situations where cubes are in a big pool, they should likely just ignore their parent flows and pull from whatever is above them. I came up with some basic working code for this, but was never had an elegant solution I could be happy with.
Multiple parents. A child stream could easily be fed by more than one parent stream. But the child having a pointer to a single parent wouldn't allow that. This can be fixed by using enough bits to allow for a bit for each possible parent direction. And likely changing the algorithm to randomly select a path in the case of multiple parents. But, I never got around to it to test and see what other issues that might expose.