简体   繁体   中英

Water in a falling sand simulation

奇怪的水物理演示

I am currently working on a very simple 'Falling Sand' simulation game in C++ and SDL2, and am having problems with getting water to flow in a more realistic manner. I basically have a grid of cells that I iterate through bottom-to-top, left-to-right and if I find a water cell, I just check below, down to left, down to the right, left then right for empty cells and it moves into the first one its finds (it makes a random choice if both diagonal cells or both horizontal cells are free). I then mark the cell it moved into as processed so that it is not checked again for the rest of that loop.

My problem is a sort of 'left-bias' in how the particles move; if I spawn a square of water cells above a barrier, they will basically all shift to left without moving once the particles begin to reach the barrier, while the cells on the right will run down in the proper way. So instead of forming a nice triangular shape flowing out evenly to both sides, the whole shape will just move to the left. This effect is reversed whenever I iterate left-to-right, so I know it's something to do with that but so far I've been stumped trying to fix it. I initially thought it was a problem with how I marked the cells as processed but I've found no obvious bugs with that system in many hours of testing. Has anyone faced any similar challeneges in developing a simulation like this, or knows something that I'm missing? Any help would be very much appreciated.

EDIT: Ok so I've made a little progress, however I've ran into another bug that seems to be unrelated to iteration, since now I save a copy of the old cells and read from that to decide an update, then update the original cells and display that. This already made the sand work better, however water, which checks horizontally for free cells, now 'disappears' when it does move horizontally. I've been testing it all morning and have yet to find a solution, I thought it might've been someting to do with how I was copying the arrays over, but it seems to work as far as I can tell.

New snippets:

Simulation.cpp

void Simulation::update()
{
    copyStates(m_cells, m_oldCells); // so now oldcells is the last new state

    for(int y = m_height - 1; y>= 0; y--)
    for(int x = 0; x < m_width; x++)
        {
            Cell* c = getOldCell(x, y); // check in the old state for possible updates
            switch(c->m_type)
            {
                case EMPTY:
                    break;
                case SAND:
                    if(c->m_visited == false) update_sand(x, y);
                    break;
                case WATER:
                    if(c->m_visited == false) update_water(x, y);
                    break;
                default:
                    break;
            }
        }
}

void Simulation::update_water(int x, int y)
{
    bool down = (getOldCell(x, y+1)->m_type == EMPTY) && checkBounds(x, y+1) && !getOldCell(x, y+1)->m_visited;
    bool d_left = (getOldCell(x-1, y+1)->m_type == EMPTY) && checkBounds(x-1, y+1) && !getOldCell(x-1, y+1)->m_visited;
    bool d_right = (getOldCell(x+1, y+1)->m_type == EMPTY) && checkBounds(x+1, y+1) && !getOldCell(x+1, y+1)->m_visited ;
    bool left = (getOldCell(x-1, y)->m_type == EMPTY) && checkBounds(x-1, y) && !getOldCell(x-1, y)->m_visited ;
    bool right = (getOldCell(x+1, y)->m_type == EMPTY) && checkBounds(x+1, y) && !getOldCell(x+1, y)->m_visited ;

    // choose random dir if both are possible
    if(d_left && d_right)
    {
        int r = rand() % 2;
        if(r) d_right = false;
        else d_left = false;
    }

    if(left && right)
    {
        int r = rand() % 2;
        if(r) right = false;
        else left = false;
    }
    
    if(down)
    {
        getCell(x, y+1)->m_type = WATER; // we now update the new state
        getOldCell(x, y+1)->m_visited = true; // mark as visited so it will not be checked again in update()
    } else if(d_left)
    {
        getCell(x-1, y+1)->m_type = WATER;
        getOldCell(x-1, y+1)->m_visited = true;
    } else if(d_right)
    {
        getCell(x+1, y+1)->m_type = WATER;
        getOldCell(x+1, y+1)->m_visited = true;
    } else if(left)
    {
        getCell(x-1, y)->m_type = WATER;
        getOldCell(x-1, y)->m_visited = true;
    } else if(right)
    {
        getCell(x+1, y)->m_type = WATER;
        getOldCell(x+1, y)->m_visited = true;
    }
    
    if(down || d_right || d_left || left || right) // the original cell is now empty; update the new state
    {
        getCell(x, y)->m_type = EMPTY;
    }
}

void Simulation::copyStates(Cell* from, Cell* to)
{
    for(int x = 0; x < m_width; x++)
    for(int y = 0; y < m_height; y++)
    {
        to[x + y * m_width].m_type = from[x + y * m_width].m_type;
        to[x + y * m_width].m_visited = from[x + y * m_width].m_visited;
    }
}

Main.cpp

sim.update();

Uint32 c_sand = 0xedec9a00;
for(int y = 0; y < sim.m_height; y++)
for(int x = 0; x < sim.m_width; x++)
{
    sim.getCell(x, y)->m_visited = false;
    if(sim.getCell(x, y)->m_type == 0) screen.setPixel(x, y, 0);
    if(sim.getCell(x, y)->m_type == 1) screen.setPixel(x, y, c_sand);
    if(sim.getCell(x, y)->m_type == 2) screen.setPixel(x, y, 0x0000cc00);
}


screen.render();

I've attached a gif showing the problem, hopefully this might help make it a little clearer. You can see the sand being placed normally, then the water and the strange patterns it makes after being placed (notice how it moves off to the left when it's spawned, unlike the sand)

You also have to mark the destination postion as visited to stop multiple cells moving in to the same place.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM