659 lines
21 KiB
C++
659 lines
21 KiB
C++
|
#ifndef STAR_CELLULAR_LIQUID_HPP
|
||
|
#define STAR_CELLULAR_LIQUID_HPP
|
||
|
|
||
|
#include "StarVariant.hpp"
|
||
|
#include "StarRect.hpp"
|
||
|
#include "StarMultiArray.hpp"
|
||
|
#include "StarMap.hpp"
|
||
|
#include "StarOrderedSet.hpp"
|
||
|
#include "StarRandom.hpp"
|
||
|
#include "StarBlockAllocator.hpp"
|
||
|
|
||
|
namespace Star {
|
||
|
|
||
|
struct CellularLiquidCollisionCell {};
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
struct CellularLiquidFlowCell {
|
||
|
Maybe<LiquidId> liquid;
|
||
|
float level;
|
||
|
float pressure;
|
||
|
};
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
struct CellularLiquidSourceCell {
|
||
|
LiquidId liquid;
|
||
|
float pressure;
|
||
|
};
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
using CellularLiquidCell = Variant<CellularLiquidCollisionCell, CellularLiquidFlowCell<LiquidId>, CellularLiquidSourceCell<LiquidId>>;
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
struct CellularLiquidWorld {
|
||
|
virtual ~CellularLiquidWorld();
|
||
|
|
||
|
virtual Vec2I uniqueLocation(Vec2I const& location) const;
|
||
|
|
||
|
virtual CellularLiquidCell<LiquidId> cell(Vec2I const& location) const = 0;
|
||
|
|
||
|
// Should return an amount between 0.0 and 1.0 as a percentage of liquid
|
||
|
// drain at this position
|
||
|
virtual float drainLevel(Vec2I const& location) const;
|
||
|
|
||
|
// Will be called only on cells which for which the cell method returned a
|
||
|
// flow cell, to update the flow cell.
|
||
|
virtual void setFlow(Vec2I const& location, CellularLiquidFlowCell<LiquidId> const& flow) = 0;
|
||
|
|
||
|
// Called once for every active liquid <-> liquid interaction of different
|
||
|
// liquid types each update. Will be called AFTER pushing all the flow
|
||
|
// values back out so modifications to liquids are sensible.
|
||
|
virtual void liquidInteraction(Vec2I const& a, LiquidId aLiquid, Vec2I const& b, LiquidId bLiquid);
|
||
|
// Called once for every liquid collision each update. Also called after
|
||
|
// pushing all the flow values out, so changes to liquids can sensibly be
|
||
|
// performed here.
|
||
|
virtual void liquidCollision(Vec2I const& pos, LiquidId liquid, Vec2I const& collisionPos);
|
||
|
};
|
||
|
|
||
|
struct LiquidCellEngineParameters {
|
||
|
float lateralMoveFactor;
|
||
|
float spreadOverfillUpFactor;
|
||
|
float spreadOverfillLateralFactor;
|
||
|
float spreadOverfillDownFactor;
|
||
|
float pressureEqualizeFactor;
|
||
|
float pressureMoveFactor;
|
||
|
float maximumPressureLevelImbalance;
|
||
|
float minimumLivenPressureChange;
|
||
|
float minimumLivenLevelChange;
|
||
|
float minimumLiquidLevel;
|
||
|
float interactTransformationLevel;
|
||
|
};
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
class LiquidCellEngine {
|
||
|
public:
|
||
|
typedef shared_ptr<CellularLiquidWorld<LiquidId>> CellularLiquidWorldPtr;
|
||
|
|
||
|
LiquidCellEngine(LiquidCellEngineParameters parameters, CellularLiquidWorldPtr cellWorld);
|
||
|
|
||
|
unsigned liquidTickDelta(LiquidId liquid);
|
||
|
void setLiquidTickDelta(LiquidId liquid, unsigned tickDelta);
|
||
|
|
||
|
void setProcessingLimit(Maybe<unsigned> processingLimit);
|
||
|
|
||
|
List<RectI> noProcessingLimitRegions() const;
|
||
|
void setNoProcessingLimitRegions(List<RectI> noProcessingLimitRegions);
|
||
|
|
||
|
void visitLocation(Vec2I const& location);
|
||
|
void visitRegion(RectI const& region);
|
||
|
|
||
|
void update();
|
||
|
|
||
|
size_t activeCells() const;
|
||
|
size_t activeCells(LiquidId liquid) const;
|
||
|
bool isActive(Vec2I const& pos) const;
|
||
|
|
||
|
private:
|
||
|
enum class Adjacency {
|
||
|
Left,
|
||
|
Right,
|
||
|
Bottom,
|
||
|
Top
|
||
|
};
|
||
|
|
||
|
struct WorkingCell {
|
||
|
Vec2I position;
|
||
|
Maybe<LiquidId> liquid;
|
||
|
bool sourceCell;
|
||
|
float level;
|
||
|
float pressure;
|
||
|
|
||
|
WorkingCell* leftCell;
|
||
|
WorkingCell* rightCell;
|
||
|
WorkingCell* topCell;
|
||
|
WorkingCell* bottomCell;
|
||
|
};
|
||
|
|
||
|
template <typename Key, typename Value>
|
||
|
using BAHashMap = StableHashMap<Key, Value, hash<Key>, std::equal_to<Key>, BlockAllocator<pair<Key const, Value>, 4096>>;
|
||
|
|
||
|
template <typename Value>
|
||
|
using BAHashSet = HashSet<Value, hash<Value>, std::equal_to<Value>>;
|
||
|
|
||
|
template <typename Value>
|
||
|
using BAOrderedHashSet = OrderedHashSet<Value, hash<Value>, std::equal_to<Value>, BlockAllocator<Value, 4096>>;
|
||
|
|
||
|
void setup();
|
||
|
void applyPressure();
|
||
|
void spreadPressure();
|
||
|
void limitPressure();
|
||
|
void pressureMove();
|
||
|
void spreadOverfill();
|
||
|
void levelMove();
|
||
|
void findInteractions();
|
||
|
void finish();
|
||
|
|
||
|
WorkingCell* workingCell(Vec2I p);
|
||
|
WorkingCell* adjacentCell(WorkingCell* cell, Adjacency adjacency);
|
||
|
|
||
|
void setPressure(float pressure, WorkingCell& cell);
|
||
|
void transferPressure(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse);
|
||
|
void transferLevel(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse);
|
||
|
void setLevel(float level, WorkingCell& cell);
|
||
|
|
||
|
RandomSource m_random;
|
||
|
LiquidCellEngineParameters m_engineParameters;
|
||
|
CellularLiquidWorldPtr m_cellWorld;
|
||
|
|
||
|
BAHashMap<LiquidId, BAOrderedHashSet<Vec2I>> m_activeCells;
|
||
|
BAHashMap<LiquidId, unsigned> m_liquidTickDeltas;
|
||
|
Maybe<unsigned> m_processingLimit;
|
||
|
List<RectI> m_noProcessingLimitRegions;
|
||
|
uint64_t m_step;
|
||
|
|
||
|
BAHashMap<Vec2I, Maybe<WorkingCell>> m_workingCells;
|
||
|
List<WorkingCell*> m_currentActiveCells;
|
||
|
BAHashSet<Vec2I> m_nextActiveCells;
|
||
|
BAHashSet<tuple<Vec2I, LiquidId, Vec2I, LiquidId>> m_liquidInteractions;
|
||
|
BAHashSet<tuple<Vec2I, LiquidId, Vec2I>> m_liquidCollisions;
|
||
|
};
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
CellularLiquidWorld<LiquidId>::~CellularLiquidWorld() {}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
Vec2I CellularLiquidWorld<LiquidId>::uniqueLocation(Vec2I const& location) const {
|
||
|
return location;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
float CellularLiquidWorld<LiquidId>::drainLevel(Vec2I const&) const {
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void CellularLiquidWorld<LiquidId>::liquidInteraction(Vec2I const&, LiquidId, Vec2I const&, LiquidId) {}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void CellularLiquidWorld<LiquidId>::liquidCollision(Vec2I const&, LiquidId, Vec2I const&) {}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
LiquidCellEngine<LiquidId>::LiquidCellEngine(LiquidCellEngineParameters parameters, CellularLiquidWorldPtr cellWorld)
|
||
|
: m_engineParameters(parameters), m_cellWorld(cellWorld), m_step(0) {}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
unsigned LiquidCellEngine<LiquidId>::liquidTickDelta(LiquidId liquid) {
|
||
|
return m_liquidTickDeltas.value(liquid, 1);
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setLiquidTickDelta(LiquidId liquid, unsigned tickDelta) {
|
||
|
m_liquidTickDeltas[liquid] = tickDelta;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setProcessingLimit(Maybe<unsigned> processingLimit) {
|
||
|
m_processingLimit = processingLimit;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
List<RectI> LiquidCellEngine<LiquidId>::noProcessingLimitRegions() const {
|
||
|
return m_noProcessingLimitRegions;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setNoProcessingLimitRegions(List<RectI> noProcessingLimitRegions) {
|
||
|
m_noProcessingLimitRegions = noProcessingLimitRegions;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::visitLocation(Vec2I const& p) {
|
||
|
m_nextActiveCells.add(p);
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::visitRegion(RectI const& region) {
|
||
|
for (int x = region.xMin(); x < region.xMax(); ++x) {
|
||
|
for (int y = region.yMin(); y < region.yMax(); ++y)
|
||
|
m_nextActiveCells.add({x, y});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::update() {
|
||
|
setup();
|
||
|
applyPressure();
|
||
|
spreadPressure();
|
||
|
limitPressure();
|
||
|
pressureMove();
|
||
|
spreadOverfill();
|
||
|
levelMove();
|
||
|
findInteractions();
|
||
|
finish();
|
||
|
|
||
|
++m_step;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
size_t LiquidCellEngine<LiquidId>::activeCells() const {
|
||
|
size_t totalSize = 0;
|
||
|
for (auto const& p : m_activeCells)
|
||
|
totalSize += p.second.size();
|
||
|
return totalSize;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
size_t LiquidCellEngine<LiquidId>::activeCells(LiquidId liquid) const {
|
||
|
return m_activeCells.value(liquid).size();
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
bool LiquidCellEngine<LiquidId>::isActive(Vec2I const& pos) const {
|
||
|
for (auto const& p : m_activeCells) {
|
||
|
if (p.second.contains(pos))
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setup() {
|
||
|
// In case an exception occurred during the last update, clear potentially
|
||
|
// stale data here
|
||
|
m_workingCells.clear();
|
||
|
m_currentActiveCells.clear();
|
||
|
|
||
|
for (auto& activeCellsPair : m_activeCells) {
|
||
|
unsigned tickDelta = liquidTickDelta(activeCellsPair.first);
|
||
|
if (tickDelta == 0 || m_step % tickDelta != 0)
|
||
|
continue;
|
||
|
|
||
|
size_t limitedCellNumber = 0;
|
||
|
for (auto const& pos : activeCellsPair.second.values()) {
|
||
|
if (m_processingLimit) {
|
||
|
bool foundInUnlimitedRegion = false;
|
||
|
for (auto const& region : m_noProcessingLimitRegions) {
|
||
|
if (region.contains(pos)) {
|
||
|
foundInUnlimitedRegion = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!foundInUnlimitedRegion) {
|
||
|
if (limitedCellNumber < *m_processingLimit)
|
||
|
++limitedCellNumber;
|
||
|
else
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto cell = workingCell(pos);
|
||
|
if (!cell || cell->liquid != activeCellsPair.first) {
|
||
|
activeCellsPair.second.remove(pos);
|
||
|
} else {
|
||
|
m_currentActiveCells.append(cell);
|
||
|
activeCellsPair.second.remove(pos);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sort(m_currentActiveCells, [](WorkingCell* lhs, WorkingCell* rhs) {
|
||
|
return lhs->position[1] < rhs->position[1];
|
||
|
});
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::applyPressure() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid || selfCell->sourceCell)
|
||
|
continue;
|
||
|
|
||
|
auto topCell = adjacentCell(selfCell, Adjacency::Top);
|
||
|
if (topCell && selfCell->liquid == topCell->liquid)
|
||
|
setPressure(max(selfCell->pressure, topCell->pressure + min(topCell->level, 1.0f)), *selfCell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::spreadPressure() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid)
|
||
|
continue;
|
||
|
|
||
|
auto spreadPressure = [&](Adjacency adjacency, float bias) {
|
||
|
auto targetCell = adjacentCell(selfCell, adjacency);
|
||
|
if (targetCell && !targetCell->sourceCell)
|
||
|
transferPressure((selfCell->pressure + bias - targetCell->pressure) * m_engineParameters.pressureEqualizeFactor, *selfCell, *targetCell, true);
|
||
|
};
|
||
|
|
||
|
if (m_random.randb()) {
|
||
|
spreadPressure(Adjacency::Left, 0.0f);
|
||
|
spreadPressure(Adjacency::Right, 0.0f);
|
||
|
} else {
|
||
|
spreadPressure(Adjacency::Right, 0.0f);
|
||
|
spreadPressure(Adjacency::Left, 0.0f);
|
||
|
}
|
||
|
|
||
|
spreadPressure(Adjacency::Bottom, 1.0f);
|
||
|
spreadPressure(Adjacency::Top, -1.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::limitPressure() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
float level = min(selfCell->level, 1.0f);
|
||
|
auto topCell = adjacentCell(selfCell, Adjacency::Top);
|
||
|
|
||
|
// Force the pressure to the cell level if there is empty space above,
|
||
|
// otherwise simply make sure the pressure is at least the level
|
||
|
if (topCell && !topCell->liquid)
|
||
|
setPressure(level, *selfCell);
|
||
|
else
|
||
|
setPressure(max(selfCell->pressure, level), *selfCell);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::pressureMove() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid)
|
||
|
continue;
|
||
|
|
||
|
auto pressureMove = [&](Adjacency adjacency) {
|
||
|
auto targetCell = adjacentCell(selfCell, adjacency);
|
||
|
if (targetCell && !targetCell->sourceCell && targetCell->level >= selfCell->level) {
|
||
|
float amount = (selfCell->pressure - targetCell->pressure) * m_engineParameters.pressureMoveFactor;
|
||
|
amount = min(amount, selfCell->level - (1.0f - m_engineParameters.maximumPressureLevelImbalance));
|
||
|
amount = min(amount, (1.0f + m_engineParameters.maximumPressureLevelImbalance) - targetCell->level);
|
||
|
transferLevel(amount, *selfCell, *targetCell, false);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (m_random.randb()) {
|
||
|
pressureMove(Adjacency::Left);
|
||
|
pressureMove(Adjacency::Right);
|
||
|
} else {
|
||
|
pressureMove(Adjacency::Right);
|
||
|
pressureMove(Adjacency::Left);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::spreadOverfill() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid || selfCell->sourceCell)
|
||
|
continue;
|
||
|
|
||
|
auto spreadOverfill = [&](Adjacency adjacency, float factor) {
|
||
|
float overfill = selfCell->level - 1.0f;
|
||
|
if (overfill > 0.0f) {
|
||
|
auto targetCell = adjacentCell(selfCell, adjacency);
|
||
|
if (targetCell)
|
||
|
transferLevel(min(overfill, (selfCell->level - targetCell->level)) * factor, *selfCell, *targetCell, false);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
spreadOverfill(Adjacency::Top, m_engineParameters.spreadOverfillUpFactor);
|
||
|
|
||
|
if (m_random.randb()) {
|
||
|
spreadOverfill(Adjacency::Left, m_engineParameters.spreadOverfillLateralFactor);
|
||
|
spreadOverfill(Adjacency::Right, m_engineParameters.spreadOverfillLateralFactor);
|
||
|
} else {
|
||
|
spreadOverfill(Adjacency::Right, m_engineParameters.spreadOverfillLateralFactor);
|
||
|
spreadOverfill(Adjacency::Left, m_engineParameters.spreadOverfillLateralFactor);
|
||
|
}
|
||
|
|
||
|
spreadOverfill(Adjacency::Bottom, m_engineParameters.spreadOverfillDownFactor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::levelMove() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid)
|
||
|
continue;
|
||
|
|
||
|
auto belowCell = adjacentCell(selfCell, Adjacency::Bottom);
|
||
|
if (belowCell)
|
||
|
transferLevel(min(1.0f - belowCell->level, selfCell->level), *selfCell, *belowCell, false);
|
||
|
|
||
|
setLevel(selfCell->level * (1.0f - m_cellWorld->drainLevel(selfCell->position)), *selfCell);
|
||
|
|
||
|
auto lateralMove = [&](Adjacency adjacency) {
|
||
|
auto targetCell = adjacentCell(selfCell, adjacency);
|
||
|
if (targetCell)
|
||
|
transferLevel((selfCell->level - targetCell->level) * m_engineParameters.lateralMoveFactor, *selfCell, *targetCell, false);
|
||
|
};
|
||
|
|
||
|
if (m_random.randb()) {
|
||
|
lateralMove(Adjacency::Left);
|
||
|
lateralMove(Adjacency::Right);
|
||
|
} else {
|
||
|
lateralMove(Adjacency::Right);
|
||
|
lateralMove(Adjacency::Left);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::findInteractions() {
|
||
|
for (auto const& selfCell : m_currentActiveCells) {
|
||
|
if (!selfCell->liquid)
|
||
|
continue;
|
||
|
|
||
|
for (auto adjacency : {Adjacency::Bottom, Adjacency::Top, Adjacency::Left, Adjacency::Right}) {
|
||
|
auto targetCell = adjacentCell(selfCell, adjacency);
|
||
|
if (!targetCell) {
|
||
|
Vec2I adjacentPos = selfCell->position;
|
||
|
if (adjacency == Adjacency::Left)
|
||
|
adjacentPos += Vec2I(-1, 0);
|
||
|
else if (adjacency == Adjacency::Right)
|
||
|
adjacentPos += Vec2I(1, 0);
|
||
|
else if (adjacency == Adjacency::Bottom)
|
||
|
adjacentPos += Vec2I(0, -1);
|
||
|
else if (adjacency == Adjacency::Top)
|
||
|
adjacentPos += Vec2I(0, 1);
|
||
|
m_liquidCollisions.add(make_tuple(selfCell->position, *selfCell->liquid, adjacentPos));
|
||
|
|
||
|
} else if (targetCell->liquid && *targetCell->liquid != *selfCell->liquid) {
|
||
|
if (targetCell->level <= m_engineParameters.interactTransformationLevel
|
||
|
|| selfCell->level <= m_engineParameters.interactTransformationLevel) {
|
||
|
if (selfCell->level > targetCell->level)
|
||
|
targetCell->liquid = selfCell->liquid;
|
||
|
else
|
||
|
selfCell->liquid = targetCell->liquid;
|
||
|
} else {
|
||
|
// Make sure to add the point pair in a predictable order so that any
|
||
|
// combination of Vec2I points will be unique in m_liquidInteractions
|
||
|
if (selfCell->position < targetCell->position)
|
||
|
m_liquidInteractions.add(make_tuple(selfCell->position, *selfCell->liquid, targetCell->position, *targetCell->liquid));
|
||
|
else
|
||
|
m_liquidInteractions.add(make_tuple(targetCell->position, *targetCell->liquid, selfCell->position, *selfCell->liquid));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::finish() {
|
||
|
m_currentActiveCells.clear();
|
||
|
|
||
|
for (auto& workingCellPair : take(m_workingCells)) {
|
||
|
if (workingCellPair.second && !workingCellPair.second->sourceCell) {
|
||
|
if (workingCellPair.second->liquid) {
|
||
|
if (workingCellPair.second->level < m_engineParameters.minimumLiquidLevel)
|
||
|
workingCellPair.second->level = 0.0f;
|
||
|
} else {
|
||
|
workingCellPair.second->level = 0.0f;
|
||
|
}
|
||
|
|
||
|
if (workingCellPair.second->level == 0.0f) {
|
||
|
workingCellPair.second->liquid = {};
|
||
|
workingCellPair.second->pressure = 0.0f;
|
||
|
}
|
||
|
|
||
|
m_cellWorld->setFlow(workingCellPair.second->position, CellularLiquidFlowCell<LiquidId>{
|
||
|
workingCellPair.second->liquid, workingCellPair.second->level, workingCellPair.second->pressure});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (auto const& interaction : take(m_liquidInteractions))
|
||
|
m_cellWorld->liquidInteraction(get<0>(interaction), get<1>(interaction), get<2>(interaction), get<3>(interaction));
|
||
|
|
||
|
for (auto const& interaction : take(m_liquidCollisions))
|
||
|
m_cellWorld->liquidCollision(get<0>(interaction), get<1>(interaction), get<2>(interaction));
|
||
|
|
||
|
for (auto const& c : take(m_nextActiveCells)) {
|
||
|
auto visit = [this](Vec2I p) {
|
||
|
p = m_cellWorld->uniqueLocation(p);
|
||
|
auto cell = workingCell(p);
|
||
|
if (cell && cell->liquid)
|
||
|
m_activeCells[*cell->liquid].add(p);
|
||
|
};
|
||
|
|
||
|
visit(c);
|
||
|
visit(c + Vec2I(-1, 0));
|
||
|
visit(c + Vec2I(1, 0));
|
||
|
visit(c + Vec2I(0, -1));
|
||
|
visit(c + Vec2I(0, 1));
|
||
|
}
|
||
|
|
||
|
eraseWhere(m_activeCells, [](auto const& p) {
|
||
|
return p.second.empty();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
typename LiquidCellEngine<LiquidId>::WorkingCell* LiquidCellEngine<LiquidId>::workingCell(Vec2I p) {
|
||
|
p = m_cellWorld->uniqueLocation(p);
|
||
|
|
||
|
auto res = m_workingCells.insert(make_pair(p, Maybe<WorkingCell>()));
|
||
|
if (res.second) {
|
||
|
auto cellData = m_cellWorld->cell(p);
|
||
|
if (auto flowCell = cellData.template ptr<CellularLiquidFlowCell<LiquidId>>())
|
||
|
res.first->second = WorkingCell{p, flowCell->liquid, false, flowCell->level, flowCell->pressure, nullptr, nullptr, nullptr, nullptr};
|
||
|
else if (auto sourceCell = cellData.template ptr<CellularLiquidSourceCell<LiquidId>>())
|
||
|
res.first->second = WorkingCell{p, sourceCell->liquid, true, 1.0f, sourceCell->pressure, nullptr, nullptr, nullptr, nullptr};
|
||
|
}
|
||
|
return res.first->second.ptr();
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
typename LiquidCellEngine<LiquidId>::WorkingCell* LiquidCellEngine<LiquidId>::adjacentCell(
|
||
|
WorkingCell* cell, Adjacency adjacency) {
|
||
|
auto getCell = [this](WorkingCell*& cellptr, Vec2I cellPos) {
|
||
|
if (cellptr)
|
||
|
return cellptr;
|
||
|
cellptr = workingCell(cellPos);
|
||
|
return cellptr;
|
||
|
};
|
||
|
|
||
|
if (adjacency == Adjacency::Left)
|
||
|
return getCell(cell->leftCell, cell->position + Vec2I(-1, 0));
|
||
|
else if (adjacency == Adjacency::Right)
|
||
|
return getCell(cell->rightCell, cell->position + Vec2I(1, 0));
|
||
|
else if (adjacency == Adjacency::Bottom)
|
||
|
return getCell(cell->bottomCell, cell->position + Vec2I(0, -1));
|
||
|
else if (adjacency == Adjacency::Top)
|
||
|
return getCell(cell->topCell, cell->position + Vec2I(0, 1));
|
||
|
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setPressure(float pressure, WorkingCell& cell) {
|
||
|
if (!cell.liquid || cell.sourceCell)
|
||
|
return;
|
||
|
|
||
|
if (fabs(cell.pressure - pressure) > m_engineParameters.minimumLivenPressureChange)
|
||
|
m_nextActiveCells.add(cell.position);
|
||
|
cell.pressure = pressure;
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::transferPressure(float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse) {
|
||
|
if (amount < 0.0f && allowReverse) {
|
||
|
return transferPressure(-amount, dest, source, false);
|
||
|
} else if (amount > 0.0f) {
|
||
|
if (!source.liquid)
|
||
|
return;
|
||
|
|
||
|
if (source.sourceCell && dest.sourceCell)
|
||
|
return;
|
||
|
|
||
|
if (dest.liquid && dest.liquid != source.liquid)
|
||
|
return;
|
||
|
|
||
|
amount = min(amount, source.pressure);
|
||
|
|
||
|
if (!source.sourceCell)
|
||
|
source.pressure -= amount;
|
||
|
|
||
|
if (dest.liquid && !dest.sourceCell)
|
||
|
dest.pressure += amount;
|
||
|
|
||
|
if (amount > m_engineParameters.minimumLivenPressureChange) {
|
||
|
m_nextActiveCells.add(source.position);
|
||
|
m_nextActiveCells.add(dest.position);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::setLevel(float level, WorkingCell& cell) {
|
||
|
if (!cell.liquid || cell.sourceCell)
|
||
|
return;
|
||
|
|
||
|
if (fabs(cell.level - level) > m_engineParameters.minimumLivenLevelChange)
|
||
|
m_nextActiveCells.add(cell.position);
|
||
|
|
||
|
cell.level = level;
|
||
|
|
||
|
if (cell.level <= 0.0f) {
|
||
|
cell.liquid = {};
|
||
|
cell.level = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename LiquidId>
|
||
|
void LiquidCellEngine<LiquidId>::transferLevel(
|
||
|
float amount, WorkingCell& source, WorkingCell& dest, bool allowReverse) {
|
||
|
if (amount < 0.0f && allowReverse) {
|
||
|
transferLevel(-amount, dest, source, false);
|
||
|
|
||
|
} else if (amount > 0.0f) {
|
||
|
if (!source.liquid)
|
||
|
return;
|
||
|
|
||
|
if (source.sourceCell && dest.sourceCell)
|
||
|
return;
|
||
|
|
||
|
if (dest.liquid && dest.liquid != source.liquid)
|
||
|
return;
|
||
|
|
||
|
amount = min(amount, source.level);
|
||
|
if (!source.sourceCell)
|
||
|
source.level -= amount;
|
||
|
|
||
|
if (!dest.sourceCell) {
|
||
|
dest.level += amount;
|
||
|
dest.liquid = source.liquid;
|
||
|
}
|
||
|
|
||
|
if (!source.sourceCell && source.level == 0.0f)
|
||
|
source.liquid = {};
|
||
|
|
||
|
if (amount > m_engineParameters.minimumLivenLevelChange) {
|
||
|
m_nextActiveCells.add(source.position);
|
||
|
m_nextActiveCells.add(dest.position);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|