From 4650a5b7f3cc77ba2c31222297c9edc1e3c3bc91 Mon Sep 17 00:00:00 2001 From: ktyl Date: Fri, 9 Dec 2022 01:36:47 +0100 Subject: [PATCH] diffusion (#12) Reviewed-on: https://sauce.pizzawednes.day/ktyl/half-earth/pulls/12 --- gdd.md | 21 ++++++ half-earth/materials/cursor.tres | 6 +- half-earth/materials/tile.tres | 6 +- half-earth/nodes/clock.tscn | 7 ++ half-earth/nodes/debug_ui.tscn | 36 +++++++++++ half-earth/nodes/game.tscn | 16 ++++- half-earth/scripts/Clock.cs | 67 +++++++++++++++++++ half-earth/scripts/DebugUI.cs | 37 +++++++++++ half-earth/scripts/GridCursor.cs | 12 ++-- half-earth/scripts/Tile.cs | 8 +++ half-earth/scripts/WorldGrid.cs | 86 +++++++++++++++++++++---- half-earth/shaders/tile_shader.gdshader | 8 +-- 12 files changed, 279 insertions(+), 31 deletions(-) create mode 100644 half-earth/nodes/clock.tscn create mode 100644 half-earth/nodes/debug_ui.tscn create mode 100644 half-earth/scripts/Clock.cs create mode 100644 half-earth/scripts/DebugUI.cs create mode 100644 half-earth/scripts/Tile.cs diff --git a/gdd.md b/gdd.md index 8ad3e5d..3c72c77 100644 --- a/gdd.md +++ b/gdd.md @@ -28,6 +28,27 @@ Add a tick counter as a UI element. Diffusing should use 'pull' operations; tiles should pull values from surrounding tiles. This is an attempt to think forwards to parallelising computation of diffusiong - ideally using the GPU. +#### Diffusion Algorithm + +Diffusion is modelled as two-dimensional steady-state heat conduction. +This means that the amount of a value does not change between the start and end of the computation of the diffusion step. +The value at one 2D location is calculated based on the values of itself and its neighbours in the previous step. +A diffusion coefficient controls the rate of diffusion. + +[Video explanation](https://www.youtube.com/watch?v=RGbV7T_iWT8) +[Derivation](https://www.tec-science.com/thermodynamics/heat/heat-equation-diffusion-equation/) + +Implementation: + +```cs +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); +} +``` + ### Tile Types Tiles start as wild tiles. diff --git a/half-earth/materials/cursor.tres b/half-earth/materials/cursor.tres index a3218a5..91a714d 100644 --- a/half-earth/materials/cursor.tres +++ b/half-earth/materials/cursor.tres @@ -4,6 +4,6 @@ [resource] shader = ExtResource( 1 ) -shader_param/baseColor = Color( 0.992157, 1, 0, 1 ) -shader_param/highlightColor = Color( 1, 0, 0.984314, 1 ) -shader_param/isHighlighted = 0 +shader_param/lowColor = Color( 1, 0.960784, 0, 1 ) +shader_param/highColor = Color( 0.921569, 0, 1, 1 ) +shader_param/t = null diff --git a/half-earth/materials/tile.tres b/half-earth/materials/tile.tres index 327c5eb..5d91ee6 100644 --- a/half-earth/materials/tile.tres +++ b/half-earth/materials/tile.tres @@ -5,6 +5,6 @@ [resource] resource_local_to_scene = true shader = ExtResource( 1 ) -shader_param/baseColor = Color( 0.0823529, 0, 1, 1 ) -shader_param/highlightColor = Color( 1, 0, 0, 1 ) -shader_param/isHighlighted = null +shader_param/lowColor = Color( 0, 0, 0, 1 ) +shader_param/highColor = Color( 1, 0.560784, 0, 1 ) +shader_param/t = null diff --git a/half-earth/nodes/clock.tscn b/half-earth/nodes/clock.tscn new file mode 100644 index 0000000..2c5bc42 --- /dev/null +++ b/half-earth/nodes/clock.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://scripts/Clock.cs" type="Script" id=1] + +[node name="Clock" type="Node"] +script = ExtResource( 1 ) +_tickRate = 10.0 diff --git a/half-earth/nodes/debug_ui.tscn b/half-earth/nodes/debug_ui.tscn new file mode 100644 index 0000000..4f2ab7f --- /dev/null +++ b/half-earth/nodes/debug_ui.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://scripts/DebugUI.cs" type="Script" id=1] + +[node name="Debug UI" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +TicksLabelPath = NodePath("Ticks") +PauseLabelPath = NodePath("Paused") + +[node name="Ticks" type="Label" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -10.0 +margin_bottom = -30.0 +grow_horizontal = 0 +grow_vertical = 0 +text = "ticks: 0" +align = 2 +valign = 2 + +[node name="Paused" type="Label" parent="."] +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -10.0 +margin_bottom = -10.0 +grow_horizontal = 0 +grow_vertical = 0 +text = "pause: false" +align = 2 +valign = 2 diff --git a/half-earth/nodes/game.tscn b/half-earth/nodes/game.tscn index c7bf9d9..9b75583 100644 --- a/half-earth/nodes/game.tscn +++ b/half-earth/nodes/game.tscn @@ -1,11 +1,23 @@ -[gd_scene load_steps=3 format=2] +[gd_scene load_steps=5 format=2] [ext_resource path="res://nodes/grid.tscn" type="PackedScene" id=1] [ext_resource path="res://nodes/grid_cursor.tscn" type="PackedScene" id=2] +[ext_resource path="res://nodes/debug_ui.tscn" type="PackedScene" id=3] +[ext_resource path="res://nodes/clock.tscn" type="PackedScene" id=4] -[node name="Root" type="Node2D"] +[node name="Root" type="Node"] [node name="Grid" parent="." instance=ExtResource( 1 )] +DiffusionCoefficient = 0.1 [node name="Cursor" parent="." instance=ExtResource( 2 )] Grid = NodePath("../Grid") + +[node name="Debug UI" parent="." instance=ExtResource( 3 )] + +[node name="Clock" parent="." instance=ExtResource( 4 )] +_autoTick = true + +[connection signal="OnPauseChanged" from="Clock" to="Debug UI" method="_on_Clock_OnPauseChanged"] +[connection signal="OnTick" from="Clock" to="Grid" method="_on_Clock_OnTick"] +[connection signal="OnTick" from="Clock" to="Debug UI" method="_on_Clock_OnTick"] diff --git a/half-earth/scripts/Clock.cs b/half-earth/scripts/Clock.cs new file mode 100644 index 0000000..3fb7456 --- /dev/null +++ b/half-earth/scripts/Clock.cs @@ -0,0 +1,67 @@ +using Godot; +using System; + +public class Clock : Node +{ + [Signal] + delegate void OnTick(int ticks); + [Signal] + delegate void OnPauseChanged(bool paused); + + [Export(PropertyHint.None, "Number of ticks per second")] + private float _tickRate; + private float TickPeriod => 1.0f / _tickRate; + + [Export] + private bool _autoTick; + + private int _ticks = 0; + private bool _paused = false; + + public override void _Input(InputEvent @event) + { + base._Input(@event); + + if (@event is InputEventKey keyEvent && keyEvent.Pressed) + { + switch ((KeyList)keyEvent.Scancode) + { + case KeyList.Space: + if (_autoTick) + { + _paused = !_paused; + EmitSignal(nameof(OnPauseChanged), _paused); + return; + } + Tick(); + break; + } + } + } + + private float _secondsSinceLastTick; + + public override void _Process(float delta) + { + base._Process(delta); + + if (!_autoTick) + return; + + if (_paused) + return; + + _secondsSinceLastTick += delta; + if (_secondsSinceLastTick < TickPeriod) + return; + + _secondsSinceLastTick -= TickPeriod; + Tick(); + } + + private void Tick() + { + _ticks++; + EmitSignal(nameof(OnTick), _ticks); + } +} diff --git a/half-earth/scripts/DebugUI.cs b/half-earth/scripts/DebugUI.cs new file mode 100644 index 0000000..8adc836 --- /dev/null +++ b/half-earth/scripts/DebugUI.cs @@ -0,0 +1,37 @@ +using Godot; +using System; + +public class DebugUI : Control +{ + [Export] + public NodePath TicksLabelPath { private get; set; } + private Label _ticksLabel; + + [Export] + public NodePath PauseLabelPath { private get; set; } + private Label _pauseLabel; + + public override void _Ready() + { + _ticksLabel = GetNode