921 lines
33 KiB
C#
921 lines
33 KiB
C#
using FluentAssertions;
|
|
using Sand.Core;
|
|
|
|
namespace Sand.Tests;
|
|
|
|
public sealed class SandSimulationTests
|
|
{
|
|
private readonly ParticleLibrary _library = ParticleLibraryLoader.LoadFromDirectory(Path.Combine(AppContext.BaseDirectory, "Content", "part"));
|
|
|
|
[Fact]
|
|
public void SandFallsIntoEmptySpace()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(2, 2, "sand");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(2, 3).Should().Be(_library.GetTypeId("sand"));
|
|
}
|
|
|
|
[Fact]
|
|
public void WaterAndSandCreateWetSand()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(3, 3, "sand");
|
|
simulation.CreateParticleAtPixel(2, 3, "water");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(3, 3).Should().Be(_library.GetTypeId("wsand"));
|
|
}
|
|
|
|
[Fact]
|
|
public void LoaderParsesExtendedMetadataFields()
|
|
{
|
|
var burningWood = _library.GetDefinition(_library.GetTypeId("burning_wood"));
|
|
var energy = _library.GetDefinition(_library.GetTypeId("energy"));
|
|
var wind = _library.GetDefinition(_library.GetTypeId("wind"));
|
|
var stone = _library.GetDefinition(_library.GetTypeId("stone"));
|
|
var ultratanium = _library.GetDefinition(_library.GetTypeId("ultratanium"));
|
|
|
|
burningWood.Produces.Should().Be("smoke");
|
|
burningWood.HeatEmission.Should().Be(50f);
|
|
energy.EnergyTransfer.Should().BeGreaterThan(0f);
|
|
wind.Radius.Should().BeGreaterThan(0f);
|
|
wind.Affects.Should().Contain("steam");
|
|
stone.Hardness.Should().Be(0.7f);
|
|
stone.Durability.Should().Be(100f);
|
|
stone.Broken.Should().Be("brkstone");
|
|
ultratanium.Explosive.Should().BeTrue();
|
|
ultratanium.ExplosionRadius.Should().Be(15);
|
|
}
|
|
|
|
[Fact]
|
|
public void LavaAndWaterCreateStoneAndSteam()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(2, 2, "lava");
|
|
simulation.CreateParticleAtPixel(3, 2, "water");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(2, 2).Should().Be(_library.GetTypeId("stone"));
|
|
simulation.GetTypeIdAtCell(3, 2).Should().Be(_library.GetTypeId("steam"));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRgbFrameReflectsParticleColor()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
var sand = _library.GetDefinition(_library.GetTypeId("sand"));
|
|
simulation.CreateParticleAtPixel(0, 0, "sand");
|
|
|
|
var frame = simulation.BuildRgbFrame().ToArray();
|
|
|
|
frame[0].Should().Be(sand.Color.R);
|
|
frame[1].Should().Be(sand.Color.G);
|
|
frame[2].Should().Be(sand.Color.B);
|
|
}
|
|
|
|
[Fact]
|
|
public void SparkSpreadsToDiagonalConductors()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 4, "iron");
|
|
simulation.CreateParticleAtPixel(5, 5, "iron");
|
|
simulation.CreateParticleAtPixel(4, 4, "spark");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.SparkTime[5, 5].Should().BeGreaterThan((short)0);
|
|
}
|
|
|
|
[Fact]
|
|
public void OuterWallPreventsPaintingBoundaryCells()
|
|
{
|
|
var simulation = new SandSimulation(10, 10, 1, _library, new SimulationSettings { PauseSim = true, OuterWall = true });
|
|
simulation.RefreshSettingsState();
|
|
|
|
simulation.CreateParticleAtPixel(0, 0, "sand");
|
|
|
|
simulation.GetTypeIdAtCell(0, 0).Should().Be(_library.GetTypeId("wall"));
|
|
}
|
|
|
|
[Fact]
|
|
public void TogglingOuterWallOffRemovesBoundaryWallsFromParticleCount()
|
|
{
|
|
var settings = new SimulationSettings { PauseSim = true, OuterWall = true };
|
|
var simulation = new SandSimulation(10, 10, 1, _library, settings);
|
|
simulation.RefreshSettingsState();
|
|
simulation.ParticleCount.Should().BeGreaterThan(0);
|
|
|
|
settings.OuterWall = false;
|
|
simulation.RefreshSettingsState();
|
|
|
|
simulation.ParticleCount.Should().Be(0);
|
|
simulation.GetTypeIdAtCell(0, 0).Should().Be(0);
|
|
simulation.GetTypeIdAtCell(9, 9).Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void WrapParticlesMovesAcrossEdge()
|
|
{
|
|
var simulation = new SandSimulation(5, 5, 1, _library, new SimulationSettings { PauseSim = false, WrapParticles = true });
|
|
simulation.CreateParticleAtPixel(4, 0, "steam");
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.ParticleCount.Should().Be(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParticleCountRemainsStableAcrossMovementSteps()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(6, 2, "sand");
|
|
|
|
for (var i = 0; i < 6; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
simulation.ParticleCount.Should().Be(1);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SymmetricSandPileDoesNotDevelopStrongRightBias()
|
|
{
|
|
var simulation = new SandSimulation(60, 40, 1, _library, new SimulationSettings { PauseSim = false });
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
simulation.CreateParticleAtPixel(x, simulation.Height - 1, "wall");
|
|
}
|
|
|
|
for (var x = 24; x <= 36; x++)
|
|
{
|
|
for (var y = 4; y <= 16; y++)
|
|
{
|
|
simulation.CreateParticleAtPixel(x, y, "sand");
|
|
}
|
|
}
|
|
|
|
var startCenter = FindAverageX(simulation, "sand");
|
|
for (var i = 0; i < 120; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
var finalCenter = FindAverageX(simulation, "sand");
|
|
Math.Abs(finalCenter - startCenter).Should().BeLessThanOrEqualTo(1.5);
|
|
}
|
|
|
|
[Fact]
|
|
public void NeutralFireCanMoveLeftWhenOnlyLeftEscapePathIsAvailable()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(5, 5, "fire");
|
|
simulation.CreateParticleAtPixel(5, 4, "wall");
|
|
simulation.CreateParticleAtPixel(6, 4, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
var fireId = _library.GetTypeId("fire");
|
|
(simulation.GetTypeIdAtCell(4, 4) == fireId || simulation.GetTypeIdAtCell(4, 5) == fireId).Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void LazyOccupiedBoundsShrinkEventuallyAfterEdgeRemoval()
|
|
{
|
|
var simulation = new SandSimulation(30, 30, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(2, 2, "wall");
|
|
simulation.CreateParticleAtPixel(20, 20, "wall");
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.ClearParticleCircle(2, 2, 0);
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.FrameStats.ParticleCount.Should().Be(1);
|
|
simulation.FrameStats.MinActiveX.Should().BeLessThanOrEqualTo(20);
|
|
simulation.FrameStats.MaxActiveX.Should().BeGreaterThanOrEqualTo(20);
|
|
simulation.FrameStats.MaxActiveX.Should().BeLessThanOrEqualTo(21);
|
|
simulation.FrameStats.MinActiveY.Should().BeLessThanOrEqualTo(20);
|
|
simulation.FrameStats.MaxActiveY.Should().BeGreaterThanOrEqualTo(20);
|
|
simulation.FrameStats.MaxActiveY.Should().BeLessThanOrEqualTo(21);
|
|
}
|
|
|
|
[Fact]
|
|
public void EmptySimulationClearsActiveBoundsStats()
|
|
{
|
|
var simulation = new SandSimulation(20, 20, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(10, 10, "sand");
|
|
|
|
simulation.Step(1f / 60f);
|
|
simulation.Clear();
|
|
simulation.Settings.PauseSim = false;
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.FrameStats.ProcessedCells.Should().Be(0);
|
|
simulation.FrameStats.ParticleCount.Should().Be(0);
|
|
simulation.FrameStats.MinActiveX.Should().Be(0);
|
|
simulation.FrameStats.MinActiveY.Should().Be(0);
|
|
simulation.FrameStats.MaxActiveX.Should().Be(0);
|
|
simulation.FrameStats.MaxActiveY.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRgbaFrameWritesExpectedLength()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(0, 0, "sand");
|
|
var buffer = new byte[10 * 10 * 4];
|
|
|
|
simulation.BuildRgbaFrame(buffer);
|
|
|
|
buffer.Length.Should().Be(400);
|
|
buffer[3].Should().Be(255);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRgbaFrameUpdatesReusedBufferAfterCellChanges()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
var buffer = new byte[10 * 10 * 4];
|
|
var sand = _library.GetDefinition(_library.GetTypeId("sand"));
|
|
|
|
simulation.CreateParticleAtPixel(2, 2, "sand");
|
|
simulation.BuildRgbaFrame(buffer);
|
|
|
|
var sandIndex = ((2 * simulation.Width) + 2) * 4;
|
|
buffer[sandIndex].Should().Be(sand.Color.R);
|
|
|
|
simulation.ClearParticleCircle(2, 2, 0);
|
|
simulation.BuildRgbaFrame(buffer);
|
|
|
|
buffer[sandIndex].Should().Be(0);
|
|
buffer[sandIndex + 1].Should().Be(0);
|
|
buffer[sandIndex + 2].Should().Be(0);
|
|
buffer[sandIndex + 3].Should().Be(255);
|
|
}
|
|
|
|
[Fact]
|
|
public void WindBrushWritesLocalWindField()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
|
|
var wind = simulation.GetWindAtCell(4, 4);
|
|
wind.X.Should().BeGreaterThan(0f);
|
|
MathF.Abs(wind.Y).Should().BeLessThan(0.2f);
|
|
}
|
|
|
|
[Fact]
|
|
public void AirBrushWritesPressureField()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
|
|
simulation.ApplyAirBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
|
|
simulation.GetAirPressureAtCell(4, 4).Should().BeGreaterThan(0f);
|
|
}
|
|
|
|
[Fact]
|
|
public void FieldDecayContinuesWithoutParticles()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.Settings.PauseSim = false;
|
|
simulation.ApplyAirBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
var initialPressure = simulation.GetAirPressureAtCell(4, 4);
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetAirPressureAtCell(4, 4).Should().BeLessThan(initialPressure);
|
|
simulation.ParticleCount.Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void FreshlySpawnedFallingSandDoesNotImmediatelyCreateVisiblePressure()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 1, "sand");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
simulation.Step(1f / 60f);
|
|
|
|
MathF.Abs(simulation.GetAirPressureAtCell(4, 3)).Should().BeLessThan(0.08f);
|
|
MathF.Abs(simulation.GetAirPressureAtCell(3, 3)).Should().BeLessThan(0.08f);
|
|
MathF.Abs(simulation.GetAirPressureAtCell(5, 3)).Should().BeLessThan(0.08f);
|
|
}
|
|
|
|
[Fact]
|
|
public void GasRespondsMoreThanLiquidToSamePressureField()
|
|
{
|
|
var steamSimulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false });
|
|
var waterSimulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false });
|
|
|
|
steamSimulation.CreateParticleAtPixel(6, 6, "steam");
|
|
waterSimulation.CreateParticleAtPixel(6, 6, "water");
|
|
|
|
for (var i = 0; i < 6; i++)
|
|
{
|
|
steamSimulation.ApplyAirBrushAtPixel(3, 6, 3, 1f, 0f);
|
|
waterSimulation.ApplyAirBrushAtPixel(3, 6, 3, 1f, 0f);
|
|
steamSimulation.Step(1f / 60f);
|
|
waterSimulation.Step(1f / 60f);
|
|
}
|
|
|
|
Math.Abs(FindParticleX(steamSimulation, "steam") - 6).Should().BeGreaterThan(0);
|
|
Math.Abs(FindParticleX(waterSimulation, "water") - 6).Should().BeLessThan(Math.Abs(FindParticleX(steamSimulation, "steam") - 6));
|
|
}
|
|
|
|
[Fact]
|
|
public void NewlySpawnedParticleClearsLocalForceAndPressureFields()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
simulation.ApplyAirBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
simulation.GetAirPressureAtCell(4, 4).Should().BeGreaterThan(0f);
|
|
simulation.GetWindAtCell(4, 4).X.Should().BeGreaterThan(0f);
|
|
|
|
simulation.CreateParticleAtPixel(4, 4, "sand");
|
|
|
|
simulation.GetAirPressureAtCell(4, 4).Should().Be(0f);
|
|
simulation.GetWindAtCell(4, 4).X.Should().Be(0f);
|
|
simulation.GetWindAtCell(4, 4).Y.Should().Be(0f);
|
|
simulation.GetForceAtCell(4, 4).X.Should().Be(0f);
|
|
simulation.GetForceAtCell(4, 4).Y.Should().Be(0f);
|
|
}
|
|
|
|
[Fact]
|
|
public void PourPaintingAddsLimitedParticlesInsteadOfFillingEntireBrush()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
|
|
simulation.CreateParticlePourAtPixel(8, 8, 3, "sand", 4, 1234);
|
|
|
|
simulation.ParticleCount.Should().Be(4);
|
|
}
|
|
|
|
[Fact]
|
|
public void FireTouchingWaterIsExtinguished()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(2, 2, "fire");
|
|
simulation.CreateParticleAtPixel(3, 2, "water");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(2, 2).Should().Be(0);
|
|
var neighbor = simulation.GetTypeIdAtCell(3, 2);
|
|
neighbor.Should().BeOneOf((ushort)0, _library.GetTypeId("water"), _library.GetTypeId("steam"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FireRisesInsteadOfBehavingLikeSolid()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(5, 5, "fire");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(5, 4).Should().Be(_library.GetTypeId("fire"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FireEventuallyProducesSmoke()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(5, 7, "fire");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
for (var i = 0; i < 12; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
CountParticles(simulation, "smoke").Should().BeGreaterThan(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void SandAndWaterDoNotSpontaneouslyGenerateSmokeOrEmbers()
|
|
{
|
|
var simulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(6, 6, "sand");
|
|
simulation.CreateParticleAtPixel(9, 6, "water");
|
|
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
CountParticles(simulation, "smoke").Should().Be(0);
|
|
CountParticles(simulation, "ember").Should().Be(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void BurningWoodStaysMostlyInPlaceAndProducesEffectParticles()
|
|
{
|
|
var simulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(8, 8, "burning_wood");
|
|
|
|
for (var i = 0; i < 24; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(8, 8).Should().Be(_library.GetTypeId("burning_wood"));
|
|
(CountParticles(simulation, "smoke") + CountParticles(simulation, "ember")).Should().BeGreaterThan(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void PlasmaTransfersEnergyToConductiveNeighbors()
|
|
{
|
|
var simulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(8, 8, "plasma");
|
|
simulation.CreateParticleAtPixel(8, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 8, "iron");
|
|
simulation.CreateParticleAtPixel(9, 8, "iron");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
simulation.CreateParticleAtPixel(9, 7, "wall");
|
|
simulation.CreateParticleAtPixel(8, 9, "wall");
|
|
|
|
for (var i = 0; i < 12; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
(simulation.SparkTime[7, 8] != 0 || simulation.SparkTime[9, 8] != 0).Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void SteamDoesNotImmediatelyCondenseAfterCreation()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(6, 6, "steam");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.CreateParticleAtPixel(7, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(6, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
|
|
for (var i = 0; i < 6; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
CountParticles(simulation, "steam").Should().BeGreaterThan(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void CooledSteamCondensesBackIntoWaterInsteadOfDisappearing()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(6, 6, "steam");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.CreateParticleAtPixel(7, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(6, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
|
|
for (var i = 0; i < 80; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(6, 6).Should().Be(_library.GetTypeId("water"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SteamRisesFasterThanSmokeBecauseOfVelocity()
|
|
{
|
|
var steamSimulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false });
|
|
var smokeSimulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false });
|
|
steamSimulation.CreateParticleAtPixel(6, 9, "steam");
|
|
smokeSimulation.CreateParticleAtPixel(6, 9, "smoke");
|
|
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
steamSimulation.Step(1f / 60f);
|
|
smokeSimulation.Step(1f / 60f);
|
|
}
|
|
|
|
FindParticleY(steamSimulation, "steam").Should().BeLessThan(FindParticleY(smokeSimulation, "smoke"));
|
|
}
|
|
|
|
[Fact]
|
|
public void LavaDoesNotCoolImmediatelyAtAmbient()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(6, 6, "lava");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.CreateParticleAtPixel(7, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(6, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.Temperature[6, 6] = 820f;
|
|
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(6, 6).Should().Be(_library.GetTypeId("lava"));
|
|
}
|
|
|
|
[Fact]
|
|
public void LavaDoesNotSelfDestructFromHeatAlone()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(6, 6, "lava");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.CreateParticleAtPixel(7, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(6, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
|
|
for (var i = 0; i < 45; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(6, 6).Should().Be(_library.GetTypeId("lava"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MoltenGoldDoesNotSpreadFasterThanWaterInRuntimeTuning()
|
|
{
|
|
var waterSimulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
var moltenGoldSimulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
PrepareFloor(waterSimulation, 6);
|
|
PrepareFloor(moltenGoldSimulation, 6);
|
|
waterSimulation.CreateParticleAtPixel(8, 5, "water");
|
|
moltenGoldSimulation.CreateParticleAtPixel(8, 5, "molten_gold");
|
|
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
waterSimulation.Step(1f / 60f);
|
|
moltenGoldSimulation.Step(1f / 60f);
|
|
}
|
|
|
|
var waterX = FindParticleX(waterSimulation, "water");
|
|
var moltenGoldX = FindParticleX(moltenGoldSimulation, "molten_gold");
|
|
Math.Abs(moltenGoldX - 8).Should().BeLessThanOrEqualTo(Math.Abs(waterX - 8));
|
|
}
|
|
|
|
[Fact]
|
|
public void LavaRespondsLessThanWaterToLateralForce()
|
|
{
|
|
var waterSimulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false });
|
|
var lavaSimulation = new SandSimulation(16, 16, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 900f });
|
|
PrepareFloor(waterSimulation, 6);
|
|
PrepareFloor(lavaSimulation, 6);
|
|
waterSimulation.CreateParticleAtPixel(8, 5, "water");
|
|
lavaSimulation.CreateParticleAtPixel(8, 5, "lava");
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
waterSimulation.ApplyWindBrushAtPixel(8, 5, 3, 1f, 0f);
|
|
lavaSimulation.ApplyWindBrushAtPixel(8, 5, 3, 1f, 0f);
|
|
waterSimulation.Step(1f / 60f);
|
|
lavaSimulation.Step(1f / 60f);
|
|
}
|
|
|
|
var waterX = FindParticleX(waterSimulation, "water");
|
|
var lavaX = FindParticleX(lavaSimulation, "lava");
|
|
Math.Abs(lavaX - 8).Should().BeLessThan(Math.Abs(waterX - 8));
|
|
}
|
|
|
|
[Fact]
|
|
public void MoltenGoldDoesNotSelfDestructFromHeatAlone()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(6, 6, "molten_gold");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.CreateParticleAtPixel(7, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(6, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
|
|
for (var i = 0; i < 35; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(6, 6).Should().Be(_library.GetTypeId("molten_gold"));
|
|
}
|
|
|
|
[Fact]
|
|
public void IceAndSnowDoNotImmediatelyMeltAtThresholdTemperatures()
|
|
{
|
|
var simulation = new SandSimulation(12, 12, 1, _library, new SimulationSettings { PauseSim = false, AmbientTemperature = 22f });
|
|
simulation.CreateParticleAtPixel(4, 6, "ice");
|
|
simulation.CreateParticleAtPixel(8, 6, "snow");
|
|
simulation.CreateParticleAtPixel(3, 6, "wall");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(3, 7, "wall");
|
|
simulation.CreateParticleAtPixel(4, 7, "wall");
|
|
simulation.CreateParticleAtPixel(5, 7, "wall");
|
|
simulation.CreateParticleAtPixel(7, 6, "wall");
|
|
simulation.CreateParticleAtPixel(9, 6, "wall");
|
|
simulation.CreateParticleAtPixel(7, 7, "wall");
|
|
simulation.CreateParticleAtPixel(8, 7, "wall");
|
|
simulation.CreateParticleAtPixel(9, 7, "wall");
|
|
simulation.Temperature[4, 6] = 8f;
|
|
simulation.Temperature[8, 6] = 10f;
|
|
|
|
for (var i = 0; i < 6; i++)
|
|
{
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(4, 6).Should().Be(_library.GetTypeId("ice"));
|
|
simulation.GetTypeIdAtCell(8, 6).Should().Be(_library.GetTypeId("snow"));
|
|
}
|
|
|
|
[Fact]
|
|
public void ExplosiveParticleDetonatesUnderSustainedPressure()
|
|
{
|
|
var simulation = new SandSimulation(32, 32, 1, _library, new SimulationSettings { PauseSim = false });
|
|
simulation.CreateParticleAtPixel(16, 16, "ultratanium");
|
|
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
simulation.ApplyAirBrushAtPixel(16, 16, 6, 1f, 0f);
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.GetTypeIdAtCell(16, 16).Should().Be(0);
|
|
(CountParticles(simulation, "smoke") + CountParticles(simulation, "fire")).Should().BeGreaterThanOrEqualTo(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void AdjacentExplosivesDoNotRecursivelyOverflowWhenDetonated()
|
|
{
|
|
var simulation = new SandSimulation(32, 32, 1, _library, new SimulationSettings { PauseSim = false });
|
|
for (var x = 12; x <= 18; x++)
|
|
{
|
|
for (var y = 12; y <= 18; y++)
|
|
{
|
|
simulation.CreateParticleAtPixel(x, y, "ultratanium");
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
simulation.ApplyAirBrushAtPixel(15, 15, 8, 1f, 0f);
|
|
simulation.Step(1f / 60f);
|
|
}
|
|
|
|
simulation.ParticleCount.Should().BeGreaterThanOrEqualTo(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void SustainedPressureBreaksStoneIntoBrokenStone()
|
|
{
|
|
var simulation = new SandSimulation(20, 20, 1, _library, new SimulationSettings { PauseSim = false });
|
|
var brokenStone = _library.GetTypeId("brkstone");
|
|
simulation.CreateParticleAtPixel(10, 10, "stone");
|
|
simulation.CreateParticleAtPixel(9, 9, "wall");
|
|
simulation.CreateParticleAtPixel(10, 9, "wall");
|
|
simulation.CreateParticleAtPixel(11, 9, "wall");
|
|
simulation.CreateParticleAtPixel(9, 10, "wall");
|
|
simulation.CreateParticleAtPixel(11, 10, "wall");
|
|
simulation.CreateParticleAtPixel(9, 11, "wall");
|
|
simulation.CreateParticleAtPixel(10, 11, "wall");
|
|
simulation.CreateParticleAtPixel(11, 11, "wall");
|
|
|
|
var broke = false;
|
|
for (var i = 0; i < 28; i++)
|
|
{
|
|
simulation.ApplyAirBrushAtPixel(10, 10, 6, 1f, 0f);
|
|
simulation.Step(1f / 60f);
|
|
if (simulation.GetTypeIdAtCell(10, 10) == brokenStone)
|
|
{
|
|
broke = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
broke.Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void WindBiasPushesGasSidewaysWhenUpwardPathIsBlocked()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 4, "steam");
|
|
simulation.CreateParticleAtPixel(4, 3, "wall");
|
|
simulation.CreateParticleAtPixel(3, 3, "wall");
|
|
simulation.CreateParticleAtPixel(5, 3, "wall");
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 2, 1f, 0f);
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(5, 4).Should().Be(_library.GetTypeId("steam"));
|
|
}
|
|
|
|
[Fact]
|
|
public void WindToolDoesNotPushWaterWhenMetadataExcludesIt()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 4, "water");
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 3, 1f, 0f);
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 3, 1f, 0f);
|
|
simulation.ApplyWindBrushAtPixel(4, 4, 3, 1f, 0f);
|
|
|
|
var wind = simulation.GetWindAtCell(4, 4);
|
|
MathF.Abs(wind.X).Should().BeLessThan(0.01f);
|
|
MathF.Abs(wind.Y).Should().BeLessThan(0.01f);
|
|
}
|
|
|
|
[Fact]
|
|
public void GravityWellPullsGasSidewaysWhenUpwardPathIsBlocked()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(5, 4, "steam");
|
|
simulation.CreateParticleAtPixel(5, 3, "wall");
|
|
simulation.CreateParticleAtPixel(4, 3, "wall");
|
|
simulation.CreateParticleAtPixel(6, 3, "wall");
|
|
simulation.ApplyGravityBrushAtPixel(3, 4, 3, 6f);
|
|
simulation.ApplyGravityBrushAtPixel(3, 4, 3, 6f);
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(4, 4).Should().Be(_library.GetTypeId("steam"));
|
|
}
|
|
|
|
[Fact]
|
|
public void RepulsorPushesLiquidSidewaysWhenUpwardPathIsBlocked()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 4, "water");
|
|
simulation.CreateParticleAtPixel(4, 5, "wall");
|
|
simulation.CreateParticleAtPixel(3, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 5, "wall");
|
|
simulation.ApplyRepulsorBrushAtPixel(3, 4, 3, 6f);
|
|
simulation.ApplyRepulsorBrushAtPixel(3, 4, 3, 6f);
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(5, 4).Should().Be(_library.GetTypeId("water"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SolidDoesNotDiagonalSwapIntoLiquid()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(4, 4, "sand");
|
|
simulation.CreateParticleAtPixel(4, 5, "wall");
|
|
simulation.CreateParticleAtPixel(3, 5, "wall");
|
|
simulation.CreateParticleAtPixel(5, 5, "water");
|
|
simulation.CreateParticleAtPixel(5, 6, "wall");
|
|
simulation.CreateParticleAtPixel(4, 6, "wall");
|
|
simulation.CreateParticleAtPixel(6, 6, "wall");
|
|
simulation.CreateParticleAtPixel(6, 5, "wall");
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(4, 4).Should().Be(_library.GetTypeId("sand"));
|
|
simulation.GetTypeIdAtCell(5, 5).Should().Be(_library.GetTypeId("water"));
|
|
}
|
|
|
|
[Fact]
|
|
public void MoltenLiquidCanSinkThroughLighterLiquidBelow()
|
|
{
|
|
var simulation = CreateSimulation();
|
|
simulation.CreateParticleAtPixel(3, 3, "molten_gold");
|
|
simulation.CreateParticleAtPixel(3, 4, "water");
|
|
simulation.CreateParticleAtPixel(2, 3, "wall");
|
|
simulation.CreateParticleAtPixel(4, 3, "wall");
|
|
simulation.CreateParticleAtPixel(2, 4, "wall");
|
|
simulation.CreateParticleAtPixel(4, 4, "wall");
|
|
simulation.CreateParticleAtPixel(3, 5, "wall");
|
|
simulation.CreateParticleAtPixel(2, 5, "wall");
|
|
simulation.CreateParticleAtPixel(4, 5, "wall");
|
|
simulation.Temperature[3, 3] = 2000f;
|
|
simulation.Settings.PauseSim = false;
|
|
|
|
simulation.Step(1f / 60f);
|
|
|
|
simulation.GetTypeIdAtCell(3, 4).Should().Be(_library.GetTypeId("molten_gold"));
|
|
simulation.GetTypeIdAtCell(3, 3).Should().Be(_library.GetTypeId("water"));
|
|
}
|
|
|
|
private int CountParticles(SandSimulation simulation, string particleId)
|
|
{
|
|
var typeId = _library.GetTypeId(particleId);
|
|
var count = 0;
|
|
for (var y = 0; y < simulation.Height; y++)
|
|
{
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
if (simulation.GetTypeIdAtCell(x, y) == typeId)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private int FindParticleX(SandSimulation simulation, string particleId)
|
|
{
|
|
var typeId = _library.GetTypeId(particleId);
|
|
for (var y = 0; y < simulation.Height; y++)
|
|
{
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
if (simulation.GetTypeIdAtCell(x, y) == typeId)
|
|
{
|
|
return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private int FindParticleY(SandSimulation simulation, string particleId)
|
|
{
|
|
var typeId = _library.GetTypeId(particleId);
|
|
for (var y = 0; y < simulation.Height; y++)
|
|
{
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
if (simulation.GetTypeIdAtCell(x, y) == typeId)
|
|
{
|
|
return y;
|
|
}
|
|
}
|
|
}
|
|
|
|
return int.MaxValue;
|
|
}
|
|
|
|
private double FindAverageX(SandSimulation simulation, string particleId)
|
|
{
|
|
var typeId = _library.GetTypeId(particleId);
|
|
double total = 0;
|
|
var count = 0;
|
|
for (var y = 0; y < simulation.Height; y++)
|
|
{
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
if (simulation.GetTypeIdAtCell(x, y) == typeId)
|
|
{
|
|
total += x;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count == 0 ? -1 : total / count;
|
|
}
|
|
|
|
private void PrepareFloor(SandSimulation simulation, int y)
|
|
{
|
|
for (var x = 0; x < simulation.Width; x++)
|
|
{
|
|
simulation.CreateParticleAtPixel(x, y, "wall");
|
|
}
|
|
}
|
|
|
|
private SandSimulation CreateSimulation() => new(10, 10, 1, _library, new SimulationSettings { PauseSim = true });
|
|
}
|