GodotComponentTest/scenes/TileWorld.cs

507 lines
16 KiB
C#

using System;
using System.Diagnostics;
using System.Linq;
using Godot;
using Godot.Collections;
using Namespace;
public class TileWorld : Spatial
{
// exports
[Export] public bool DebugMap;
[ExportFlagsEnum(typeof(MapType))] public MapType GenerationMapType = MapType.Debug;
[Export] public int Size = 64;
// constants
private static readonly Color RockColor = new(0.5f, 0.5f, 0.4f);
private static readonly Color GrassColor = new(0, 0.4f, 0);
private PackedScene _chestScene = GD.Load<PackedScene>("res://entities/Chest.tscn");
private GenerationState _currentGenerationState = GenerationState.Heightmap;
private Spatial _environmentNode;
private readonly Array<Spatial> _grassAssets = new();
private enum GenerationState
{
Heightmap,
Color,
Objects,
Done
}
// signals
[Signal]
private delegate void WorldGenerated();
// public members
public enum MapType
{
Noise,
Debug,
Flat
}
public Image ColormapImage;
public Spatial Entities;
public Image HeightmapImage;
public float HeightScale = 1.0f;
public HexGrid HexGrid;
public Image NavigationmapImage;
public int Seed = 0;
// private members
private int _halfSize;
private TextureRect _heightmapOffscreenTextureRect;
private Viewport _heightmapOffscreenViewport;
private int _resizeExtraFrameCount;
private bool _resizeTriggered;
private readonly Array<Spatial> _rockAssets = new();
private Random _tileTypeRandom;
private readonly Array<Spatial> _treeAssets = new();
private TextureRect _worldOffscreenTextureRect;
private Viewport _worldOffscreenViewport;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
HexGrid = new HexGrid();
_tileTypeRandom = new Random();
_worldOffscreenViewport = (Viewport)GetNode("WorldOffscreenViewport");
Debug.Assert(_worldOffscreenViewport != null);
_worldOffscreenViewport.Size = new Vector2(Size, Size);
_worldOffscreenTextureRect = (TextureRect)GetNode("WorldOffscreenViewport/TextureRect");
Debug.Assert(_worldOffscreenTextureRect != null);
_worldOffscreenTextureRect.SetSize(new Vector2(Size, Size));
_heightmapOffscreenViewport = (Viewport)GetNode("HeightmapOffscreenViewport");
Debug.Assert(_heightmapOffscreenViewport != null);
_heightmapOffscreenViewport.Size = new Vector2(Size, Size);
_heightmapOffscreenTextureRect = (TextureRect)GetNode("HeightmapOffscreenViewport/TextureRect");
Debug.Assert(_heightmapOffscreenTextureRect != null);
_heightmapOffscreenTextureRect.SetSize(new Vector2(Size, Size));
GetNode<Spatial>("Assets").Visible = false;
foreach (Spatial asset in GetNode<Node>("Assets/Rocks").GetChildren())
{
_rockAssets.Add(asset);
}
foreach (Spatial asset in GetNode<Node>("Assets/Grass").GetChildren())
{
_grassAssets.Add(asset);
}
foreach (Spatial asset in GetNode<Node>("Assets/Trees").GetChildren())
{
_treeAssets.Add(asset);
}
_chestScene = GD.Load<PackedScene>("res://entities/Chest.tscn");
_environmentNode = GetNode<Spatial>("Environment");
Entities = GetNode<Spatial>("Entities");
ResetWorldImages(Size);
}
public void ResetWorldImages(int size)
{
GD.Print("Resetting World Images to size " + size);
Vector2 sizeVector = new Vector2(size, size);
ColormapImage = new Image();
ColormapImage.Create(size, size, false, Image.Format.Rgba8);
ColormapImage.FillRect(new Rect2(0, 0, size, size), Colors.Black);
_worldOffscreenTextureRect.SetSize(sizeVector);
_worldOffscreenViewport.Size = sizeVector;
HeightmapImage = new Image();
HeightmapImage.Create(size, size, false, Image.Format.Rf);
HeightmapImage.FillRect(new Rect2(0, 0, size, size), Colors.ForestGreen);
_heightmapOffscreenTextureRect.SetSize(sizeVector);
_heightmapOffscreenViewport.Size = sizeVector;
NavigationmapImage = new Image();
NavigationmapImage.Create(size, size, false, Image.Format.Rgb8);
NavigationmapImage.FillRect(new Rect2(0, 0, size, size), new Color(1, 1, 1));
}
public void Generate(int size)
{
GD.Print("Triggering generation for size: " + size);
if (Size != size)
{
ResetWorldImages(size);
_resizeTriggered = true;
_resizeExtraFrameCount = 1;
}
Size = size;
_worldOffscreenViewport.Size = new Vector2(size, size);
_heightmapOffscreenViewport.Size = new Vector2(size, size);
_halfSize = Mathf.RoundToInt(size) / 2;
HexGrid.Obstacles.Clear();
HexGrid.Barriers.Clear();
OnMapGenerationStart();
switch (GenerationMapType)
{
case MapType.Debug:
GenerateDebugMap();
break;
case MapType.Flat:
GenerateFlatMap();
break;
case MapType.Noise:
GenerateNoiseMap();
break;
}
}
private void GenerateDebugMap()
{
ColormapImage = new Image();
ColormapImage.Create(Size, Size, false, Image.Format.Rgba8);
HeightmapImage = new Image();
HeightmapImage.Create(Size, Size, false, Image.Format.Rf);
HeightmapImage.Lock();
ColormapImage.Lock();
foreach (int coordX in Enumerable.Range(0, Size))
{
foreach (int coordY in Enumerable.Range(0, Size))
{
ColormapImage.SetPixel(coordX, coordY,
new Color((float)Mathf.Min(coordX, coordY) / Size, 0, 0));
HeightmapImage.SetPixel(coordX, coordY,
new Color((float)Mathf.Min(coordX, coordY) / Size, 0, 0));
}
}
// Colormap.SetPixel(Size - 1, Size - 1, new Color(1, 1, 1, 1));
ColormapImage.Fill(Colors.ForestGreen);
ColormapImage.Unlock();
OnMapGenerationComplete();
}
private void GenerateFlatMap()
{
ColormapImage = new Image();
ColormapImage.Create(Size, Size, false, Image.Format.Rgba8);
HeightmapImage = new Image();
HeightmapImage.Create(Size, Size, false, Image.Format.Rf);
HeightmapImage.Lock();
ColormapImage.Lock();
foreach (int coordX in Enumerable.Range(0, Size))
{
foreach (int coordY in Enumerable.Range(0, Size))
{
HeightmapImage.SetPixel(coordX, coordY,
new Color(0.5f, 0.5f, 0.5f));
}
}
ColormapImage.Fill(Colors.ForestGreen);
ColormapImage.Unlock();
OnMapGenerationComplete();
}
private void GenerateNoiseMap()
{
HeightmapImage = new Image();
HeightmapImage.Create(Size, Size, false, Image.Format.Rgba8);
OpenSimplexNoise noiseGenerator = new OpenSimplexNoise();
noiseGenerator.Seed = Seed;
noiseGenerator.Octaves = 4;
noiseGenerator.Period = 20;
noiseGenerator.Persistence = 0.2f;
noiseGenerator.Lacunarity = 4;
ImageTexture heightmapTexture = new ImageTexture();
heightmapTexture.CreateFromImage(noiseGenerator.GetSeamlessImage(Size));
heightmapTexture.Flags = 0;
_heightmapOffscreenTextureRect.Texture = heightmapTexture;
OnHeightMapChanged();
}
private void OnMapGenerationStart()
{
foreach (Node child in _environmentNode.GetChildren())
{
child.QueueFree();
}
foreach (Node child in Entities.GetChildren())
{
child.QueueFree();
}
}
private void OnHeightMapChanged()
{
GD.Print("Triggering rendering of height map");
_currentGenerationState = GenerationState.Heightmap;
_heightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once;
}
private void OnMapGenerationComplete()
{
_currentGenerationState = GenerationState.Done;
_worldOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled;
_heightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled;
_resizeTriggered = false;
HeightmapImage.Lock();
EmitSignal("WorldGenerated");
}
private Spatial SelectAsset(Vector2 offsetCoord, Array<Spatial> assets, Random randomGenerator, double probability)
{
if (randomGenerator.NextDouble() < 1.0 - probability)
{
return null;
}
int assetIndex = randomGenerator.Next(assets.Count);
Spatial assetInstance = (Spatial)assets[assetIndex].Duplicate();
Transform assetTransform = Transform.Identity;
assetTransform.origin = GetHexCenterFromOffset(offsetCoord);
assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
assetTransform.basis =
assetTransform.basis.Rotated(Vector3.Up, (float)(randomGenerator.NextDouble() * Mathf.Pi * 2));
assetInstance.Transform = assetTransform;
return assetInstance;
}
private bool IsColorEqualApprox(Color colorA, Color colorB)
{
Vector3 colorDifference = new Vector3(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1;
}
private bool IsColorWater(Color color)
{
return (color.r == 0 && color.g == 0 && color.b > 0.01);
}
public void MarkCellUnwalkable(HexCell cell)
{
HexGrid.AddObstacle(cell);
NavigationmapImage.Lock();
NavigationmapImage.SetPixelv(OffsetToTextureCoord(cell.OffsetCoords), Colors.Red);
NavigationmapImage.Unlock();
}
private void PopulateEnvironment()
{
Random environmentRandom = new Random(Seed);
ColormapImage.Lock();
HeightmapImage.Lock();
foreach (int textureCoordU in Enumerable.Range(0, Size))
{
foreach (int textureCoordV in Enumerable.Range(0, Size))
{
Color colorValue = ColormapImage.GetPixel(textureCoordU, textureCoordV);
Vector2 textureCoord = new Vector2(textureCoordU, textureCoordV);
HexCell cell = TextureCoordToCell(textureCoord);
Vector2 offsetCoord = cell.OffsetCoords;
if (IsColorEqualApprox(colorValue, RockColor))
{
Spatial rockAsset = SelectAsset(offsetCoord, _rockAssets, environmentRandom, 0.15);
if (rockAsset != null)
{
_environmentNode.AddChild(rockAsset);
MarkCellUnwalkable(cell);
}
}
else if (IsColorEqualApprox(colorValue, GrassColor))
{
Spatial grassAsset = SelectAsset(offsetCoord, _grassAssets, environmentRandom, 0.35);
if (grassAsset != null)
{
_environmentNode.AddChild(grassAsset);
}
Spatial treeAsset = SelectAsset(offsetCoord, _treeAssets, environmentRandom, 0.10);
if (treeAsset != null)
{
Entities.AddChild(treeAsset);
MarkCellUnwalkable(cell);
}
else if (environmentRandom.NextDouble() < 0.01)
{
Chest chestAsset = (Chest)_chestScene.Instance();
Transform assetTransform = Transform.Identity;
assetTransform.origin = GetHexCenterFromOffset(offsetCoord);
assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
chestAsset.Transform = assetTransform;
Entities.AddChild(chestAsset);
MarkCellUnwalkable(cell);
}
}
else if (IsColorWater(colorValue))
{
MarkCellUnwalkable(cell);
}
}
}
HeightmapImage.Unlock();
ColormapImage.Unlock();
}
public override void _Process(float delta)
{
if (_resizeTriggered && _resizeExtraFrameCount > 0)
{
_resizeExtraFrameCount--;
return;
}
if (_currentGenerationState == GenerationState.Heightmap)
{
HeightmapImage.CopyFrom(_heightmapOffscreenViewport.GetTexture().GetData());
_currentGenerationState = GenerationState.Color;
ImageTexture imageTexture = new ImageTexture();
imageTexture.CreateFromImage(HeightmapImage);
imageTexture.Flags = 0;
_worldOffscreenTextureRect.Texture = imageTexture;
GD.Print("Triggering rendering of color map");
_worldOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once;
_resizeExtraFrameCount = 1;
}
else if (_currentGenerationState == GenerationState.Color)
{
ColormapImage = new Image();
ColormapImage.Create(Size, Size, false, Image.Format.Rgb8);
ColormapImage.CopyFrom(_worldOffscreenViewport.GetTexture().GetData());
_currentGenerationState = GenerationState.Objects;
PopulateEnvironment();
}
else if (_currentGenerationState == GenerationState.Objects)
{
OnMapGenerationComplete();
}
}
public bool IsOffsetCoordValid(Vector2 offsetCoord)
{
return ((int)Math.Clamp(offsetCoord.x, -(float)Size / 2, (float)Size / 2 - 1) == (int)offsetCoord.x
&& (int)Math.Clamp(offsetCoord.y, -(float)Size / 2, (float)Size / 2 - 1) == (int)offsetCoord.y);
}
public HexTile3D.TileType GetTileTypeAtOffset(Vector2 offsetCoord)
{
if (!IsOffsetCoordValid(offsetCoord))
{
return HexTile3D.TileType.Undefined;
}
return HexTile3D.ValidTileTypes[_tileTypeRandom.Next(HexTile3D.ValidTileTypes.Length)];
}
public Vector2 OffsetToTextureCoord(Vector2 offsetCoord)
{
Vector2 mapSize = Vector2.One * Size;
Vector2 textureCoord = (offsetCoord + mapSize / 2).PosMod(mapSize);
return textureCoord;
}
public void SetHeightAtOffset(Vector2 offsetCoord, float height)
{
Vector2 textureCoord = OffsetToTextureCoord(offsetCoord);
HeightmapImage.SetPixel((int)textureCoord.x, (int)textureCoord.y, new Color(height, 0f, 0f));
}
public float GetHeightAtOffset(Vector2 offsetCoord)
{
if (_currentGenerationState != GenerationState.Done)
{
return 0f;
}
Vector2 textureCoord = OffsetToTextureCoord(offsetCoord);
float heightmapHeight = (HeightmapImage.GetPixel((int)textureCoord.x, (int)(textureCoord.y)).r - 0.5f) *
HeightScale;
if (heightmapHeight <= 0)
heightmapHeight -= 0.03f;
else
heightmapHeight = 0f;
return heightmapHeight;
}
public void SetTileColorAtOffset(Vector2 offsetCoord, Color color)
{
Vector2 textureCoord = OffsetToTextureCoord(offsetCoord);
ColormapImage.Lock();
ColormapImage.SetPixel((int)textureCoord.x, (int)textureCoord.y, color);
ColormapImage.Unlock();
}
public Vector2 WorldToOffsetCoords(Vector3 worldCoord)
{
return HexGrid.GetHexAt(new Vector2(worldCoord.x, worldCoord.z)).OffsetCoords +
Vector2.One * Mathf.Round(Size / 2f);
}
public Vector3 GetTileWorldCenterFromOffset(Vector2 offsetCoord)
{
Vector2 tileCenter = HexGrid.GetHexCenterFromOffset(offsetCoord - Vector2.One * Mathf.Round(Size / 2f));
return new Vector3(tileCenter.x, GetHeightAtOffset(offsetCoord), tileCenter.y);
}
public Vector3 GetHexCenterFromOffset(Vector2 offsetCoord)
{
Vector2 tileCenter = HexGrid.GetHexCenterFromOffset(offsetCoord);
return new Vector3(tileCenter.x, GetHeightAtOffset(offsetCoord), tileCenter.y);
}
public HexCell TextureCoordToCell(Vector2 textureCoord)
{
return HexCell.FromOffsetCoords(textureCoord - Vector2.One * _halfSize);
}
public Vector2 TextureCoordToOffsetCoord(Vector2 textureCoord)
{
return TextureCoordToCell(textureCoord).OffsetCoords;
}
}