using System.Diagnostics; using System.Linq; using Godot; using Godot.Collections; public class WorldChunk : Spatial { private readonly PackedScene _hexTile3DScene = GD.Load("res://scenes/HexTile3D.tscn"); private MultiMeshInstance _multiMeshInstance; private readonly Array _tileInstanceIndices = new(); private readonly SpatialMaterial _rectMaterial = new(); private Sprite _heightmapSprite; private TextureRect _heightmapTextureRect; private Sprite _noiseMask; private Sprite _noiseSprite; private bool _showTextureOverlay; [Export] public Vector2 ChunkIndex; public Color DebugColor = Colors.White; public Spatial Entities; public Spatial Tiles; private readonly HexGrid _hexGrid = new(); private readonly bool _showHexTiles; [Export] public Texture HeightMap; public int HeightMapFrameCount; public Image TileTypeImage; public Viewport HeightmapOffscreenViewport; [Export] public Texture NavigationMap; public bool NoiseTextureCheckerboardOverlay = false; // signals [Signal] private delegate void TileClicked(HexTile3D tile3d); [Signal] private delegate void TileHovered(HexTile3D tile3d); // other members public Rect2 PlaneRect; // ui elements // scene nodes private MeshInstance PlaneRectMesh; public int Size = 32; // resources // exports [Export] public Texture TileTypeMap; public int TileTypeMapFrameCount; public Viewport TileTypeOffscreenViewport; [Export] public bool ShowTextureOverlay { get => _showTextureOverlay; set { if (PlaneRectMesh != null) { PlaneRectMesh.Visible = value; } } } // Called when the node enters the scene tree for the first time. public override void _Ready() { PlaneRectMesh = (MeshInstance)FindNode("PlaneRectMesh"); Debug.Assert(PlaneRectMesh != null); if (PlaneRectMesh.Visible) { _showTextureOverlay = true; } Transform planeRectTransform = Transform.Identity; planeRectTransform = planeRectTransform.Scaled(new Vector3(PlaneRect.Size.x, 0.125f, PlaneRect.Size.y)); planeRectTransform.origin.x = PlaneRect.GetCenter().x; planeRectTransform.origin.z = PlaneRect.GetCenter().y; PlaneRectMesh.Transform = planeRectTransform; // PlaneRectMesh.MaterialOverride = new SpatialMaterial(); // ((SpatialMaterial)PlaneRectMesh.MaterialOverride).AlbedoColor = DebugColor; // ((SpatialMaterial)PlaneRectMesh.MaterialOverride).FlagsTransparent = true; HeightmapOffscreenViewport = (Viewport)FindNode("HeightmapOffscreenViewport"); HeightmapOffscreenViewport.Size = Vector2.One * Size; Debug.Assert(HeightmapOffscreenViewport != null); _noiseSprite = (Sprite)FindNode("NoiseSprite"); _noiseMask = (Sprite)FindNode("NoiseMask"); _heightmapSprite = (Sprite)FindNode("HeightmapSprite"); TileTypeOffscreenViewport = (Viewport)FindNode("TileTypeOffscreenViewport"); TileTypeOffscreenViewport.Size = Vector2.One * Size; Debug.Assert(TileTypeOffscreenViewport != null); Entities = (Spatial)FindNode("Entities"); Debug.Assert(Entities != null); Tiles = (Spatial)FindNode("Tiles"); Debug.Assert(Tiles != null); } public void SetSize(int size) { Size = size; if (TileTypeOffscreenViewport != null) { TileTypeOffscreenViewport.Size = Vector2.One * size; HeightmapOffscreenViewport.Size = Vector2.One * size; _noiseMask.Transform = Transform2D.Identity.Scaled(Vector2.One * size / _noiseMask.Texture.GetSize().x); _noiseSprite.Transform = Transform2D.Identity.Scaled(Vector2.One * size / _noiseSprite.Texture.GetSize().x); _heightmapSprite.Transform = Transform2D.Identity.Scaled(Vector2.One * size / _heightmapSprite.Texture.GetSize().x); } } public void SetChunkIndex(Vector2 chunkIndex, HexGrid hexGrid) { ChunkIndex = chunkIndex; float chunkSize = Size; Vector2 planeCoordSouthWest = hexGrid.GetHexCenterFromOffset(chunkIndex * chunkSize); Transform = new Transform(Basis.Identity, new Vector3(planeCoordSouthWest.x, 0, planeCoordSouthWest.y)); Vector2 localPlaneCoordSouthWest = new Vector2(-hexGrid.HexSize.x, hexGrid.HexSize.y) * 0.5f; Vector2 localPlaneCoordNorthEast = hexGrid.GetHexCenterFromOffset(Vector2.One * chunkSize) + new Vector2(hexGrid.HexSize.x, -hexGrid.HexSize.y) * 0.5f; PlaneRect = new Rect2( new Vector2(localPlaneCoordSouthWest.x, localPlaneCoordNorthEast.y), new Vector2(localPlaneCoordNorthEast.x - localPlaneCoordSouthWest.x, localPlaneCoordSouthWest.y - localPlaneCoordNorthEast.y) ); } public void InitializeTileInstances(Vector2 chunkIndex, MultiMeshInstance multiMeshInstance, int tileInstanceIndexStart) { _multiMeshInstance = multiMeshInstance; _tileInstanceIndices.Clear(); int chunkSize = Size; foreach (Spatial node in Tiles.GetChildren()) { node.QueueFree(); } foreach (int i in Enumerable.Range(0, chunkSize)) { foreach (int j in Enumerable.Range(0, chunkSize)) { HexTile3D tile3D = (HexTile3D)_hexTile3DScene.Instance(); tile3D.Connect("TileClicked", this, nameof(OnTileClicked)); tile3D.Connect("TileHovered", this, nameof(OnTileHovered)); tile3D.Cell.OffsetCoords = new Vector2(chunkIndex * chunkSize + new Vector2(i, j)); _tileInstanceIndices.Add(tileInstanceIndexStart + _tileInstanceIndices.Count); Transform tileTransform = Transform.Identity; Vector2 centerPlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(i, j)); tileTransform.origin = new Vector3(centerPlaneCoord.x, 0, centerPlaneCoord.y); tile3D.Transform = tileTransform; Tiles.AddChild(tile3D); } } _multiMeshInstance.Multimesh.VisibleInstanceCount = _multiMeshInstance.Multimesh.InstanceCount; GD.Print("Chunk: " + chunkIndex + " Last index: " + _tileInstanceIndices.Last()); } public void ClearContent() { foreach (Spatial child in Entities.GetChildren()) { child.QueueFree(); } } public void UpdateTileTransforms() { Transform chunkTransform = Transform.Identity; Vector2 chunkOriginPlaneCoord = _hexGrid.GetHexCenterFromOffset(ChunkIndex * Size); chunkTransform.origin = new Vector3(chunkOriginPlaneCoord.x, 0, chunkOriginPlaneCoord.y); Transform = chunkTransform; Basis tileOrientation = new(Vector3.Up, 90f * Mathf.Pi / 180f); foreach (int i in Enumerable.Range(0, _tileInstanceIndices.Count)) { int column = i % Size; int row = i / Size; Vector2 tilePlaneCoord = _hexGrid.GetHexCenterFromOffset(new Vector2(column, row)); Transform hexTransform = new(tileOrientation, chunkTransform.origin + new Vector3(tilePlaneCoord.x, 0, tilePlaneCoord.y)); if (_showHexTiles) { hexTransform = new Transform(tileOrientation.Scaled(Vector3.One * 0.95f), hexTransform.origin); } _multiMeshInstance.Multimesh.SetInstanceTransform(_tileInstanceIndices[i], hexTransform); } } // other members public void SaveToFile(string chunkName) { Image image = new(); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, TileTypeMap.GetData().GetData()); image.SavePng(chunkName + "_tileType.png"); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, NavigationMap.GetData().GetData()); image.SavePng(chunkName + "_navigationMap.png"); image.CreateFromData(Size, Size, false, Image.Format.Rgba8, HeightMap.GetData().GetData()); image.SavePng(chunkName + "_heightMap.png"); } public void LoadFromFile(string chunkName) { } public void SetNoisemap(Texture texture) { _noiseSprite.Texture = texture; _noiseSprite.Transform = Transform2D.Identity.Scaled(HeightmapOffscreenViewport.Size / _noiseSprite.Texture.GetSize().x); HeightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once; HeightMapFrameCount = 1; } public void SetHeightmap(Texture texture) { _heightmapSprite.Texture = texture; _heightmapSprite.Transform = Transform2D.Identity.Scaled(TileTypeOffscreenViewport.Size / _heightmapSprite.Texture.GetSize()); TileTypeOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Once; TileTypeMapFrameCount = 1; } public void CreateUnlockedTileTypeImage() { TileTypeImage = TileTypeOffscreenViewport.GetTexture().GetData(); TileTypeImage.Lock(); } public override void _Process(float delta) { Texture tileTypeTexture = TileTypeOffscreenViewport.GetTexture(); if (NoiseTextureCheckerboardOverlay) { Image tileTypeImage = tileTypeTexture.GetData(); tileTypeImage.Lock(); foreach (int i in Enumerable.Range(0, Size)) { foreach (int j in Enumerable.Range(0, Size)) { Vector2 textureCoord = new(i, j); Color baseColor = tileTypeImage.GetPixelv(textureCoord); if ((i + j) % 2 == 0) { tileTypeImage.SetPixelv(textureCoord, baseColor); } else { tileTypeImage.SetPixelv(textureCoord, baseColor * 0.6f); } } } tileTypeImage.Unlock(); ImageTexture imageTexture = new(); imageTexture.CreateFromImage(tileTypeImage, 0); tileTypeTexture = imageTexture; } _rectMaterial.AlbedoTexture = tileTypeTexture; _rectMaterial.FlagsTransparent = true; // _rectMaterial.AlbedoTexture = _heightmapRect.Texture; _rectMaterial.Uv1Scale = new Vector3(-3, -2, 1); _rectMaterial.Uv1Offset = Vector3.One * 2; //RectMaterial.Uv1Triplanar = true; PlaneRectMesh.SetSurfaceMaterial(0, _rectMaterial); if (HeightMapFrameCount == 0) { HeightmapOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; } HeightMapFrameCount = HeightMapFrameCount > 0 ? HeightMapFrameCount - 1 : 0; if (TileTypeMapFrameCount == 0) { TileTypeOffscreenViewport.RenderTargetUpdateMode = Viewport.UpdateMode.Disabled; } TileTypeMapFrameCount = TileTypeMapFrameCount > 0 ? TileTypeMapFrameCount - 1 : 0; PlaneRectMesh.MaterialOverride = null; } public void OnTileClicked(HexTile3D tile) { EmitSignal("TileClicked", tile); } public void OnTileHovered(HexTile3D tile) { EmitSignal("TileHovered", tile); } }