Skip to content

Instantly share code, notes, and snippets.

@iskolbin
Created May 12, 2019 22:52
Show Gist options
  • Select an option

  • Save iskolbin/785916fa67e4dc125c84008e726b01a0 to your computer and use it in GitHub Desktop.

Select an option

Save iskolbin/785916fa67e4dc125c84008e726b01a0 to your computer and use it in GitHub Desktop.

Revisions

  1. iskolbin created this gist May 12, 2019.
    327 changes: 327 additions & 0 deletions liquid_celluar.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,327 @@
    #ifndef LIQUID_CELLUAR_H_
    #define LIQUID_CELLUAR_H_

    #ifndef LC_NO_STDLIB
    #include <stdlib.h>
    #define LC_MALLOC malloc
    #define LC_FREE free
    #else
    #ifndef LC_MALLOC
    #error "No stdlib mode, but LC_MALLOC not defined"
    #endif
    #ifndef LC_FREE
    #error "No stdlib mode, but LC_FREE not defined"
    #endif
    #endif

    #ifdef LC_STATIC
    #define LCDEF static
    #else
    #define LCDEF extern
    #endif

    enum LCCellType {
    LC_CELL_VACUUM,
    LC_CELL_SOLID,
    };

    enum LCFlowDirection {
    LC_TOP = 0,
    LC_RIGHT = 1,
    LC_BOTTOM = 2,
    LC_LEFT = 3
    };

    typedef struct LCCell LCCell;

    struct LCCell {
    int Type;
    float Liquid;
    int Settled;
    int SettleCount;
    int FlowDirections[4];
    LCCell *Neighbors[4];
    };

    typedef struct LCWorld {
    // Max and min cell liquid values
    float MaxValue;
    float MinValue;

    // Extra liquid a cell can store than the cell above it
    float MaxCompression;

    // Lowest and highest amount of liquids allowed to flow per iteration
    float MinFlow;
    float MaxFlow;

    // Adjusts flow speed (0.0f - 1.0f)
    float FlowSpeed;

    // Keep track of modifications to cell liquid values
    float *Diffs;

    LCCell *Cells;
    int width;
    int height;
    } LCWorld;

    LCDEF void LCInit(LCWorld *world, int width, int height);
    LCDEF void LCClose(LCWorld *world);
    LCDEF void LCSimulate(LCWorld *world);

    #endif // LIQUID_CELLUAR_H_


    #ifdef LIQUID_CELLUAR_IMPLEMENTATION

    #ifndef LIQUID_CELLUAR_IMPLEMENTATION_SINGLE
    #define LIQUID_CELLUAR_IMPLEMENTATION_SINGLE
    #else
    #error "liquid_celluar header included second time"
    #endif // LIQUID_CELLUAR_IMPLEMENTATION_SINGLE

    void LCInit(LCWorld *world, int width, int height) {
    if (world->Diffs) LC_FREE(world->Diffs);
    if (world->Cells) LC_FREE(world->Cells);
    world->MaxValue = 1.0;
    world->MinValue = 0.005;
    world->MaxCompression = 0.25;
    world->MinFlow = 0.005;
    world->MaxFlow = 4;
    world->FlowSpeed = 1;
    world->width = width;
    world->height = height;
    world->Diffs = LC_MALLOC(sizeof *world->Diffs * width*height);
    world->Cells = LC_MALLOC(sizeof *world->Cells * width*height);
    }

    void LCClose(LCWorld *world) {
    if (world->Diffs) LC_FREE(world->Diffs);
    if (world->Cells) LC_FREE(world->Cells);
    world->width = 0;
    world->height = 0;
    }

    #define LC_IDX(W,X,Y) ((X)*((W)->height)+(Y))
    #define LC_MIN(X,Y) ((X)<(Y)?(X):(Y))
    #define LC_MAX(X,Y) ((X)>(Y)?(X):(Y))

    // Calculate how much liquid should flow to destination with pressure
    static float CalculateVerticalFlowValue(LCWorld *world, float remainingLiquid, LCCell *destination) {
    float sum = remainingLiquid + destination->Liquid;
    float value = 0;

    if (sum <= world->MaxValue) {
    value = world->MaxValue;
    } else if (sum < 2 * world->MaxValue + world->MaxCompression) {
    value = (world->MaxValue * world->MaxValue + sum * world->MaxCompression) / (world->MaxValue + world->MaxCompression);
    } else {
    value = (sum + world->MaxCompression) / 2;
    }

    return value;
    }

    static void ResetFlowDirections(LCCell *cell) {
    cell->FlowDirections[LC_TOP] = 0;
    cell->FlowDirections[LC_RIGHT] = 0;
    cell->FlowDirections[LC_BOTTOM] = 0;
    cell->FlowDirections[LC_LEFT] = 0;
    }

    // Force neighbors to simulate on next iteration
    static void UnsettleNeighbors(LCCell *cell) {
    if (cell->Neighbors[LC_TOP]) cell->Neighbors[LC_TOP]->Settled = 0;
    if (cell->Neighbors[LC_RIGHT]) cell->Neighbors[LC_RIGHT]->Settled = 0;
    if (cell->Neighbors[LC_BOTTOM]) cell->Neighbors[LC_BOTTOM]->Settled = 0;
    if (cell->Neighbors[LC_LEFT]) cell->Neighbors[LC_LEFT]->Settled = 0;
    }

    // Run one simulation step
    void LCSimulate(LCWorld *world) {
    int width = world->width;
    int height = world->height;
    LCCell *cells = world->Cells;
    float flow = 0;

    // Reset the diffs array
    for (int x = 0, index = 0; x < width; x++) {
    for (int y = 0; y < height; y++, index++) {
    world->Diffs[index] = 0;
    }
    }

    // Main loop
    for (int x = 0, index = 0; x < width; x++) {
    for (int y = 0; y < height; y++, index++) {

    // Get reference to Cell and reset flow
    LCCell *cell = cells + index;
    ResetFlowDirections(cell);

    // Validate cell
    if (cell->Type == LC_CELL_SOLID) {
    cell->Liquid = 0;
    continue;
    }
    if (cell->Liquid == 0)
    continue;
    if (cell->Settled)
    continue;
    if (cell->Liquid < world->MinValue) {
    cell->Liquid = 0;
    continue;
    }

    // Keep track of how much liquid this cell started off with
    float startValue = cell->Liquid;
    float remainingValue = cell->Liquid;
    flow = 0;

    // Flow to bottom cell
    if (cell->Neighbors[LC_BOTTOM] && cell->Neighbors[LC_BOTTOM]->Type == LC_CELL_VACUUM) {

    // Determine rate of flow
    flow = CalculateVerticalFlowValue(world, cell->Liquid, cell->Neighbors[LC_BOTTOM]) - cell->Neighbors[LC_BOTTOM]->Liquid;
    if (cell->Neighbors[LC_BOTTOM]->Liquid > 0 && flow > world->MinFlow)
    flow *= world->FlowSpeed;

    // Constrain flow
    flow = LC_MAX(flow, 0);
    if (flow > LC_MIN(world->MaxFlow, cell->Liquid))
    flow = LC_MIN(world->MaxFlow, cell->Liquid);

    // Update temp values
    if (flow != 0) {
    remainingValue -= flow;
    world->Diffs[index] -= flow;
    world->Diffs[LC_IDX(world, x, y + 1)] += flow;
    cell->FlowDirections[LC_BOTTOM] = 1;
    cell->Neighbors[LC_BOTTOM]->Settled = 0;
    }
    }

    // Check to ensure we still have liquid in this cell
    if (remainingValue < world->MinValue) {
    world->Diffs[index] -= remainingValue;
    continue;
    }

    // Flow to left cell
    if (cell->Neighbors[LC_LEFT] && cell->Neighbors[LC_LEFT]->Type == LC_CELL_VACUUM) {

    // Calculate flow rate
    flow = (remainingValue - cell->Neighbors[LC_LEFT]->Liquid) / 4;
    if (flow > world->MinFlow)
    flow *= world->FlowSpeed;

    // constrain flow
    flow = LC_MAX(flow, 0);
    if (flow > LC_MIN(world->MaxFlow, remainingValue))
    flow = LC_MIN(world->MaxFlow, remainingValue);

    // Adjust temp values
    if (flow != 0) {
    remainingValue -= flow;
    world->Diffs[index] -= flow;
    world->Diffs[LC_IDX(world, x - 1, y)] += flow;
    cell->FlowDirections[LC_LEFT] = 1;
    cell->Neighbors[LC_LEFT]->Settled = 0;
    }
    }

    // Check to ensure we still have liquid in this cell
    if (remainingValue < world->MinValue) {
    world->Diffs[index] -= remainingValue;
    continue;
    }

    // Flow to right cell
    if (cell->Neighbors[LC_RIGHT] && cell->Neighbors[LC_RIGHT]->Type == LC_CELL_VACUUM) {

    // calc flow rate
    flow = (remainingValue - cell->Neighbors[LC_RIGHT]->Liquid) / 3;
    if (flow > world->MinFlow)
    flow *= world->FlowSpeed;

    // constrain flow
    flow = LC_MAX(flow, 0);
    if (flow > LC_MIN(world->MaxFlow, remainingValue))
    flow = LC_MIN(world->MaxFlow, remainingValue);

    // Adjust temp values
    if (flow != 0) {
    remainingValue -= flow;
    world->Diffs[index] -= flow;
    world->Diffs[LC_IDX(world, x + 1, y)] += flow;
    cell->FlowDirections[LC_RIGHT] = 1;
    cell->Neighbors[LC_RIGHT]->Settled = 0;
    }
    }

    // Check to ensure we still have liquid in this cell
    if (remainingValue < world->MinValue) {
    world->Diffs[index] -= remainingValue;
    continue;
    }

    // Flow to Top cell
    if (cell->Neighbors[LC_TOP] && cell->Neighbors[LC_TOP]->Type == LC_CELL_VACUUM) {

    flow = remainingValue - CalculateVerticalFlowValue(world, remainingValue, cell->Neighbors[LC_TOP]);
    if (flow > world->MinFlow)
    flow *= world->FlowSpeed;

    // constrain flow
    flow = LC_MAX(flow, 0);
    if (flow > LC_MIN(world->MaxFlow, remainingValue))
    flow = LC_MIN(world->MaxFlow, remainingValue);

    // Adjust values
    if (flow != 0) {
    remainingValue -= flow;
    world->Diffs[index] -= flow;
    world->Diffs[LC_IDX(world, x, y - 1)] += flow;
    cell->FlowDirections[LC_TOP] = 1;
    cell->Neighbors[LC_TOP]->Settled = 0;
    }
    }

    // Check to ensure we still have liquid in this cell
    if (remainingValue < world->MinValue) {
    world->Diffs[index] -= remainingValue;
    continue;
    }

    // Check if cell is settled
    if (startValue == remainingValue) {
    cell->SettleCount++;
    if (cell->SettleCount >= 10) {
    ResetFlowDirections(cell);
    cell->Settled = 1;
    }
    } else {
    UnsettleNeighbors(cell);
    }
    }
    }

    // Update Cell values
    for (int x = 0, index = 0; x < width; x++) {
    for (int y = 0; y < height; y++, index++) {
    LCCell *cell = cells + index;
    cell->Liquid += world->Diffs[index];
    if (cell->Liquid < world->MinValue) {
    cell->Liquid = 0;
    cell->Settled = 0;
    }
    }
    }
    }

    #undef LC_IDX
    #undef LC_MIN
    #undef LC_MAX

    #endif // LIQUID_CELLUAR_IMPLEMENTATION