using System; using Godot; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using GoDotLog; public class StreamContainer : Spatial { // readonly variables private readonly Transform _deactivatedTileTransform = new Transform(Basis.Identity, Vector3.Up * 1000); // scene nodes private MeshInstance _bounds; private Spatial _activeTiles; private Queue _unusedTiles; public TileWorld TileWorld; private MultiMeshInstance _tileMultiMesh; // resources private PackedScene _hexTileScene = GD.Load("res://scenes/HexTile3D.tscn"); // exports [Export] public Vector2 Dimensions = new Vector2(8, 4); [Export] public NodePath World; [Export] private bool ShowHexTiles = false; [Signal] delegate void TileClicked(HexTile3D tile3d); [Signal] delegate void TileHovered(HexTile3D tile3d); // other members private Rect2 _currentOffsetCoordRect; private Rect2 _oldOffsetCoordRect; private HexGrid _hexGrid; private Dictionary _coordToTile = new(); private Dictionary _tileToInstanceIndex = new(); public List RemovedCoords = new List(); public List AddedCoords = new List(); public Rect2 CurrentOffsetCoordRect { get { return _currentOffsetCoordRect; } } public void SetWorld(TileWorld tileWorld) { TileWorld = tileWorld; } // Called when the node enters the scene tree for the first time. public override void _Ready() { _unusedTiles = new Queue(); _bounds = GetNode("Bounds"); _activeTiles = GetNode("ActiveTiles"); _tileMultiMesh = GetNode("TileMultiMesh"); _tileMultiMesh.Multimesh.InstanceCount = (int)((Dimensions.x + 5) * (Dimensions.y + 5)); _tileMultiMesh.Multimesh.InstanceCount = 1810; foreach (int i in Enumerable.Range(0, _tileMultiMesh.Multimesh.InstanceCount)) { _tileMultiMesh.Multimesh.SetInstanceTransform(i, _deactivatedTileTransform); } _hexGrid = new HexGrid(); Transform boundsTransform = Transform.Identity; boundsTransform = boundsTransform.Scaled(new Vector3(Dimensions.x, 1, Dimensions.y)); _bounds.Transform = boundsTransform; TileWorld = GetNode(World); } public void UpdateRects(Vector2 centerPlane) { _oldOffsetCoordRect = _currentOffsetCoordRect; Vector2 bottomLeftCoord = centerPlane - new Vector2(Dimensions.x / 2, Dimensions.y / 2); Vector2 topRightCoord = centerPlane + new Vector2(Dimensions.x / 2, Dimensions.y / 2); // GD.Print("World rect now: " + _worldRect.ToString() + " center: " + _worldRect.GetCenter()); // y axis needs to be inverted as HexGrid's offset coordinates use the opposite axis direction HexCell bottomLeftCell = _hexGrid.GetHexAt(new Vector2(bottomLeftCoord.x, topRightCoord.y)); HexCell topRightCell = _hexGrid.GetHexAt(new Vector2(topRightCoord.x, bottomLeftCoord.y)); _currentOffsetCoordRect = new Rect2(bottomLeftCell.OffsetCoords, topRightCell.OffsetCoords - bottomLeftCell.OffsetCoords + Vector2.One); // Vector2 centerOffset = _hexGrid.GetHexAt(centerPlane).OffsetCoords; // _currentOffsetCoordRect = new Rect2(centerOffset + new Vector2(-Dimensions.x / 2, -Dimensions.y / 2), // Dimensions); // GD.Print("Offset rect now: " + _currentOffsetCoordRect.ToString() + " center: " + // _currentOffsetCoordRect.GetCenter()); Transform boundsTransform = _bounds.Transform; boundsTransform.origin.x = centerPlane.x; boundsTransform.origin.z = centerPlane.y; _bounds.Transform = boundsTransform; // GD.Print("Bounds Transform: " + boundsTransform.ToString()); // GD.Print("Bounds Global : " + _bounds.GlobalTransform.ToString()); if (!_currentOffsetCoordRect.Equals(_oldOffsetCoordRect)) { UpdateTileCache(); } } public void UpdateTileCache() { RemovedCoords.Clear(); AddedCoords.Clear(); _unusedTiles.Clear(); Rect2 expandedRect = _currentOffsetCoordRect.Merge(_oldOffsetCoordRect).Grow(2); Rect2 clippedRect = _currentOffsetCoordRect.Clip(_oldOffsetCoordRect); MarkUnusedTiles(expandedRect, clippedRect); AddNewTiles(expandedRect, clippedRect); foreach (HexTile3D tile3D in _unusedTiles.ToArray()) { RemovedCoords.Add(tile3D.OffsetCoords); _activeTiles.RemoveChild(tile3D); tile3D.Disconnect("TileClicked", this, nameof(OnTileClicked)); tile3D.Disconnect("TileHovered", this, nameof(OnTileHovered)); tile3D.QueueFree(); } } public void MarkUnusedTiles(Rect2 expandedRect, Rect2 clippedRect) { foreach (int coord_x in Enumerable.Range(Mathf.FloorToInt(expandedRect.Position.x) - 5, Mathf.CeilToInt(expandedRect.Size.x) + 20)) { foreach (int coord_y in Enumerable.Range(Mathf.FloorToInt(expandedRect.Position.y) - 5, Mathf.CeilToInt(expandedRect.Size.y) + 20)) { Vector2 coord = new Vector2(coord_x, coord_y); if (clippedRect.HasPoint(coord)) { continue; } bool isInCurrent = _currentOffsetCoordRect.HasPoint(coord); bool isInOld = _oldOffsetCoordRect.HasPoint(coord); if (isInOld && !isInCurrent && _coordToTile.Keys.Contains(coord)) { HexTile3D tile3D = _coordToTile[coord]; OnTileDeactivate(tile3D, coord); } } } } public void AddNewTiles(Rect2 expandedRect, Rect2 clippedRect) { foreach (int coord_x in Enumerable.Range(Mathf.FloorToInt(_currentOffsetCoordRect.Position.x), Mathf.CeilToInt(_currentOffsetCoordRect.Size.x))) { foreach (int coord_y in Enumerable.Range(Mathf.FloorToInt(_currentOffsetCoordRect.Position.y), Mathf.CeilToInt(_currentOffsetCoordRect.Size.y))) { Vector2 coord = new Vector2(coord_x, coord_y); if (clippedRect.HasPoint(coord)) { continue; } if (_unusedTiles.Count == 0) { AddedCoords.Add(coord); HexTile3D tile3D = GetTile3dAt(coord); } else { HexTile3D tile3D = _unusedTiles.Dequeue(); tile3D.OffsetCoords = coord; tile3D.Type = TileWorld.GetTileTypeAtOffset(coord); Transform tileTransform = tile3D.Transform; tileTransform.origin.y = TileWorld.GetHeightAtOffset(coord); tile3D.Transform = tileTransform; OnTileActivate(tile3D); _coordToTile[coord] = tile3D; } } } } public HexTile3D GetTile3dAt(Vector2 offsetCoords) { if (!_coordToTile.Keys.Contains(offsetCoords)) { HexTile3D tile3D = (HexTile3D)_hexTileScene.Instance(); tile3D.OffsetCoords = offsetCoords; _activeTiles.AddChild(tile3D); tile3D.Connect("TileClicked", this, nameof(OnTileClicked)); tile3D.Connect("TileHovered", this, nameof(OnTileHovered)); if (ShowHexTiles) { MeshInstance HexTileMesh = tile3D.GetNode("Mesh"); HexTileMesh.Transform = HexTileMesh.Transform.Scaled(new Vector3(0.95f, 1, 0.95f)); } Transform tileTransform = tile3D.Transform; tileTransform.origin.y = TileWorld.GetHeightAtOffset(offsetCoords); tile3D.Transform = tileTransform; tile3D.Type = TileWorld.GetTileTypeAtOffset(offsetCoords); _tileToInstanceIndex[tile3D] = _tileToInstanceIndex.Count; _coordToTile[offsetCoords] = tile3D; } OnTileActivate(_coordToTile[offsetCoords]); return _coordToTile[offsetCoords]; } public void SetCenterTile(HexCell cell) { UpdateRects(_hexGrid.GetHexCenter(cell)); } public void SetTileMaterial(ShaderMaterial material) { foreach (Spatial node in _activeTiles.GetChildren()) { HexTile3D tile = (HexTile3D)node; if (tile == null) { continue; } tile.Mesh.SetSurfaceMaterial(0, material); } } public void OnTileActivate(HexTile3D tile3D) { int instanceIndex = _tileToInstanceIndex[tile3D]; Vector3 scale = Vector3.One; if (ShowHexTiles) { scale.x *= 0.96f; scale.z *= 0.96f; } Transform instanceTransform = new Transform(tile3D.GlobalTransform.basis.Rotated(Vector3.Up, Mathf.Deg2Rad(30)).Scaled(scale), tile3D.GlobalTransform.origin + Vector3.Up * -2.5f); _tileMultiMesh.Multimesh.SetInstanceTransform(instanceIndex, instanceTransform); } public void OnTileDeactivate(HexTile3D tile3D, Vector2 coord) { int instanceIndex = _tileToInstanceIndex[tile3D]; _tileMultiMesh.Multimesh.SetInstanceTransform(instanceIndex, _deactivatedTileTransform); tile3D.Type = HexTile3D.TileType.Undefined; _unusedTiles.Enqueue(tile3D); _coordToTile.Remove(coord); RemovedCoords.Add(coord); } public void OnTileClicked(HexTile3D tile) { EmitSignal("TileClicked", tile); } public void OnTileHovered(HexTile3D tile) { EmitSignal("TileHovered", tile); } public void OnWorldGenerated() { foreach (Spatial node in _activeTiles.GetChildren()) { HexTile3D tile = (HexTile3D)node; if (tile == null) { continue; } Transform tileTransform = tile.GlobalTransform; tileTransform.origin.y = TileWorld.GetHeightAtOffset(tile.OffsetCoords); tile.GlobalTransform = tileTransform; OnTileActivate(tile); } } }