Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.