JohnathonNow / Bending

A 2D, multiplayer online action game.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Performance Enhancement: Fluid Simulation

JohnathonNow opened this issue · comments

Rather than always simulate every pixel, or even a cap preferring nearby pixels, or any other such trick, we should split up the map into fixed sized regions and mark them as active or not. Any call to change a part of the map (e.g. World.fill), would mark effected regions as active, as would any pixel flowing into a region. A region would be marked as inactive after one iteration in which it is unchanged. As most of the map is empty at a given time, and certainly fluids rarely cover a large portion of a map, this should improve things considerably.

There also should be some clever way of iterating over the active chunks. Quadtrees are commonly used, but in order to avoid moving a pixel more than once per iteration we move backwards through the map. I'm not quite sure how to do that with quadtrees, as a lowerleft-lowerright-most traversal would not do the correct order. There is probably some way to figure that out, but I'd have to think.
There also could be a solution involving linked lists with more than one pointer between chunks, but again I'm not sure exactly what that would entail.

Edit: The linked list approach could work as follows:
Every node has five fields:

  • next - the next node in the list
  • prev - the previous node in the list
  • nextActive - the next active node in the list
  • prevActive - the previous active node in the list
  • index - the location of a node in the list

To mark a node as inactive, you simply do:

public void deactivate() {
    this.prevActive.nextActive = this.nextActive;
    this.nextActive.prevActive = this.prevActive;
}

Naively, to mark a node as active, you do:

public void activate(Node head) {
    while (head.nextActive != null and head.nextActive.index < this.index) {
        head = head.nextActive;
    }
    this.prevActive = head;
    this.nextActive = head.nextActive;
    this.nextActive.prevActive = this;
    head.nextActive = this;
}

However, for the case of fluids falling into a new chunk, we can optimize, as we do not need to start from the head of the list. Instead, we can start from the last active chunk in the row two below the current chunk.

Similarly, when performing a fill operating, we only need to scan the list once, for the bottom-left chunk, and use that as the start for the other chunks that are affected by the fill. There may be even more efficient ways to handle that, but since those operations are rare, and scanning is quick, it probably is unnecessary.