Files
Frontiers/Source/World.cs

317 lines
8.0 KiB
C#

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;
using Environment = Godot.Environment;
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 float _TemperatureNoiseAmplitude = 0.1f;
[Export]
public float TemperatureNoiseAmplitude
{
get => _TemperatureNoiseAmplitude;
set
{
_TemperatureNoiseAmplitude = value;
LoadTiles();
}
}
private float _MoistureNoiseAmplitude = 0.1f;
[Export]
public float MoistureNoiseAmplitude
{
get => _MoistureNoiseAmplitude;
set
{
_MoistureNoiseAmplitude = value;
LoadTiles();
}
}
private DebugType _debugType = DebugType.None;
[Export]
public DebugType DebugType
{
get => _debugType;
set
{
_debugType = value;
LoadTiles();
}
}
ConcurrentDictionary<Vector2I, Tile> _tiles = new ConcurrentDictionary<Vector2I, Tile>();
private CancellationTokenSource _cancellationTokenSource;
private static readonly int MaxDegreeOfParallelism = Math.Max(1, (int)(System.Environment.ProcessorCount * 0.5));
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;
for (int y = 0; y < chunksY; 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;
}
}
}