494 lines
14 KiB
C#
494 lines
14 KiB
C#
namespace Sand.Core;
|
|
|
|
public sealed partial class SandSimulation
|
|
{
|
|
private void ApplyMovement(int x, int y, ushort typeId, bool leftFirst, ref uint seed)
|
|
{
|
|
if (_isStatic[typeId] != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var netForceX = 0f;
|
|
var netForceY = 0f;
|
|
if (_hasActiveFieldBounds || _settings.WindX != 0f || _settings.WindY != 0f)
|
|
{
|
|
var forceResponse = GetForceResponse(typeId);
|
|
netForceX = GetNetForceX(x, y, typeId) * forceResponse;
|
|
netForceY = GetNetForceY(x, y, typeId) * forceResponse;
|
|
}
|
|
|
|
if (netForceX > 0.25f)
|
|
{
|
|
leftFirst = false;
|
|
}
|
|
else if (netForceX < -0.25f)
|
|
{
|
|
leftFirst = true;
|
|
}
|
|
|
|
switch ((ParticleBehaviorKind)_behaviorKind[typeId])
|
|
{
|
|
case ParticleBehaviorKind.Fire:
|
|
MoveFire(x, y, leftFirst, ref seed, netForceX);
|
|
return;
|
|
case ParticleBehaviorKind.BurningWood:
|
|
return;
|
|
case ParticleBehaviorKind.Ember:
|
|
MoveEmber(x, y, leftFirst, ref seed, netForceX);
|
|
return;
|
|
case ParticleBehaviorKind.Plasma:
|
|
MovePlasma(x, y, leftFirst, ref seed, netForceX, netForceY);
|
|
return;
|
|
}
|
|
|
|
switch ((ParticleKind)_kind[typeId])
|
|
{
|
|
case ParticleKind.Solid:
|
|
MoveSolid(x, y, typeId, leftFirst, ref seed, netForceX);
|
|
break;
|
|
case ParticleKind.Liquid:
|
|
MoveLiquid(x, y, typeId, leftFirst, ref seed, netForceX);
|
|
break;
|
|
case ParticleKind.Gas:
|
|
MoveGas(x, y, typeId, leftFirst, ref seed, netForceX, netForceY);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void MoveSolid(int x, int y, ushort typeId, bool leftFirst, ref uint seed, float netForceX)
|
|
{
|
|
if (TryMoveSolidDown(x, y, typeId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!CanSlipSolid(typeId, ref seed, netForceX))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (leftFirst)
|
|
{
|
|
if (TryMoveEmpty(x, y, x - 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TryMoveEmpty(x, y, x + 1, y + 1);
|
|
return;
|
|
}
|
|
|
|
if (TryMoveEmpty(x, y, x + 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
TryMoveEmpty(x, y, x - 1, y + 1);
|
|
}
|
|
|
|
private void MoveLiquid(int x, int y, ushort typeId, bool leftFirst, ref uint seed, float netForceX)
|
|
{
|
|
if (TryMoveLiquidDown(x, y, typeId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var windPreferred = netForceX > 0.2f ? 1 : netForceX < -0.2f ? -1 : 0;
|
|
if (windPreferred != 0 && CanFlowLiquid(typeId, ref seed, lateral: true, netForceX))
|
|
{
|
|
if (TryMoveEmpty(x, y, x + windPreferred, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
var diagonalAllowed = CanFlowLiquid(typeId, ref seed, lateral: false, netForceX);
|
|
var lateralAllowed = diagonalAllowed || CanFlowLiquid(typeId, ref seed, lateral: true, netForceX);
|
|
|
|
if (leftFirst)
|
|
{
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x - 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x + 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x + 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x - 1, y + 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void MoveGas(int x, int y, ushort typeId, bool leftFirst, ref uint seed, float netForceX, float netForceY)
|
|
{
|
|
if (CanRiseGas(typeId, ref seed, netForceY) && TryMoveEmpty(x, y, x, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var windPreferred = netForceX > 0.15f ? 1 : netForceX < -0.15f ? -1 : 0;
|
|
if (windPreferred != 0 && CanDriftGas(typeId, ref seed, netForceX, lateral: true) && TryMoveEmpty(x, y, x + windPreferred, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var diagonalAllowed = CanDriftGas(typeId, ref seed, netForceX, lateral: false);
|
|
var lateralAllowed = diagonalAllowed || CanDriftGas(typeId, ref seed, netForceX, lateral: true);
|
|
|
|
if (leftFirst)
|
|
{
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x - 1, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x + 1, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x + 1, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (diagonalAllowed && TryMoveEmpty(x, y, x - 1, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (lateralAllowed && TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
private bool TryMoveInto(int x, int y, int nx, int ny, ushort typeId, bool swapAllowed)
|
|
{
|
|
if (!TryNormalizeCoordinate(ref nx, ref ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_settings.OuterWall && typeId != _idWall && IsBoundary(nx, ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var neighborId = TypeId[nx, ny];
|
|
if (neighborId == 0)
|
|
{
|
|
MoveCell(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
if (!swapAllowed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var neighborKind = (ParticleKind)_kind[neighborId];
|
|
var movableNeighbor = neighborKind == ParticleKind.Liquid || neighborKind == ParticleKind.Gas;
|
|
if (movableNeighbor && _mass[typeId] > _mass[neighborId])
|
|
{
|
|
SwapCells(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool TryMoveEmpty(int x, int y, int nx, int ny)
|
|
{
|
|
if (!TryNormalizeCoordinate(ref nx, ref ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_settings.OuterWall && IsBoundary(nx, ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (TypeId[nx, ny] != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MoveCell(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
private bool TryMoveSolidDown(int x, int y, ushort typeId)
|
|
{
|
|
var nx = x;
|
|
var ny = y + 1;
|
|
if (!TryNormalizeCoordinate(ref nx, ref ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_settings.OuterWall && IsBoundary(nx, ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var below = TypeId[nx, ny];
|
|
if (below == 0)
|
|
{
|
|
MoveCell(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
var belowKind = (ParticleKind)_kind[below];
|
|
if ((belowKind == ParticleKind.Liquid || belowKind == ParticleKind.Gas) && _mass[typeId] > _mass[below])
|
|
{
|
|
SwapCells(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool TryMoveLiquidDown(int x, int y, ushort typeId)
|
|
{
|
|
var nx = x;
|
|
var ny = y + 1;
|
|
if (!TryNormalizeCoordinate(ref nx, ref ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (_settings.OuterWall && IsBoundary(nx, ny))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var below = TypeId[nx, ny];
|
|
if (below == 0)
|
|
{
|
|
MoveCell(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
var belowKind = (ParticleKind)_kind[below];
|
|
if (belowKind == ParticleKind.Gas && _mass[typeId] > _mass[below])
|
|
{
|
|
SwapCells(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
if (_isMolten[typeId] != 0 && belowKind == ParticleKind.Liquid && _mass[typeId] > _mass[below])
|
|
{
|
|
SwapCells(x, y, nx, ny);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void MoveFire(int x, int y, bool leftFirst, ref uint seed, float netForceX)
|
|
{
|
|
if (TryMoveEmpty(x, y, x, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var preferredRight = netForceX > 0.05f ? true : netForceX < -0.05f ? false : !leftFirst;
|
|
if (preferredRight)
|
|
{
|
|
if (TryMoveEmpty(x, y, x + 1, y - 1) || TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TryMoveEmpty(x, y, x - 1, y - 1) || TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TryMoveEmpty(x, y, x - 1, y - 1) || TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (TryMoveEmpty(x, y, x + 1, y - 1) || TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (NextChance(ref seed, 0.15f))
|
|
{
|
|
ResetCell(x, y);
|
|
}
|
|
}
|
|
|
|
private void MoveEmber(int x, int y, bool leftFirst, ref uint seed, float netForceX)
|
|
{
|
|
if (NextChance(ref seed, MathF.Max(0.1f, _upwardBias[TypeId[x, y]])))
|
|
{
|
|
if (TryMoveEmpty(x, y, x, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (leftFirst)
|
|
{
|
|
if (TryMoveEmpty(x, y, x - 1, y - 1) || TryMoveEmpty(x, y, x - 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (TryMoveEmpty(x, y, x + 1, y - 1) || TryMoveEmpty(x, y, x + 1, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
MoveSolid(x, y, TypeId[x, y], leftFirst, ref seed, netForceX);
|
|
}
|
|
|
|
private void MovePlasma(int x, int y, bool leftFirst, ref uint seed, float netForceX, float netForceY)
|
|
{
|
|
if (TryMoveEmpty(x, y, x, y - 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var lateralBias = MathF.Max(0.15f, _sideDriftBias[TypeId[x, y]]);
|
|
if (NextChance(ref seed, lateralBias))
|
|
{
|
|
var dir = netForceX > 0.05f ? 1 : netForceX < -0.05f ? -1 : leftFirst ? -1 : 1;
|
|
if (TryMoveEmpty(x, y, x + dir, y - 1) || TryMoveEmpty(x, y, x + dir, y))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
MoveGas(x, y, TypeId[x, y], leftFirst, ref seed, netForceX, netForceY);
|
|
}
|
|
|
|
private float GetEffectiveVelocity(ushort typeId)
|
|
{
|
|
if (_velocity[typeId] > 0f)
|
|
{
|
|
return _velocity[typeId];
|
|
}
|
|
|
|
return (ParticleKind)_kind[typeId] switch
|
|
{
|
|
ParticleKind.Solid => 0.45f,
|
|
ParticleKind.Liquid => 0.35f,
|
|
ParticleKind.Gas => 0.25f,
|
|
_ => 0.35f,
|
|
};
|
|
}
|
|
|
|
private float GetForceResponse(ushort typeId)
|
|
{
|
|
var mobility = 0.35f + GetEffectiveVelocity(typeId);
|
|
var damping = MathF.Max(0.25f, (_mass[typeId] * 0.85f) + (_friction[typeId] * 0.35f) + (_viscosity[typeId] * 0.45f));
|
|
return Clamp((mobility / damping) * _forceResponseMultiplier[typeId], 0.15f, 2f);
|
|
}
|
|
|
|
private bool CanSlipSolid(ushort typeId, ref uint seed, float netForceX)
|
|
{
|
|
if (MathF.Abs(netForceX) > 0.45f)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var chance = Clamp(0.2f + (GetEffectiveVelocity(typeId) * 0.95f) - (_friction[typeId] * 0.35f), 0.1f, 0.95f);
|
|
return NextChance(ref seed, chance);
|
|
}
|
|
|
|
private bool CanFlowLiquid(ushort typeId, ref uint seed, bool lateral, float netForceX)
|
|
{
|
|
var forceBonus = MathF.Min(0.25f, MathF.Abs(netForceX) * 0.08f);
|
|
var velocity = GetEffectiveVelocity(typeId);
|
|
var baseChance = lateral
|
|
? 0.18f + (velocity * 0.95f) - (_viscosity[typeId] * 0.42f) - (_friction[typeId] * 0.08f)
|
|
: 0.28f + (velocity * 0.9f) - (_viscosity[typeId] * 0.28f) - (_friction[typeId] * 0.05f);
|
|
var flowScale = lateral ? _lateralFlowMultiplier[typeId] : _diagonalFlowMultiplier[typeId];
|
|
var chance = Clamp((baseChance * flowScale) + forceBonus, 0.03f, 0.98f);
|
|
return NextChance(ref seed, chance);
|
|
}
|
|
|
|
private bool CanRiseGas(ushort typeId, ref uint seed, float netForceY)
|
|
{
|
|
if (netForceY < -0.65f)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var velocity = GetEffectiveVelocity(typeId);
|
|
var buoyancy = MathF.Max(0f, -netForceY) * 0.08f;
|
|
var chance = Clamp(0.1f + (velocity * 1.8f) - (_viscosity[typeId] * 0.22f) + buoyancy, 0.05f, 0.99f);
|
|
return NextChance(ref seed, chance);
|
|
}
|
|
|
|
private bool CanDriftGas(ushort typeId, ref uint seed, float netForceX, bool lateral)
|
|
{
|
|
var forceBonus = MathF.Min(0.28f, MathF.Abs(netForceX) * 0.08f);
|
|
var velocity = GetEffectiveVelocity(typeId);
|
|
var baseChance = lateral
|
|
? 0.18f + (velocity * 1.15f) - (_viscosity[typeId] * 0.1f)
|
|
: 0.22f + (velocity * 1.2f) - (_viscosity[typeId] * 0.16f);
|
|
var chance = Clamp(baseChance + forceBonus - (_friction[typeId] * 0.04f), 0.05f, 0.99f);
|
|
return NextChance(ref seed, chance);
|
|
}
|
|
}
|