300 lines
11 KiB
C#
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;
|
|
}
|
|
|
|
}
|