Created
May 12, 2019 22:52
-
-
Save iskolbin/785916fa67e4dc125c84008e726b01a0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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