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