diff --git a/half-earth/scripts/HeatSystem.cs b/half-earth/scripts/HeatSystem.cs new file mode 100644 index 0000000..ebeb79d --- /dev/null +++ b/half-earth/scripts/HeatSystem.cs @@ -0,0 +1,100 @@ +using Godot; + +public class HeatSystem +{ + private readonly TileGrid _tiles; + private readonly float _diffusionCoefficient; + + private float[] _nextTemperatures = null; + + public HeatSystem(TileGrid tiles, float diffusionCoefficient) + { + _tiles = tiles; + _diffusionCoefficient = diffusionCoefficient; + _nextTemperatures = new float[tiles.Count]; + } + + public void Process() + { + GenerateHeat(); + Diffuse(); + } + + + private float TransferHeat(float t0, float alpha, float nx, float ny, float px, float py) + { + float d2tdx2 = nx - 2 * t0 + px; + float d2tdy2 = ny - 2 * t0 + py; + return t0 + alpha * (d2tdx2 + d2tdy2); + } + + private void GetNeighbourTemperatures(int x, int y, out float nx, out float ny, out float px, out float py) + { + // default value + var t = _tiles[x, y].temperature; + + nx = x > 0 ? _tiles[x - 1, y].temperature : t; + px = x < _tiles.Size - 1 ? _tiles[x + 1, y].temperature : t; + + ny = y > 0 ? _tiles[x, y - 1].temperature : t; + py = y < _tiles.Size - 1 ? _tiles[x, y + 1].temperature : t; + } + private void GenerateHeat() + { + for (int i = 0; i < _tiles.Count; i++) + { + var tile = _tiles[i]; + var type = tile.type; + tile.temperature += type.HeatGeneration; + ApplyHeatDamage(ref tile); + _tiles[i] = tile; + } + } + + private void ApplyHeatDamage(ref Tile tile) + { + if (!(tile.type is IDamageable damageable)) + return; + + var surplus = tile.temperature - damageable.Threshold; + if (surplus < 0) + return; + + // TODO: parameterised balancing + tile.currentHealth -= surplus; + tile.currentHealth = Mathf.Clamp(tile.currentHealth, 0, 1); + } + + private void Diffuse() + { + var D = _diffusionCoefficient; + + for (int i = 0; i < _tiles.Count; i++) + { + float t = _tiles[i].temperature; + + _tiles.MapIndex(i, out var x, out var y); + + GetNeighbourTemperatures( + x, y, + out var nx, + out var ny, + out var px, + out var py); + + var temperature = TransferHeat(t, D, nx, ny, px, py); + + // TODO: what if it's really really cold out? + temperature = Mathf.Max(0, temperature); + _nextTemperatures[i] = temperature; + } + + for (int i = 0; i < _tiles.Count; i++) + { + var tile = _tiles[i]; + tile.temperature = _nextTemperatures[i]; + _tiles[i] = tile; + } + } + +} diff --git a/half-earth/scripts/WorldGrid.cs b/half-earth/scripts/WorldGrid.cs index 83c546e..fb78f04 100644 --- a/half-earth/scripts/WorldGrid.cs +++ b/half-earth/scripts/WorldGrid.cs @@ -16,13 +16,12 @@ public class WorldGrid : Node2D [Export] public float DiffusionCoefficient { get; set; } = 0.01f; - private float[] _nextValues = null; - public struct TileView { public ShaderMaterial material; } private TileView[] _tileViews; + private HeatSystem _heat; // Called when the node enters the scene tree for the first time. public override void _Ready() @@ -30,6 +29,8 @@ public class WorldGrid : Node2D _tileGrid = (TileGrid)_tileGridResource; GenerateCanvasItems(_tileGrid); + + _heat = new HeatSystem(_tileGrid, DiffusionCoefficient); } #region Positioning @@ -53,8 +54,6 @@ public class WorldGrid : Node2D var tile = _tileGrid[idx]; tile.type = tileType; _tileGrid[x, y] = tile; - - _tileViews[idx].material.SetShaderParam("lowColor", tileType.Color); } #endregion @@ -76,7 +75,6 @@ public class WorldGrid : Node2D this.Clear(); _tileViews = new TileView[grid.Count]; - _nextValues = new float[grid.Count]; for (int i = 0; i < grid.Count; i++) { @@ -96,95 +94,11 @@ public class WorldGrid : Node2D TileView view = default; view.material = (ShaderMaterial)canvasItem.Material; - //view.material.SetShaderParam("lowColor", _tileGrid.StartTileType.Color); _tileViews[i] = view; } } public ShaderMaterial GetTileMaterial(int idx) => _tileViews[idx].material; - #region Simulation - public void _on_Clock_OnTick(int ticks) - { - GenerateHeat(); - Diffuse(); - } - - private float TransferHeat(float t0, float alpha, float nx, float ny, float px, float py) - { - float d2tdx2 = nx - 2 * t0 + px; - float d2tdy2 = ny - 2 * t0 + py; - return t0 + alpha * (d2tdx2 + d2tdy2); - } - - private void GetNeighbourTemperatures(int x, int y, out float nx, out float ny, out float px, out float py) - { - // default value - var t = _tileGrid[x, y].temperature; - - nx = x > 0 ? _tileGrid[x - 1, y].temperature : t; - px = x < _tileGrid.Size - 1 ? _tileGrid[x + 1, y].temperature : t; - - ny = y > 0 ? _tileGrid[x, y - 1].temperature : t; - py = y < _tileGrid.Size - 1 ? _tileGrid[x, y + 1].temperature : t; - } - - private void ApplyHeatDamage(ref Tile tile) - { - if (!(tile.type is IDamageable damageable)) - return; - - var surplus = tile.temperature - damageable.Threshold; - if (surplus < 0) - return; - - // TODO: parameterised balancing - tile.currentHealth -= surplus; - tile.currentHealth = Mathf.Clamp(tile.currentHealth, 0, 1); - } - - private void GenerateHeat() - { - for (int i = 0; i < _tileGrid.Count; i++) - { - var tile = _tileGrid[i]; - var type = tile.type; - tile.temperature += type.HeatGeneration; - ApplyHeatDamage(ref tile); - _tileGrid[i] = tile; - } - } - - private void Diffuse() - { - var D = DiffusionCoefficient; - - for (int i = 0; i < _tileGrid.Count; i++) - { - float t = _tileGrid[i].temperature; - - _tileGrid.MapIndex(i, out var x, out var y); - - GetNeighbourTemperatures( - x, y, - out var nx, - out var ny, - out var px, - out var py); - - var temperature = TransferHeat(t, D, nx, ny, px, py); - - // TODO: what if it's really really cold out? - temperature = Mathf.Max(0, temperature); - _nextValues[i] = temperature; - } - - for (int i = 0; i < _tileGrid.Count; i++) - { - var tile = _tileGrid[i]; - tile.temperature = _nextValues[i]; - _tileGrid[i] = tile; - } - } - #endregion + public void _on_Clock_OnTick(int ticks) => _heat.Process(); }