Strange oscillating ripples in my shallow water implementation
Asked Answered
V

1

7

I've been trying to implement the shallow water equations in Unity, but I've run in a weird bug. I get these strange oscillating ripples in my water. I made some screenshot:

enter image description here enter image description here

And a video you can find here: https://www.youtube.com/watch?v=crXLrvETdjA

I based my code on the paper Fast Hydraulic Erosion Simulation and Visualization on GPU by Xing Mei. And you can find the entire solver code here: http://pastebin.com/JktpizHW (or see below.) Each time I use a formula from the paper, I added its number as a comment.

I tried different timesteps, for the video I used 0.02, lowering it just made it oscillate slower. I also tried a bigger grid (video uses 100, I tried 200 but then the ripples just were smaller.) I checked all formulas several times, and can't find any error.

Anyone here who can figure out what's going wrong?

Extra info:

As you can see from the pastebin, I programmed it in c#. I used Unity as my engine for visualisation, and I'm just using a grid mesh to visualise the water. I alter the mesh's vertex y component to match the height I calculate.

The DoUpdate method gets a float[][] lowerLayersHeight parameter, that's basically the height of the terrain under the water. In the video it's just all 0.

public override void DoUpdate(float dt, float dx, float[][] lowerLayersHeight) {
        int x, y;
        float totalHeight, dhL, dhR, dhT, dhB;
        float dt_A_g_l = dt * _A * g / dx; //all constants for equation 2
        float K; // scaling factor for the outflow flux
        float dV;

        for (x=1 ; x <= N ; x++ ) {
                for (y=1 ; y <= N ; y++ ) {
                        //
                        // 3.2.1 Outflow Flux Computation
                        // --------------------------------------------------------------
                        totalHeight = lowerLayersHeight[x][y] + _height[x][y];
                        dhL = totalHeight - lowerLayersHeight[x-1][y] - _height[x-1][y]; //(3)
                        dhR = totalHeight - lowerLayersHeight[x+1][y] - _height[x+1][y];
                        dhT = totalHeight - lowerLayersHeight[x][y+1] - _height[x][y+1];
                        dhB = totalHeight - lowerLayersHeight[x][y-1] - _height[x][y-1];

                        _tempFlux[x][y].left =   Mathf.Max(0.0f, _flux[x][y].left        + dt_A_g_l * dhL ); //(2)
                        _tempFlux[x][y].right =  Mathf.Max(0.0f, _flux[x][y].right       + dt_A_g_l * dhR );
                        _tempFlux[x][y].top =    Mathf.Max(0.0f, _flux[x][y].top         + dt_A_g_l * dhT );
                        _tempFlux[x][y].bottom = Mathf.Max(0.0f, _flux[x][y].bottom      + dt_A_g_l * dhB );

                        float totalFlux = _tempFlux[x][y].left + _tempFlux[x][y].right + _tempFlux[x][y].top + _tempFlux[x][y].bottom;
                        if (totalFlux > 0) {
                                K = Mathf.Min(1.0f, _height[x][y] * dx * dx / totalFlux / dt);  //(4)

                                _tempFlux[x][y].left =   K * _tempFlux[x][y].left;  //(5)
                                _tempFlux[x][y].right =  K * _tempFlux[x][y].right;
                                _tempFlux[x][y].top =    K * _tempFlux[x][y].top;
                                _tempFlux[x][y].bottom = K * _tempFlux[x][y].bottom;
                        }
                        //swap temp and the real one after the for-loops

                        //
                        // 3.2.2 Water Surface
                        // ----------------------------------------------------------------------------------------
                        dV = dt * (
                                //sum in
                                _tempFlux[x-1][y].right + _tempFlux[x][y-1].top + _tempFlux[x+1][y].left + _tempFlux[x][y+1].bottom
                                //minus sum out
                                - _tempFlux[x][y].right - _tempFlux[x][y].top - _tempFlux[x][y].left - _tempFlux[x][y].bottom
                                ); //(6)
                        _tempHeight[x][y] = _height[x][y] + dV / (dx*dx); //(7)
                        //swap temp and the real one after the for-loops
                }
        }
        Helpers.Swap(ref _tempFlux, ref _flux);
        Helpers.Swap(ref _tempHeight, ref _height);
}
Villain answered 2/7, 2014 at 14:7 Comment(2)
Stackoverflow recommends that you post the smallest snippet of code that reproduces the problem or is the most relevant portion of the problem right in the text of your answer. This is so that the question can remain relevant to googlers in the future; if you link to pastebin, the pastebin may be deleted and this question becomes worthless to future users because they can't see your code.Cutworm
That's true, though it's somewhere in the algorithm and I have no clue where. It isn't too much code, so I'll just post it here too.Villain
V
6

I fixed it myself! Though of it while driving to a friend. The problem is quite simple, what I do in the bugged code is for each cell (or grid-point) calculate the flux, then the height and then I go to the next cell. What I should do is first calculate the flux for all cells, then iterate a second time over all the cells and calculate their height. So the code becomes:

for (x=1 ; x <= N ; x++ ) {
    for (y=1 ; y <= N ; y++ ) {
        //
        // 3.2.1 Outflow Flux Computation
        // --------------------------------------------------------------
        ***
    }
}

for (x=1 ; x <= N ; x++ ) {
    for (y=1 ; y <= N ; y++ ) {
        //
        // 3.2.2 Water Surface
        // ---------------------------------------------------------------------------
        ***
    }
}
Helpers.Swap(ref _tempFlux, ref _flux);
Helpers.Swap(ref _tempHeight, ref _height);

(Of course *** becomes the corresponding code from the question above.)

Now I have a working water simulation.

Villain answered 2/7, 2014 at 20:9 Comment(2)
This is such a common mistake to make it should have its own name :) It's a classic in Conway's Game of Life for example, and I've seen it a few times in various numerical simulations.Yul
Hi, can you give more details on how to run this, I have to do a similar project and I'm a C# guy, I would love to do this in C# instead of C++Gouache

© 2022 - 2024 — McMap. All rights reserved.