sandpypi/Sand.Core/SandSimulation.cs

348 lines
17 KiB
C#

namespace Sand.Core;
public sealed partial class SandSimulation
{
private readonly ParticleLibrary _library;
private readonly SimulationSettings _settings;
private readonly byte[] _rgbBuffer;
private readonly byte[] _rgbaBuffer;
private readonly byte[] _kind;
private readonly byte[] _isStatic;
private readonly float[] _mass;
private readonly float[] _hardness;
private readonly byte[] _isMolten;
private readonly float[] _velocity;
private readonly float[] _conductivity;
private readonly byte[] _conductive;
private readonly float[] _flamability;
private readonly float[] _durability;
private readonly float[] _heatCapacity;
private readonly float[] _friction;
private readonly float[] _viscosity;
private readonly float[] _pressure;
private readonly byte[] _burningInit;
private readonly float[] _burnDuration;
private readonly float[] _burnTemperature;
private readonly float[] _burnRate;
private readonly float[] _initialTemperature;
private readonly ushort[] _evapTarget;
private readonly float[] _evapTemperature;
private readonly ushort[] _meltTarget;
private readonly float[] _meltTemperature;
private readonly ushort[] _solidifyTarget;
private readonly float[] _solidifyTemperature;
private readonly ushort[] _freezeTarget;
private readonly float[] _freezeTemperature;
private readonly float[] _defaultLifetime;
private readonly ushort[] _producesTarget;
private readonly ushort[] _producesOnDeathTarget;
private readonly float[] _heatEmission;
private readonly float[] _energyTransfer;
private readonly float[] _radius;
private readonly float[] _forceFalloff;
private readonly float[] _turbulence;
private readonly float[] _pressureResistance;
private readonly float[] _pressureTolerance;
private readonly float[] _pressureThreshold;
private readonly short[] _pressureThresholdDuration;
private readonly ushort[] _brokenTarget;
private readonly byte[] _explosive;
private readonly short[] _explosionRadius;
private readonly float[] _explosionForce;
private readonly Rgb24?[] _explosionColor;
private readonly byte[] _behaviorKind;
private readonly float[] _lifetimeMultiplier;
private readonly float[] _burnDecayPerStep;
private readonly float[] _ambientCoolingMultiplier;
private readonly float[] _neighborHeatTransferMultiplier;
private readonly float[] _upwardBias;
private readonly float[] _sideDriftBias;
private readonly float[] _fireSpreadChance;
private readonly float[] _smokeSpawnChance;
private readonly float[] _emberSpawnChance;
private readonly float[] _produceChance;
private readonly float[] _heatEmissionMultiplier;
private readonly float[] _energyTransferMultiplier;
private readonly float[] _pressureSensitivity;
private readonly float[] _pressureDecayMultiplier;
private readonly float[] _forceResponseMultiplier;
private readonly float[] _lateralFlowMultiplier;
private readonly float[] _diagonalFlowMultiplier;
private readonly float[] _phaseTransitionHysteresis;
private readonly float[] _minLifetimeTicks;
private readonly float[] _maxLifetimeTicks;
private readonly Rgb24[] _colorLut;
private readonly float[,] _windFieldX;
private readonly float[,] _windFieldY;
private readonly float[,] _forceFieldX;
private readonly float[,] _forceFieldY;
private readonly float[,] _airPressure;
private readonly float[,] _pressureDuration;
private readonly float[,] _cellAge;
private readonly float[,] _integrity;
private readonly ToolProfile _windTool;
private readonly ToolProfile _gravityTool;
private readonly ToolProfile _repulsorTool;
private readonly ToolProfile _airTool;
private readonly ushort _idFire;
private readonly ushort _idLava;
private readonly ushort _idWater;
private readonly ushort _idSand;
private readonly ushort _idWetSand;
private readonly ushort _idDirt;
private readonly ushort _idMud;
private readonly ushort _idStone;
private readonly ushort _idSteam;
private readonly ushort _idAcid;
private readonly ushort _idSpark;
private readonly ushort _idEnergy;
private readonly ushort _idWall;
private readonly ushort _idSmoke;
private readonly ushort _idGlass;
private readonly ushort _idBurningWood;
private readonly ushort _idEmber;
private readonly ushort _idPlasma;
private readonly ushort _idSnow;
private readonly ushort _idIce;
private readonly int[,] _processedFrame;
private readonly int[,] _explosionFrame;
private int _activeStepToken;
private bool _boundsDirty;
private int _staleOccupiedBoundsFrames;
private bool _hasOccupiedBounds;
private int _minOccupiedX;
private int _minOccupiedY;
private int _maxOccupiedX;
private int _maxOccupiedY;
private bool _fieldBoundsDirty;
private bool _hasActiveFieldBounds;
private int _minActiveFieldX;
private int _minActiveFieldY;
private int _maxActiveFieldX;
private int _maxActiveFieldY;
private bool _hasDirtyVisualBounds;
private bool _fullVisualDirty = true;
private int _minDirtyVisualX;
private int _minDirtyVisualY;
private int _maxDirtyVisualX;
private int _maxDirtyVisualY;
private bool _deferVisualDirtyTracking;
private uint _visualSettingsStamp = uint.MaxValue;
public SandSimulation(int width, int height, int particleSize, IParticleLibrary library, SimulationSettings? settings = null)
{
Width = width;
Height = height;
ParticleSize = particleSize;
_library = library as ParticleLibrary ?? throw new ArgumentException("Expected ParticleLibrary implementation.", nameof(library));
_settings = settings ?? new SimulationSettings();
TypeId = new ushort[width, height];
Temperature = new float[width, height];
BurnTime = new float[width, height];
Burning = new byte[width, height];
SparkTime = new short[width, height];
Lifetime = new float[width, height];
_pressureDuration = new float[width, height];
_cellAge = new float[width, height];
_windFieldX = new float[width, height];
_windFieldY = new float[width, height];
_forceFieldX = new float[width, height];
_forceFieldY = new float[width, height];
_airPressure = new float[width, height];
_processedFrame = new int[width, height];
_explosionFrame = new int[width, height];
_integrity = new float[width, height];
_rgbBuffer = new byte[width * height * 3];
_rgbaBuffer = new byte[width * height * 4];
FrameStats = new SimulationFrameStats();
var count = _library.Definitions.Count + 1;
_kind = new byte[count];
_isStatic = new byte[count];
_mass = new float[count];
_hardness = new float[count];
_isMolten = new byte[count];
_velocity = new float[count];
_conductivity = new float[count];
_conductive = new byte[count];
_flamability = new float[count];
_durability = new float[count];
_heatCapacity = new float[count];
_friction = new float[count];
_viscosity = new float[count];
_pressure = new float[count];
_burningInit = new byte[count];
_burnDuration = new float[count];
_burnTemperature = new float[count];
_burnRate = new float[count];
_initialTemperature = new float[count];
_evapTarget = new ushort[count];
_evapTemperature = Enumerable.Repeat(9999f, count).ToArray();
_meltTarget = new ushort[count];
_meltTemperature = Enumerable.Repeat(9999f, count).ToArray();
_solidifyTarget = new ushort[count];
_solidifyTemperature = Enumerable.Repeat(-9999f, count).ToArray();
_freezeTarget = new ushort[count];
_freezeTemperature = Enumerable.Repeat(-9999f, count).ToArray();
_defaultLifetime = new float[count];
_producesTarget = new ushort[count];
_producesOnDeathTarget = new ushort[count];
_heatEmission = new float[count];
_energyTransfer = new float[count];
_radius = new float[count];
_forceFalloff = new float[count];
_turbulence = new float[count];
_pressureResistance = new float[count];
_pressureTolerance = new float[count];
_pressureThreshold = new float[count];
_pressureThresholdDuration = new short[count];
_brokenTarget = new ushort[count];
_explosive = new byte[count];
_explosionRadius = new short[count];
_explosionForce = new float[count];
_explosionColor = new Rgb24?[count];
_behaviorKind = new byte[count];
_lifetimeMultiplier = Enumerable.Repeat(1f, count).ToArray();
_burnDecayPerStep = Enumerable.Repeat(1f, count).ToArray();
_ambientCoolingMultiplier = Enumerable.Repeat(1f, count).ToArray();
_neighborHeatTransferMultiplier = Enumerable.Repeat(1f, count).ToArray();
_upwardBias = new float[count];
_sideDriftBias = new float[count];
_fireSpreadChance = new float[count];
_smokeSpawnChance = new float[count];
_emberSpawnChance = new float[count];
_produceChance = new float[count];
_heatEmissionMultiplier = Enumerable.Repeat(1f, count).ToArray();
_energyTransferMultiplier = Enumerable.Repeat(1f, count).ToArray();
_pressureSensitivity = Enumerable.Repeat(1f, count).ToArray();
_pressureDecayMultiplier = Enumerable.Repeat(1f, count).ToArray();
_forceResponseMultiplier = Enumerable.Repeat(1f, count).ToArray();
_lateralFlowMultiplier = Enumerable.Repeat(1f, count).ToArray();
_diagonalFlowMultiplier = Enumerable.Repeat(1f, count).ToArray();
_phaseTransitionHysteresis = new float[count];
_minLifetimeTicks = new float[count];
_maxLifetimeTicks = new float[count];
_colorLut = new Rgb24[count];
for (ushort typeId = 1; typeId < count; typeId++)
{
var definition = _library.GetDefinition(typeId);
var runtime = ParticleRuntimeProfileBuilder.Build(definition);
_kind[typeId] = (byte)definition.Kind;
_isStatic[typeId] = definition.IsStatic ? (byte)1 : (byte)0;
_mass[typeId] = definition.Mass;
_hardness[typeId] = MathF.Max(0f, definition.Hardness);
_isMolten[typeId] = definition.Id == "lava" || definition.Id.StartsWith("molten_", StringComparison.Ordinal) ? (byte)1 : (byte)0;
_velocity[typeId] = definition.Velocity;
_conductivity[typeId] = definition.Conductivity;
_conductive[typeId] = definition.Conductive ? (byte)1 : (byte)0;
_flamability[typeId] = definition.Flamability;
_durability[typeId] = MathF.Max(1f, definition.Durability);
_heatCapacity[typeId] = MathF.Max(0.1f, definition.HeatCapacity);
_friction[typeId] = definition.Friction;
_viscosity[typeId] = definition.Viscosity;
_pressure[typeId] = definition.Pressure;
_burningInit[typeId] = definition.Burning ? (byte)1 : (byte)0;
_burnDuration[typeId] = definition.BurnDuration;
_burnTemperature[typeId] = definition.BurnTemperature;
_burnRate[typeId] = MathF.Max(0.05f, definition.BurnRate);
_initialTemperature[typeId] = definition.Temperature;
_evapTarget[typeId] = ResolveOptionalTypeId(definition.Evaporate);
_meltTarget[typeId] = ResolveOptionalTypeId(definition.Melt);
_solidifyTarget[typeId] = ResolveOptionalTypeId(definition.Solidify);
_freezeTarget[typeId] = ResolveOptionalTypeId(definition.Freeze);
_producesTarget[typeId] = ResolveOptionalTypeId(definition.Produces);
_producesOnDeathTarget[typeId] = ResolveOptionalTypeId(definition.ProducesOnDeath);
_heatEmission[typeId] = definition.HeatEmission;
_energyTransfer[typeId] = definition.EnergyTransfer;
_radius[typeId] = definition.Radius;
_forceFalloff[typeId] = definition.ForceFalloff <= 0f ? 1f : definition.ForceFalloff;
_turbulence[typeId] = definition.Turbulence;
_pressureResistance[typeId] = definition.PressureResistance;
_pressureTolerance[typeId] = definition.PressureTolerance;
_pressureThreshold[typeId] = definition.PressureThreshold;
_pressureThresholdDuration[typeId] = checked((short)Math.Clamp(definition.PressureThresholdDuration, 0, short.MaxValue));
_brokenTarget[typeId] = ResolveOptionalTypeId(definition.Broken);
_explosive[typeId] = definition.Explosive ? (byte)1 : (byte)0;
_explosionRadius[typeId] = checked((short)Math.Clamp(definition.ExplosionRadius, 0, short.MaxValue));
_explosionForce[typeId] = definition.ExplosionForce;
_explosionColor[typeId] = definition.ExplosionColor;
if (definition.EvaporateTemperature is not null) _evapTemperature[typeId] = definition.EvaporateTemperature.Value;
if (definition.MeltTemperature is not null) _meltTemperature[typeId] = definition.MeltTemperature.Value;
if (definition.SolidifyTemperature is not null) _solidifyTemperature[typeId] = definition.SolidifyTemperature.Value;
if (definition.FreezeTemperature is not null) _freezeTemperature[typeId] = definition.FreezeTemperature.Value;
_behaviorKind[typeId] = (byte)runtime.Balance.BehaviorKind;
_lifetimeMultiplier[typeId] = runtime.Balance.LifetimeMultiplier;
_burnDecayPerStep[typeId] = runtime.Balance.BurnDecayPerStep;
_ambientCoolingMultiplier[typeId] = runtime.Balance.AmbientCoolingMultiplier;
_neighborHeatTransferMultiplier[typeId] = runtime.Balance.NeighborHeatTransferMultiplier;
_upwardBias[typeId] = runtime.Balance.UpwardBias;
_sideDriftBias[typeId] = runtime.Balance.SideDriftBias;
_fireSpreadChance[typeId] = runtime.Balance.FireSpreadChance;
_smokeSpawnChance[typeId] = runtime.Balance.SmokeSpawnChance;
_emberSpawnChance[typeId] = runtime.Balance.EmberSpawnChance;
_produceChance[typeId] = runtime.Balance.ProduceChance;
_heatEmissionMultiplier[typeId] = runtime.Balance.HeatEmissionMultiplier;
_energyTransferMultiplier[typeId] = runtime.Balance.EnergyTransferMultiplier;
_pressureSensitivity[typeId] = runtime.Balance.PressureSensitivity;
_pressureDecayMultiplier[typeId] = runtime.Balance.PressureDecayMultiplier;
_forceResponseMultiplier[typeId] = runtime.Balance.ForceResponseMultiplier;
_lateralFlowMultiplier[typeId] = runtime.Balance.LateralFlowMultiplier;
_diagonalFlowMultiplier[typeId] = runtime.Balance.DiagonalFlowMultiplier;
_phaseTransitionHysteresis[typeId] = runtime.Balance.PhaseTransitionHysteresis;
_minLifetimeTicks[typeId] = runtime.Balance.MinLifetimeTicks;
_maxLifetimeTicks[typeId] = runtime.Balance.MaxLifetimeTicks;
_defaultLifetime[typeId] = ResolveDefaultLifetime(definition, runtime.Balance);
_colorLut[typeId] = definition.Color;
}
_idFire = ResolveOptionalTypeId("fire");
_idLava = ResolveOptionalTypeId("lava");
_idWater = ResolveOptionalTypeId("water");
_idSand = ResolveOptionalTypeId("sand");
_idWetSand = ResolveOptionalTypeId("wsand");
_idDirt = ResolveOptionalTypeId("dirt");
_idMud = ResolveOptionalTypeId("mud");
_idStone = ResolveOptionalTypeId("stone");
_idSteam = ResolveOptionalTypeId("steam");
_idAcid = ResolveOptionalTypeId("acid");
_idSpark = ResolveOptionalTypeId("spark");
_idEnergy = ResolveOptionalTypeId("energy");
_idWall = ResolveOptionalTypeId("wall");
_idSmoke = ResolveOptionalTypeId("smoke");
_idGlass = ResolveOptionalTypeId("glass");
_idBurningWood = ResolveOptionalTypeId("burning_wood");
_idEmber = ResolveOptionalTypeId("ember");
_idPlasma = ResolveOptionalTypeId("plasma");
_idSnow = ResolveOptionalTypeId("snow");
_idIce = ResolveOptionalTypeId("ice");
if (_idBurningWood != 0 && _producesOnDeathTarget[_idBurningWood] == 0 && _idEmber != 0)
{
_producesOnDeathTarget[_idBurningWood] = _idEmber;
}
_windTool = BuildToolProfile("wind", defaultStrength: 4f, fallbackRadiusCells: 4);
_gravityTool = BuildToolProfile("gravity_well", defaultStrength: 3.5f, fallbackRadiusCells: 6);
_repulsorTool = BuildToolProfile("repulsor", defaultStrength: 3f, fallbackRadiusCells: 5);
_airTool = new ToolProfile("air", 4, 3f, 1f, 0.1f, ["all"]);
Clear();
}
public int Width { get; }
public int Height { get; }
public int ParticleSize { get; }
public int Frame { get; private set; }
public int ParticleCount { get; private set; }
public ushort[,] TypeId { get; }
public float[,] Temperature { get; }
public float[,] BurnTime { get; }
public byte[,] Burning { get; }
public short[,] SparkTime { get; }
public float[,] Lifetime { get; }
public SimulationSettings Settings => _settings;
public SimulationFrameStats FrameStats { get; }
public ISimulationAccelerator? Accelerator { get; set; }
public float GetPressureDurationAtCell(int x, int y) => _pressureDuration[x, y];
}