using Godot; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Godot.Collections; public enum DebugType { None, Slope, Height, Temperature, Moisture } [Tool] public partial class World : Node2D { private int _chunksX = 32; [Export] public int ChunkXCount { get => _chunksX; set { _chunksX = value; LoadTiles(); } } public Vector2I ChunksPerAxis { get { if (HeightMapImage == null) { return new Vector2I(_chunksX, _chunksX); } Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight()); int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX)); return new Vector2I(_chunksX, chunksY); } } private int _tileSize = 96; [Export] public int TileSize { get => _tileSize; set { _tileSize = value; LoadTiles(); } } [Export] public Texture2D HeightMapTexture; private float _rockThreshold = 0.05f; [Export] public float RockThreshold { get => _rockThreshold; set { _rockThreshold = value; LoadTiles(); } } private float _snowHeightThreshold = 0.8f; [Export] public float SnowHeightThreshold { get => _snowHeightThreshold; set { _snowHeightThreshold = value; LoadTiles(); } } private float _waterThreshold = 0.0f; [Export] public float WaterThreshold { get => _waterThreshold; set { _waterThreshold = value; LoadTiles(); } } private float _desertTemperatureThreshold = 0.7f; [Export] public float DesertTemperatureThreshold { get => _desertTemperatureThreshold; set { _desertTemperatureThreshold = value; LoadTiles(); } } private float _desertMoistureThreshold = 0.3f; [Export] public float DesertMoistureThreshold { get => _desertMoistureThreshold; set { _desertMoistureThreshold = value; LoadTiles(); } } private float _temperatureNoiseFrequency = 0.01f; [Export] public float TemperatureNoiseFrequency { get => _temperatureNoiseFrequency; set { _temperatureNoiseFrequency = value; LoadTiles(); } } private float _moistureNoiseFrequency = 0.01f; [Export] public float MoistureNoiseFrequency { get => _moistureNoiseFrequency; set { _moistureNoiseFrequency = value; LoadTiles(); } } private DebugType _debugType = DebugType.None; [Export] public DebugType DebugType { get => _debugType; set { _debugType = value; LoadTiles(); } } ConcurrentDictionary _tiles = new ConcurrentDictionary(); private CancellationTokenSource _cancellationTokenSource; public World() { } public override void _Ready() { // If the height map image is not assigned, log an error and return if (HeightMapTexture == null) { GD.PrintErr("Height map image is not assigned."); return; } LoadTiles(); } private void LoadTiles() { // Check if HeightMapImage is available if (HeightMapImage == null) { GD.PrintErr("Cannot load tiles: HeightMapImage is null"); return; } // Cancel any existing generation _cancellationTokenSource?.Cancel(); _cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = _cancellationTokenSource.Token; // Clear existing tiles foreach (var tile in _tiles.Values) { tile.Visible = false; // Delete and delete its children foreach (Node child in tile.GetChildren()) { child.QueueFree(); } tile.QueueFree(); } _tiles.Clear(); int scale = 1; // Store local copies of parameters to avoid them changing mid-generation int tileSize = TileSize; int chunksX = _chunksX; // Get the resolution of the height map image Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight()); // Calculate the number of chunks in the Y direction based on the aspect ratio int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX)); // Loop through each chunk position and create a Tile Task.Run(() => { try { for (int x = 0; x < chunksX; x++) { if (cancellationToken.IsCancellationRequested) return; Parallel.For(0, chunksY, new ParallelOptions { CancellationToken = cancellationToken }, y => { if (cancellationToken.IsCancellationRequested) return; Vector2I chunkId = new Vector2I(x, y); Tile tile = new Tile(new Vector2I(tileSize, tileSize), chunkId, this); tile.CancellationToken = cancellationToken; tile.Position = new Vector2(chunkId.X * tileSize, chunkId.Y * tileSize) * scale; tile.Scale = new Vector2(scale, scale); if (!cancellationToken.IsCancellationRequested) { CallDeferred("add_child", tile); _tiles[chunkId] = tile; GD.Print("Loaded tile at chunk " + chunkId); } }); } } catch (OperationCanceledException) { GD.Print("Tile generation cancelled"); } }, cancellationToken); } private Image _heightMapCache; public Image HeightMapImage { get { // If not cached, load and cache it if (_heightMapCache == null && HeightMapTexture != null) { _heightMapCache = HeightMapTexture.GetImage(); // If GetImage() returns null, try loading directly from file if (_heightMapCache == null && HeightMapTexture.ResourcePath != "") { _heightMapCache = Image.LoadFromFile(HeightMapTexture.ResourcePath); if (_heightMapCache == null) { GD.PushError($"Could not load heightmap from {HeightMapTexture.ResourcePath}"); } } } return _heightMapCache; } } }