sandpypi/Sand.Core/SandSimulation.Step.cs

300 lines
11 KiB
C#

namespace Sand.Core;
public sealed partial class SandSimulation
{
public void Step(float dt)
{
if (_settings.PauseSim)
{
return;
}
if (_settings.EnableAcceleration && Accelerator is not null)
{
Accelerator.Step(this, dt);
return;
}
int preStepStartX;
int preStepStartY;
int preStepEndX;
int preStepEndY;
var hadPreStepBounds = AreFieldVisualsEnabled()
? TryGetSimulationBounds(out preStepStartX, out preStepStartY, out preStepEndX, out preStepEndY)
: TryGetOccupiedBoundsOnly(out preStepStartX, out preStepStartY, out preStepEndX, out preStepEndY);
_deferVisualDirtyTracking = true;
DecayFields(dt);
if (ParticleCount == 0)
{
_deferVisualDirtyTracking = false;
MarkBoundsUnionDirty(hadPreStepBounds, preStepStartX, preStepStartY, preStepEndX, preStepEndY);
Frame++;
FrameStats.Frame = Frame;
FrameStats.ProcessedCells = 0;
FrameStats.ParticleCount = 0;
FrameStats.MinActiveX = 0;
FrameStats.MinActiveY = 0;
FrameStats.MaxActiveX = 0;
FrameStats.MaxActiveY = 0;
FrameStats.LoadedChunkCount = 0;
FrameStats.ActiveChunkCount = 0;
FrameStats.DirtyChunkCount = 0;
FrameStats.SteppedChunkCount = 0;
FrameStats.SleepingChunkCount = 0;
FrameStats.FieldPageCount = 0;
FrameStats.MoveAttemptCount = 0;
FrameStats.VerticalMoveAttemptCount = 0;
FrameStats.DiagonalMoveAttemptCount = 0;
FrameStats.LateralMoveAttemptCount = 0;
FrameStats.SuccessfulMoveCount = 0;
FrameStats.SwapAttemptCount = 0;
FrameStats.StalledMovableCount = 0;
FrameStats.MovementOnlyFastPathCount = 0;
FrameStats.FullRuntimeStepCount = 0;
FrameStats.FullRuntimeSolidCount = 0;
FrameStats.FullRuntimeLiquidCount = 0;
FrameStats.FullRuntimeGasCount = 0;
FrameStats.MovedParticleCount = 0;
FrameStats.SwappedParticleCount = 0;
FrameStats.VisualDirtyPageCount = 0;
FrameStats.FrameBuildBytesTouched = 0;
FrameStats.ActivationTimeMicroseconds = 0;
FrameStats.MovementTimeMicroseconds = 0;
FrameStats.RuntimeTimeMicroseconds = 0;
FrameStats.FieldDecayTimeMicroseconds = 0;
FrameStats.RenderTimeMicroseconds = 0;
return;
}
EnsureOccupiedBounds();
var margin = _settings.WrapParticles ? 0 : 1;
var startX = _settings.WrapParticles ? 0 : Math.Max(0, _minOccupiedX - margin);
var endX = _settings.WrapParticles ? Width - 1 : Math.Min(Width - 1, _maxOccupiedX + margin);
var startY = _settings.WrapParticles ? 0 : Math.Max(0, _minOccupiedY - margin);
var endY = _settings.WrapParticles ? Height - 1 : Math.Min(Height - 1, _maxOccupiedY + margin);
_activeStepToken = Frame + 1;
var processedCells = 0;
for (var y = endY; y >= startY; y--)
{
for (var x = startX; x <= endX; x++)
{
if (_processedFrame[x, y] == _activeStepToken)
{
continue;
}
var typeId = TypeId[x, y];
if (typeId == 0)
{
continue;
}
var seed = Hash(x, y, Frame);
var leftFirst = (seed & 1u) == 0;
MarkProcessed(x, y);
processedCells++;
if (TickLifetime(x, y, dt))
{
continue;
}
typeId = TypeId[x, y];
if (TickSpark(x, y, typeId, ref seed))
{
continue;
}
typeId = TypeId[x, y];
AutoIgnite(x, y, typeId);
ApplyRuntimeEmissionAndProduction(x, y, typeId, ref seed);
if (TickBurning(x, y, typeId, ref seed))
{
continue;
}
typeId = TypeId[x, y];
if (TickSpecialBehavior(x, y, typeId, ref seed))
{
continue;
}
typeId = TypeId[x, y];
ApplyPhaseTransitions(x, y, ref typeId);
if (typeId == 0)
{
continue;
}
if (ApplyLocalReactions(x, y, ref typeId, ref seed))
{
continue;
}
typeId = TypeId[x, y];
if (TickPressure(x, y, typeId, ref seed))
{
continue;
}
ApplyTemperatureDiffusion(x, y, typeId);
ApplyMovement(x, y, typeId, leftFirst, ref seed);
}
}
if (_settings.OuterWall && _idWall != 0)
{
ApplyBoundaryWallsFast();
}
_deferVisualDirtyTracking = false;
MarkBoundsUnionDirty(hadPreStepBounds, preStepStartX, preStepStartY, preStepEndX, preStepEndY);
Frame++;
_activeStepToken = 0;
_staleOccupiedBoundsFrames = _boundsDirty ? _staleOccupiedBoundsFrames + 1 : 0;
FrameStats.Frame = Frame;
FrameStats.ProcessedCells = processedCells;
FrameStats.ParticleCount = ParticleCount;
FrameStats.MinActiveX = _hasOccupiedBounds ? _minOccupiedX : 0;
FrameStats.MinActiveY = _hasOccupiedBounds ? _minOccupiedY : 0;
FrameStats.MaxActiveX = _hasOccupiedBounds ? _maxOccupiedX : 0;
FrameStats.MaxActiveY = _hasOccupiedBounds ? _maxOccupiedY : 0;
FrameStats.LoadedChunkCount = 0;
FrameStats.ActiveChunkCount = 0;
FrameStats.DirtyChunkCount = 0;
FrameStats.SteppedChunkCount = 0;
FrameStats.SleepingChunkCount = 0;
FrameStats.FieldPageCount = 0;
FrameStats.MoveAttemptCount = 0;
FrameStats.VerticalMoveAttemptCount = 0;
FrameStats.DiagonalMoveAttemptCount = 0;
FrameStats.LateralMoveAttemptCount = 0;
FrameStats.SuccessfulMoveCount = 0;
FrameStats.SwapAttemptCount = 0;
FrameStats.StalledMovableCount = 0;
FrameStats.MovementOnlyFastPathCount = 0;
FrameStats.FullRuntimeStepCount = 0;
FrameStats.FullRuntimeSolidCount = 0;
FrameStats.FullRuntimeLiquidCount = 0;
FrameStats.FullRuntimeGasCount = 0;
FrameStats.MovedParticleCount = 0;
FrameStats.SwappedParticleCount = 0;
FrameStats.VisualDirtyPageCount = 0;
FrameStats.FrameBuildBytesTouched = 0;
FrameStats.ActivationTimeMicroseconds = 0;
FrameStats.MovementTimeMicroseconds = 0;
FrameStats.RuntimeTimeMicroseconds = 0;
FrameStats.FieldDecayTimeMicroseconds = 0;
FrameStats.RenderTimeMicroseconds = 0;
}
public ReadOnlySpan<byte> BuildRgbFrame()
{
UpdateCachedVisualBuffers();
return _rgbBuffer;
}
public ReadOnlySpan<byte> BuildRgbaFrame()
{
UpdateCachedVisualBuffers();
FrameStats.FrameBuildBytesTouched = (long)_rgbaBuffer.Length;
FrameStats.RenderTimeMicroseconds = 0;
return _rgbaBuffer;
}
public void BuildRgbaFrame(Span<byte> destination)
{
var expectedLength = Width * Height * 4;
if (destination.Length < expectedLength)
{
throw new ArgumentException($"RGBA destination must be at least {expectedLength} bytes.", nameof(destination));
}
UpdateCachedVisualBuffers();
_rgbaBuffer.AsSpan().CopyTo(destination);
FrameStats.FrameBuildBytesTouched = (long)_rgbaBuffer.Length;
FrameStats.RenderTimeMicroseconds = 0;
}
private Rgb24 ResolveVisualColor(int x, int y)
{
var typeId = TypeId[x, y];
if (typeId == 0)
{
if (_settings.EnablePressureVisuals)
{
var pressure = MathF.Abs(_airPressure[x, y]);
if (pressure > 0.08f)
{
var tint = (byte)Math.Clamp((int)(pressure * 32f), 10, 140);
if (_airPressure[x, y] >= 0f)
{
return new Rgb24((byte)(26 + tint), (byte)(18 + tint / 3), 24);
}
return new Rgb24(18, (byte)(24 + tint / 4), (byte)(30 + tint));
}
}
if (_settings.EnableWindVisuals)
{
var totalFieldX = _windFieldX[x, y] + _forceFieldX[x, y];
var totalFieldY = _windFieldY[x, y] + _forceFieldY[x, y];
var windStrength = MathF.Sqrt((totalFieldX * totalFieldX) + (totalFieldY * totalFieldY));
if (windStrength > 0.08f)
{
var tint = (byte)Math.Clamp((int)(windStrength * 40f), 10, 120);
return new Rgb24((byte)(16 + tint / 3), (byte)(24 + tint / 2), (byte)(32 + tint));
}
}
return new Rgb24(0, 0, 0);
}
var color = SparkTime[x, y] > 0 ? new Rgb24(255, 255, 150) : _colorLut[typeId];
if (_settings.EnableTempVisuals)
{
var delta = Temperature[x, y] - _settings.AmbientTemperature;
if (delta > 5f)
{
var heat = (byte)Math.Clamp((int)(delta * 4f), 0, 120);
color = new Rgb24(
(byte)Math.Min(255, color.R + heat),
(byte)Math.Max(0, color.G - heat / 3),
(byte)Math.Max(0, color.B - heat / 2));
}
else if (delta < -5f)
{
var cool = (byte)Math.Clamp((int)(Math.Abs(delta) * 4f), 0, 120);
color = new Rgb24(
(byte)Math.Max(0, color.R - cool / 2),
(byte)Math.Max(0, color.G - cool / 3),
(byte)Math.Min(255, color.B + cool));
}
}
if (_settings.EnableGasEffect && _kind[typeId] == (byte)ParticleKind.Gas)
{
var bias = (byte)(Hash(x, y, Frame) & 0x0F);
color = new Rgb24(
(byte)Math.Min(255, color.R + bias),
(byte)Math.Min(255, color.G + bias),
(byte)Math.Min(255, color.B + bias));
}
if (_settings.EnableGlow && (Burning[x, y] != 0 || typeId == _idFire || typeId == _idLava || SparkTime[x, y] > 0))
{
color = new Rgb24(
(byte)Math.Min(255, color.R + 25),
(byte)Math.Min(255, color.G + 18),
(byte)Math.Min(255, color.B + 12));
}
return color;
}
}