Search code examples
c#loopsgraphlogicnodes

How to detect equilibrium in this specific water simulation node graph


I have an array based grid that represents terrain, and I am trying to simulate the spreading of water over the terrain. The grid is a x,y,z byte array in which each byte represents a different terrain type, in this case the value 55 represents a solid wall, 0 represents air, and 1 to 10 represent amount of water inside a given cell. In this system I am filling one cell with water by clicking it which causes its water amount to increase by 1. If there are adjacent cells with less water, the water flows to them and attempts to keep level (one cell cant rise to 4 water before all other available cells reach 3).

This is a top down view, and when I say water level I refer to the byte value (1 to 10) of the cells

I use a similar method to drain water from the system, clicking one cell to drain water will drain water from across all other cells until they all are the same level before others are allowed to drop their water level.

However I am now having problems with breaking walls that contain a lot of water and having the water spread. I use a combination of the drain and flow, draining water from the big pool and flowing it to the empty spaces.

here is a video of the system working to give a better idea: https://www.youtube.com/watch?v=o2KG1p6gj4E

here is the relevant code:

    public IEnumerator FlowRoutine(int x, int y, int z, Dictionary<Vector3Int, byte> dictcrossed)
    {
        V3IntByte ValidWaterFlowCell = new V3IntByte();

        ValidWaterFlowCell = FindNextFlowCell(x, y, z, dictcrossed);

        if (ValidWaterFlowCell.pos != InvalidVector2 )
        {
            GridArray[x, y, z] -= 1;
            GridArray[ValidWaterFlowCell.pos.x, ValidWaterFlowCell.pos.y, ValidWaterFlowCell.pos.z] += 1;
            dictcrossed.Add(ValidWaterFlowCell.pos, GridArray[ValidWaterFlowCell.pos.x, ValidWaterFlowCell.pos.y, ValidWaterFlowCell.pos.z]);

            yield return StartCoroutine(FlowRoutine(ValidWaterFlowCell.pos.x, ValidWaterFlowCell.pos.y, ValidWaterFlowCell.pos.z, dictcrossed));

        }


        UpdateAllButtons();

        yield return null;
    }
    public V3IntByte FindNextFlowCell(int x, int y, int z, Dictionary<Vector3Int, byte> dictzito)
    {
        V3IntByte SelectedCell = new V3IntByte(InvalidVector2, 0);

        Vector3Int WaterFinder = KeepPosInBounds(new Vector3Int(x - 1, y, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] + 1 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y + 1, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] + 1 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x + 1, y, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] + 1 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y - 1, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] + 1 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }
        ///
        WaterFinder = KeepPosInBounds(new Vector3Int(x - 1, y, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > 0 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y + 1, z));
        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > 0 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x + 1, y, z));
        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > 0 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y - 1, z));

        if (GridArray[x, y, z] > GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > 0 && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
            return SelectedCell;
        }

        return SelectedCell;
    }


    public IEnumerator DrainRoutine(int x, int y, int z, Dictionary<Vector3Int, byte> dictdrained)
    {
        dictdrained.Add(new Vector3Int(x, y, z), GridArray[x, y, z]);

        V3IntByte HighestValidAdjacentWater = FindHighestAdjacentWater(x,y,z,dictdrained);

        if (GridArray[x, y, z] < HighestValidAdjacentWater.water )
        {
            GridArray[x, y, z] += 1;
            GridArray[HighestValidAdjacentWater.pos.x, HighestValidAdjacentWater.pos.y, HighestValidAdjacentWater.pos.z] -= 1;

            yield return StartCoroutine(DrainRoutine(HighestValidAdjacentWater.pos.x, HighestValidAdjacentWater.pos.y, HighestValidAdjacentWater.pos.z, dictdrained));
        }

        UpdateAllButtons();

        yield return null;
    }
    public V3IntByte FindHighestAdjacentWater(int x, int y, int z, Dictionary<Vector3Int, byte> dictzito)
    {


        V3IntByte SelectedCell = new V3IntByte(InvalidVector4FirstDrain, 0);

        Vector3Int WaterFinder = KeepPosInBounds(new Vector3Int(x - 1, y, z));

        if (GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > SelectedCell.water && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 1)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y + 1, z));

        if (GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > SelectedCell.water && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 1)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
        }


        WaterFinder = KeepPosInBounds(new Vector3Int(x + 1, y, z));

        if (GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > SelectedCell.water && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 1)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
        }

        WaterFinder = KeepPosInBounds(new Vector3Int(x, y - 1, z));

        if (GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] > SelectedCell.water && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 55 && dictzito.ContainsKey(WaterFinder) == false && GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z] != 1)
        {
            SelectedCell.pos = WaterFinder;
            SelectedCell.water = GridArray[WaterFinder.x, WaterFinder.y, WaterFinder.z];
        }


        return SelectedCell;
    }


    public IEnumerator BridgeRoutine(int x, int y, int z)
    {
        StartCoroutine(DrainRoutine(x, y, z, new Dictionary<Vector3Int, byte>()));

        //
        StartCoroutine(FlowRoutine(x, y, z, new Dictionary<Vector3Int, byte>()));
        Debug.Log("running bridge");

        yield return null;
    }
    public IEnumerator MassBridgeRoutine(int x, int y, int z)
    {
        int counter = 0;

        while ( counter<100 )
        {
            counter++;
            yield return StartCoroutine(BridgeRoutine(x, y, z));
        }

        Debug.Log(counter);
        yield return null;
    }

    ///
    public Vector3Int KeepPosInBounds(Vector3Int newPos)
    {
        if (newPos.x < 0) { newPos.x = XGridSize - 1; }
        if (newPos.y < 0) { newPos.y = YGridSize - 1; }
        if (newPos.z < 0) { newPos.z = ZGridSize - 1; }
        if (newPos.x >= XGridSize) { newPos.x = 0; }
        if (newPos.y >= YGridSize) { newPos.y = 0; }
        if (newPos.z >= ZGridSize) { newPos.z = 0; }

        return newPos;
    }

I am using dictionaries to hold crossed cells in my fill and drain methods to make sure that water never doubles back and properly fills every available cell before it is allowed to raise in level.

However I have ran into a problem which is that when I run my bridge method I cannot figure out how to detect that the system has reached equilibrium and my bridging method keeps draining and flowing water into itself in an infinite loop even though the final state of the water amounts stays the same.

I would like to detect this equilibrium without having to ping the whole grid every frame for changes because I am going to be running this system on very large scale grids.

Here is a video of the massbridge method, which quickly loops the bridge method to fill the empty space: https://www.youtube.com/watch?v=-4WUivwnMZ0

However I cant figure out when to stop the loop.

The particular piece of code I'm stuck on is this part:

        while (  )
        {
            yield return StartCoroutine(BridgeRoutine(x, y, z));
        }

I am at a loss of what to enter as condition in this while loop so that it stops when the system is in equilibrium without checking every single cell from step to step.

When the system reaches equilibrium it just drains water from a cell that has 3, flows it to one that has 2 which causes it to rise to 3 and enables it to be drained again.

I would appreciate any advice thanks in advance!


Solution

  • Solved by checking for equilibrium in just the cell and 4 adjacents that originated the bridge