sandpypi/Sand.Core/SandSimulation.Movement.cs

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);
}
}