GodotComponentTest/scenes/World.cs

777 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Godot;
using Godot.Collections;
using Priority_Queue;
public class World : Spatial {
public enum GenerationState {
Empty,
Heightmap,
TileType,
Objects,
Done
}
// constants
public int ChunkSize = 14;
public const int NumChunkRows = 3;
public const int NumChunkColumns = NumChunkRows;
private readonly System.Collections.Generic.Dictionary<int, HexTile3D.TileTypeInfo> _tileTypeInfos = new();
private readonly Godot.Collections.Dictionary<Vector2, WorldChunk> _cachedWorldChunks;
private readonly List<Vector2> _addedChunkIndices = new();
private readonly List<WorldChunk> _deactivatedWorldChunks = new();
private readonly Image _heightmapImage = new();
private readonly List<Vector2> _removedChunkIndices = new();
private readonly Image _tileTypeMapImage = new();
// referenced scenes
private readonly PackedScene _worldChunkScene = GD.Load<PackedScene>("res://scenes/WorldChunk.tscn");
private List<Vector2> _activeChunkIndices = new();
private Rect2 _centerChunkRect;
private readonly List<Spatial> _removedSpatialNodes = new();
// delegate void OnCoordClicked(Vector2 world_pos);
// other members
private Vector2 _chunkIndexNorthEast;
private Vector2 _chunkIndexSouthWest;
private Array<Spatial> _grassAssets;
private ImageTexture _heightmapTexture;
private OpenSimplexNoise _noiseGenerator = new();
private Array<Spatial> _rockAssets;
private MultiMeshInstance _tileMultiMeshInstance;
private int _usedTileMeshInstances;
private Array<Spatial> _treeAssets;
private ImageTexture _viewTileTypeTexture;
public Vector2 CenterChunkIndex = Vector2.Zero;
public Spatial Chunks;
public Color DebugColor;
public HexGrid HexGrid = new();
public int Seed = 0;
public GenerationState State = GenerationState.Empty;
public Vector2 WorldTextureCoordinateOffset;
// ui elements
// scene nodes
// resources
// exports
// [Export] public Vector2 Size = new Vector2(1, 1);
// signals
[Signal]
private delegate void OnTilesChanged(Array<Vector2> removedChunkIndices, Array<Vector2> addedChunkIndices);
[Signal]
private delegate void OnWorldViewTileTypeImageChanged(Image viewTileTypeImage);
[Signal]
private delegate void OnHeightmapImageChanged(Image heightmapImage);
[Signal]
public delegate void EntityClicked(Entity entity);
// signals
[Signal]
private delegate void TileClicked(HexTile3D tile3d);
[Signal]
private delegate void TileHovered(HexTile3D tile3d);
public World() {
Debug.Assert(ChunkSize % 2 == 0);
_cachedWorldChunks = new Godot.Collections.Dictionary<Vector2, WorldChunk>();
}
// Called when the node enters the scene tree for the first time.
public override void _Ready() {
Chunks = (Spatial)FindNode("Chunks");
Debug.Assert(Chunks != null);
_tileMultiMeshInstance = (MultiMeshInstance)FindNode("TileMultiMeshInstance");
Debug.Assert(_tileMultiMeshInstance != null);
InitNoiseGenerator();
GetNode<Spatial>("Assets").Visible = false;
_rockAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Rocks").GetChildren()) {
_rockAssets.Add(asset);
}
_grassAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Grass").GetChildren()) {
_grassAssets.Add(asset);
}
_treeAssets = new Array<Spatial>();
foreach (Spatial asset in GetNode<Node>("Assets/Trees").GetChildren()) {
_treeAssets.Add(asset);
}
_tileTypeInfos[0] = new HexTile3D.TileTypeInfo("Undefined", Colors.Black, 0xffff);
LoadTileTypeInfo("DeepWater", (int)Entity.EntityMaskEnum.Water);
LoadTileTypeInfo("Water", (int)Entity.EntityMaskEnum.Water);
LoadTileTypeInfo("LightWater", (int)Entity.EntityMaskEnum.Water);
LoadTileTypeInfo("Sand", (int)Entity.EntityMaskEnum.Ground);
LoadTileTypeInfo("Grass", (int)Entity.EntityMaskEnum.Ground);
LoadTileTypeInfo("Forest", (int)Entity.EntityMaskEnum.Ground);
LoadTileTypeInfo("Rock", (int)Entity.EntityMaskEnum.Ground);
LoadTileTypeInfo("Sand", (int)Entity.EntityMaskEnum.Ground);
}
private void LoadTileTypeInfo(string tileTypeName, ushort tileTypeMask) {
ShaderMaterial worldTileTypeShaderMaterial =
GD.Load<ShaderMaterial>("res://materials/WorldTileTypeMaterial.tres");
Color tileTypeColor = (Color)worldTileTypeShaderMaterial.GetShaderParam(tileTypeName + "Color");
_tileTypeInfos[tileTypeColor.ToRgba32()] =
new HexTile3D.TileTypeInfo(tileTypeName, tileTypeColor, tileTypeMask);
}
public void InitNoiseGenerator() {
_noiseGenerator = new OpenSimplexNoise();
_noiseGenerator.Seed = Seed;
_noiseGenerator.Octaves = 1;
_noiseGenerator.Period = 10;
_noiseGenerator.Persistence = 0.5f;
_noiseGenerator.Lacunarity = 2;
}
public void Reset() {
foreach (Spatial chunkChild in Chunks.GetChildren()) {
chunkChild.QueueFree();
}
// foreach (WorldChunk chunk in _cachedWorldChunks.Values) {
// chunk.QueueFree();
// }
_cachedWorldChunks.Clear();
_addedChunkIndices.Clear();
_tileMultiMeshInstance.Multimesh.InstanceCount =
ChunkSize * ChunkSize * NumChunkColumns * NumChunkRows;
_usedTileMeshInstances = 0;
State = GenerationState.Empty;
}
public WorldChunk GetOrCreateWorldChunk(Vector2 chunkIndex, Color debugColor) {
WorldChunk chunk;
if (IsChunkCached(chunkIndex)) {
return _cachedWorldChunks[chunkIndex];
}
if (_deactivatedWorldChunks.Count > 0) {
chunk = _deactivatedWorldChunks.First();
_deactivatedWorldChunks.RemoveAt(0);
} else {
chunk = CreateWorldChunk(chunkIndex, debugColor);
}
_cachedWorldChunks[chunkIndex] = chunk;
return chunk;
}
private bool IsChunkCached(Vector2 chunkIndex) {
return _cachedWorldChunks.ContainsKey(chunkIndex);
}
private WorldChunk CreateWorldChunk(Vector2 chunkIndex, Color debugColor) {
WorldChunk result = (WorldChunk)_worldChunkScene.Instance();
result.SetSize(ChunkSize);
Chunks.AddChild(result);
result.Connect("TileClicked", this, nameof(OnTileClicked));
result.Connect("TileHovered", this, nameof(OnTileHovered));
result.SetSize(ChunkSize);
result.InitializeTileInstances(chunkIndex, _tileMultiMeshInstance, _usedTileMeshInstances);
_usedTileMeshInstances += result.Tiles.GetChildCount();
result.SetChunkIndex(chunkIndex, HexGrid);
result.UpdateTileTransforms();
result.DebugColor = debugColor;
result.DebugColor.a = 0.6f;
_cachedWorldChunks.Add(chunkIndex, result);
return result;
}
private bool IsColorEqualApprox(Color colorA, Color colorB) {
Vector3 colorDifference = new(colorA.r - colorB.r, colorA.g - colorB.g, colorA.b - colorB.b);
return colorDifference.LengthSquared() < 0.1 * 0.1;
}
private Spatial SelectAsset(Vector2 textureCoord, 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 = HexGrid.GetHexCenterVec3FromOffset(textureCoord);
// TODO: assetTransform.origin.y = GetHeightAtOffset(offsetCoord);
assetTransform.origin.y = 0;
assetTransform.basis =
assetTransform.basis.Rotated(Vector3.Up, (float)(randomGenerator.NextDouble() * Mathf.Pi * 2));
assetInstance.Transform = assetTransform;
return assetInstance;
}
private void PopulateChunk(WorldChunk chunk) {
Random environmentRandom = new(Seed);
chunk.CreateUnlockedTileTypeImage();
foreach (int textureCoordU in Enumerable.Range(0, chunk.Size)) {
foreach (int textureCoordV in Enumerable.Range(0, chunk.Size)) {
Vector2 textureCoord = new(textureCoordU, textureCoordV);
Vector2 offsetCoord = chunk.ChunkIndex * ChunkSize + textureCoord;
HexTile3D.TileTypeInfo tileTypeInfo = GetTileTypeInfoAtOffset(offsetCoord);
if (tileTypeInfo.Name == "Rock") {
Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.15);
if (rockAsset != null) {
chunk.Entities.AddChild(rockAsset);
MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord));
}
} else if (tileTypeInfo.Name == "Grass" || tileTypeInfo.Name == "DarkGrass") {
Spatial grassAsset = SelectAsset(textureCoord, _grassAssets, environmentRandom, 0.15);
if (grassAsset != null) {
chunk.Entities.AddChild(grassAsset);
}
Tree treeAsset = SelectAsset(textureCoord, _treeAssets, environmentRandom, 0.05) as Tree;
if (treeAsset != null) {
chunk.Entities.AddChild(treeAsset);
treeAsset.Connect("EntityClicked", this, nameof(OnEntityClicked));
treeAsset.Connect("TreeChopped", this, nameof(OnBlockingSpatialRemoved));
MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord));
}
// TODO: MarkCellUnwalkable(cell);
// else if (environmentRandom.NextDouble() < 0.01)
} else if (tileTypeInfo.Name == "Water" || tileTypeInfo.Name == "LightWater" ||
tileTypeInfo.Name == "DeepWater") {
Spatial rockAsset = SelectAsset(textureCoord, _rockAssets, environmentRandom, 0.01);
if (rockAsset != null) {
chunk.Entities.AddChild(rockAsset);
MarkCellUnwalkable(HexGrid.GetHexAtOffset(offsetCoord));
}
// {
// var chestAsset = (Chest)_chestScene.Instance();
// var 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);
// }
}
}
}
public Vector2 WorldToOffsetCoords(Vector3 fromPositionWorld) {
return HexGrid.GetHexAt(new Vector2(fromPositionWorld.x, fromPositionWorld.z)).OffsetCoords;
}
public Vector3 GetHexCenterFromOffset(Vector2 fromPositionOffset) {
return HexGrid.GetHexCenterVec3FromOffset(fromPositionOffset);
}
public void UpdateCenterChunkFromPlaneCoord(Vector2 planeCoord) {
if (State != GenerationState.Done && State != GenerationState.Empty) {
GD.PrintErr("Cannot update chunk to new planeCoord " + planeCoord + ": Chunk generation not yet finished!");
return;
}
// mark all chunks as retired
Godot.Collections.Dictionary<Vector2, WorldChunk> oldCachedChunks = new(_cachedWorldChunks);
// set new center chunk
CenterChunkIndex = GetChunkTupleFromPlaneCoord(planeCoord);
// load or create adjacent chunks
_activeChunkIndices = new List<Vector2>();
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, -1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, -1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(1, -1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, 0));
_activeChunkIndices.Add(CenterChunkIndex);
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, 0));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(-1, +1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(0, +1));
_activeChunkIndices.Add(CenterChunkIndex + new Vector2(+1, +1));
// clear unused chunks
_deactivatedWorldChunks.Clear();
_addedChunkIndices.Clear();
foreach (Vector2 oldChunkIndex in oldCachedChunks.Keys) {
if (!_activeChunkIndices.Contains(oldChunkIndex)) {
DeactivateChunk(oldCachedChunks[oldChunkIndex]);
}
}
foreach (Vector2 activeChunkIndex in _activeChunkIndices) {
WorldChunk chunk = GetOrCreateWorldChunk(activeChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
}
Debug.Assert(_activeChunkIndices.Count == NumChunkRows * NumChunkColumns);
foreach (Vector2 chunkKey in _activeChunkIndices) {
if (!oldCachedChunks.ContainsKey(chunkKey)) {
ActivateChunk(_cachedWorldChunks[chunkKey], chunkKey);
State = GenerationState.Heightmap;
}
}
WorldChunk currentChunk = GetOrCreateWorldChunk(CenterChunkIndex,
new Color(GD.Randf(), GD.Randf(), GD.Randf()));
_centerChunkRect = new Rect2(
new Vector2(currentChunk.Transform.origin.x, currentChunk.Transform.origin.z)
+ currentChunk.PlaneRect.Position,
currentChunk.PlaneRect.Size);
UpdateChunkBounds();
UpdateNavigationBounds();
}
private void ActivateChunk(WorldChunk chunk, Vector2 chunkIndex) {
chunk.SetChunkIndex(chunkIndex, HexGrid);
chunk.UpdateTileTransforms();
_addedChunkIndices.Add(chunk.ChunkIndex);
GenerateChunkNoiseMap(chunk);
}
private void DeactivateChunk(WorldChunk chunk) {
_cachedWorldChunks.Remove(chunk.ChunkIndex);
chunk.ClearContent();
_deactivatedWorldChunks.Add(chunk);
}
private void GenerateChunkNoiseMap(WorldChunk chunk) {
Vector2 chunkIndex = chunk.ChunkIndex;
ImageTexture noiseImageTexture = new();
noiseImageTexture.CreateFromImage(_noiseGenerator.GetImage(ChunkSize, ChunkSize, chunkIndex * ChunkSize),
0);
chunk.SetNoisemap(noiseImageTexture);
}
private void RemoveChunk(Vector2 cachedChunkKey) {
_cachedWorldChunks.Remove(cachedChunkKey);
_removedChunkIndices.Add(cachedChunkKey);
foreach (WorldChunk chunk in Chunks.GetChildren()) {
if (chunk.ChunkIndex == new Vector2(cachedChunkKey.x, cachedChunkKey.y)) {
chunk.QueueFree();
}
}
}
private Vector2 GetChunkTupleFromPlaneCoord(Vector2 planeCoord) {
HexCell hexCell = HexGrid.GetHexAt(planeCoord);
return GetChunkIndexFromOffsetCoord(hexCell.OffsetCoords);
}
private Vector2 GetChunkIndexFromOffsetCoord(Vector2 offsetCoord) {
return (offsetCoord / ChunkSize).Floor();
}
public void SetCenterPlaneCoord(Vector2 centerPlaneCoord) {
if (State != GenerationState.Done) {
return;
}
if (!_centerChunkRect.HasPoint(centerPlaneCoord)) {
UpdateCenterChunkFromPlaneCoord(centerPlaneCoord);
}
}
private void UpdateWorldViewTexture() {
int worldChunkSize = ChunkSize;
int numWorldChunkRows = NumChunkRows;
int numWorldChunkColumns = NumChunkColumns;
_heightmapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8);
_tileTypeMapImage.Create(worldChunkSize * numWorldChunkColumns, worldChunkSize * numWorldChunkRows, false,
Image.Format.Rgba8);
foreach (Vector2 chunkIndex in _activeChunkIndices) {
WorldChunk worldChunk = GetOrCreateWorldChunk(chunkIndex, Colors.White);
_heightmapImage.BlendRect(
worldChunk.HeightmapOffscreenViewport.GetTexture().GetData(),
new Rect2(Vector2.Zero, Vector2.One * worldChunkSize),
(chunkIndex - CenterChunkIndex + Vector2.One) * worldChunkSize);
_tileTypeMapImage.BlendRect(
worldChunk.TileTypeOffscreenViewport.GetTexture().GetData(),
new Rect2(Vector2.Zero, Vector2.One * worldChunkSize),
(chunkIndex - CenterChunkIndex + Vector2.One) * worldChunkSize);
}
_heightmapTexture = new ImageTexture();
_heightmapTexture.CreateFromImage(_heightmapImage);
_viewTileTypeTexture = new ImageTexture();
_viewTileTypeTexture.CreateFromImage(_tileTypeMapImage, (uint)Texture.FlagsEnum.ConvertToLinear);
WorldTextureCoordinateOffset = _chunkIndexSouthWest * worldChunkSize;
EmitSignal("OnWorldViewTileTypeImageChanged", _tileTypeMapImage);
EmitSignal("OnHeightmapImageChanged", _heightmapImage);
}
private void UpdateChunkBounds() {
_chunkIndexSouthWest = Vector2.Inf;
_chunkIndexNorthEast = -Vector2.Inf;
foreach (Vector2 chunkIndex in _activeChunkIndices) {
if (chunkIndex.x <= _chunkIndexSouthWest.x && chunkIndex.y <= _chunkIndexSouthWest.y) {
_chunkIndexSouthWest = chunkIndex;
} else if (chunkIndex.x >= _chunkIndexNorthEast.x && chunkIndex.y >= _chunkIndexNorthEast.y) {
_chunkIndexNorthEast = chunkIndex;
}
}
}
private void UpdateNavigationBounds() {
HexCell cellSouthWest = HexGrid.GetHexAtOffset(_chunkIndexSouthWest * ChunkSize);
HexGrid.SetBoundsOffset(cellSouthWest, ChunkSize * new Vector2(NumChunkColumns, NumChunkRows));
}
public void MarkCellUnwalkable(HexCell cell) {
HexGrid.AddObstacle(cell);
}
public float GetHexCost(Entity entity, HexCell cell) {
float nextHexCost = HexGrid.GetHexCost(cell);
if (nextHexCost != 0) {
Vector2 nextOffset = cell.OffsetCoords;
Color tileTypeColor = GetTileTypeColorAtOffset(nextOffset);
if (_tileTypeInfos.TryGetValue(tileTypeColor.ToRgba32(), out HexTile3D.TileTypeInfo info)) {
int tileTypeMask = info.TileTypeMask;
if ((entity.EntityMask ^ tileTypeMask) != 0) {
nextHexCost = 0;
}
} else {
GD.Print("Could not find tile type info for color " + tileTypeColor);
}
}
return nextHexCost;
}
private HexTile3D.TileTypeInfo GetTileTypeInfoAtOffset(Vector2 offsetCoord) {
Color tileTypeColor = GetTileTypeColorAtOffset(offsetCoord);
if (_tileTypeInfos.TryGetValue(tileTypeColor.ToRgba32(), out HexTile3D.TileTypeInfo info)) {
return info;
}
return _tileTypeInfos[0];
}
public Color GetTileTypeColorAtOffset(Vector2 offsetCoord) {
Vector2 chunkIndex = GetChunkIndexFromOffsetCoord(offsetCoord);
if (!_cachedWorldChunks.ContainsKey(chunkIndex)) {
return Colors.Black;
}
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
Vector2 textureCoordinate = offsetCoord - Vector2.One * ChunkSize * chunkIndex;
Color tileTypeColor = chunk.TileTypeImage.GetPixel((int)textureCoordinate.x, (int)textureCoordinate.y);
return tileTypeColor;
}
public float GetMoveCost(Entity entity, HexCell currentHex, HexCell nextHex) {
if (GetHexCost(entity, nextHex) == 0) {
return 0;
}
return HexGrid.GetMoveCost(currentHex.AxialCoords,
new HexCell(nextHex.AxialCoords - currentHex.AxialCoords).CubeCoords);
}
public List<HexCell> FindPath(Entity entity, HexCell startHex, HexCell goalHex) {
if (State != GenerationState.Done) {
return new List<HexCell>();
}
Vector2 goalAxialCoords = goalHex.AxialCoords;
SimplePriorityQueue<Vector2, float> frontier = new();
frontier.Enqueue(startHex.AxialCoords, 0);
System.Collections.Generic.Dictionary<Vector2, Vector2> cameFrom = new();
System.Collections.Generic.Dictionary<Vector2, float> costSoFar = new();
cameFrom.Add(startHex.AxialCoords, startHex.AxialCoords);
costSoFar.Add(startHex.AxialCoords, 0);
int FindPathCheckedCellCount = 0;
while (frontier.Any()) {
FindPathCheckedCellCount++;
HexCell currentHex = new(frontier.Dequeue());
Vector2 currentAxial = currentHex.AxialCoords;
if (currentHex == goalHex) {
break;
}
foreach (HexCell nextHex in currentHex.GetAllAdjacent()) {
Vector2 nextAxial = nextHex.AxialCoords;
float moveCost = GetMoveCost(entity, currentHex, nextHex);
if (nextHex == goalHex && moveCost == 0 && GetHexCost(entity, nextHex) == 0) {
// Goal ist an obstacle
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
frontier.Clear();
break;
}
if (moveCost == 0) {
continue;
}
moveCost += costSoFar[currentHex.AxialCoords];
if (!costSoFar.ContainsKey(nextHex.AxialCoords) || moveCost < costSoFar[nextHex.AxialCoords]) {
costSoFar[nextHex.AxialCoords] = moveCost;
float priority = moveCost + nextHex.DistanceTo(goalHex);
frontier.Enqueue(nextHex.AxialCoords, priority);
cameFrom[nextHex.AxialCoords] = currentHex.AxialCoords;
}
}
}
// GD.Print("Checked Cell Count: " + FindPathCheckedCellCount);
List<HexCell> result = new();
if (!cameFrom.ContainsKey(goalHex.AxialCoords)) {
GD.Print("Failed to find path from " + startHex + " to " + goalHex);
return result;
}
if (HexGrid.GetHexCost(goalAxialCoords) != 0) {
result.Add(goalHex);
}
HexCell pathHex = goalHex;
while (pathHex != startHex) {
pathHex = new HexCell(cameFrom[pathHex.AxialCoords]);
result.Insert(0, pathHex);
}
return result;
}
public bool CheckSweptTriangleCellCollision(Entity entity, Vector3 startWorld, Vector3 endWorld, float radius) {
Vector2 startPlane = new(startWorld.x, startWorld.z);
Vector2 endPlane = new(endWorld.x, endWorld.z);
Vector2 directionPlane = (endPlane - startPlane).Normalized();
Vector2 sidePlane = directionPlane.Rotated(Mathf.Pi * 0.5f);
List<HexCell> cells =
HexGrid.GetCellsForLine(startPlane + directionPlane * radius, endPlane + directionPlane * radius);
foreach (HexCell cell in cells) {
if (GetHexCost(entity, cell) == 0) {
return true;
}
}
cells = HexGrid.GetCellsForLine(startPlane + sidePlane * radius, endPlane + sidePlane * radius);
foreach (HexCell cell in cells) {
if (GetHexCost(entity, cell) == 0) {
return true;
}
}
cells = HexGrid.GetCellsForLine(startPlane - sidePlane * radius, endPlane - sidePlane * radius);
foreach (HexCell cell in cells) {
if (GetHexCost(entity, cell) == 0) {
return true;
}
}
return false;
}
public List<NavigationPoint> SmoothPath(Entity entity, List<NavigationPoint> navigationPoints) {
if (navigationPoints.Count <= 2) {
return navigationPoints;
}
Vector3 bodyGlobalTranslation = entity.GlobalTranslation;
List<NavigationPoint> smoothedPath = new();
int startIndex = 0;
int endIndex = navigationPoints.Count > 1 ? 1 : 0;
smoothedPath.Add(navigationPoints[startIndex]);
Vector3 startPoint = navigationPoints[startIndex].WorldPosition;
while (endIndex != navigationPoints.Count) {
Vector3 endPoint = navigationPoints[endIndex].WorldPosition;
if (CheckSweptTriangleCellCollision(entity, startPoint, endPoint, 0.27f)) {
if (endIndex - startIndex == 1) {
GD.Print("Aborting SmoothPath: input path passes through collision geometry.");
entity.GlobalTranslation = bodyGlobalTranslation;
return smoothedPath;
}
smoothedPath.Add(navigationPoints[endIndex - 1]);
startIndex = endIndex - 1;
startPoint = navigationPoints[startIndex].WorldPosition;
entity.GlobalTranslation = startPoint;
continue;
}
if (endIndex == navigationPoints.Count - 1) {
break;
}
endIndex += 1;
}
smoothedPath.Add(navigationPoints[endIndex]);
entity.GlobalTranslation = bodyGlobalTranslation;
return smoothedPath;
}
public override void _Process(float delta) {
GenerationState oldState = State;
UpdateGenerationState();
if (oldState != GenerationState.Done && State == GenerationState.Done) {
UpdateWorldViewTexture();
}
while (_removedSpatialNodes.Count > 0) {
GD.Print("Queueing deletion of " + _removedSpatialNodes[0]);
_removedSpatialNodes[0].QueueFree();
_removedSpatialNodes.RemoveAt(0);
}
}
private void UpdateGenerationState() {
if (State == GenerationState.Heightmap) {
int numChunksGeneratingHeightmap = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices) {
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.HeightMapFrameCount > 0) {
numChunksGeneratingHeightmap++;
}
}
if (numChunksGeneratingHeightmap == 0) {
// assign height map images
foreach (Vector2 chunkIndex in _addedChunkIndices) {
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
chunk.SetHeightmap(chunk.HeightmapOffscreenViewport.GetTexture());
}
State = GenerationState.TileType;
}
} else if (State == GenerationState.TileType) {
int numChunksGeneratingTileType = 0;
foreach (Vector2 chunkIndex in _addedChunkIndices) {
WorldChunk chunk = _cachedWorldChunks[chunkIndex];
if (chunk.TileTypeMapFrameCount > 0) {
numChunksGeneratingTileType++;
}
}
if (numChunksGeneratingTileType == 0) {
State = GenerationState.Objects;
}
} else if (State == GenerationState.Objects) {
// generate objects
foreach (Vector2 chunkIndex in _addedChunkIndices) {
PopulateChunk(_cachedWorldChunks[chunkIndex]);
}
_addedChunkIndices.Clear();
State = GenerationState.Done;
}
}
private void OnEntityClicked(Entity entity) {
EmitSignal("EntityClicked", entity);
}
public void OnTileClicked(HexTile3D tile) {
if (State != GenerationState.Done) {
return;
}
EmitSignal("TileClicked", tile);
}
public void OnTileHovered(HexTile3D tile) {
if (State != GenerationState.Done) {
return;
}
EmitSignal("TileHovered", tile);
HexTile3D.TileTypeInfo tileTypeInfo = GetTileTypeInfoAtOffset(tile.OffsetCoords);
}
public void OnBlockingSpatialRemoved(Spatial spatialNode) {
if (spatialNode.IsQueuedForDeletion()) {
return;
}
HexGrid.RemoveObstacle(HexGrid.GetHexAt(new Vector2(spatialNode.GlobalTranslation.x,
spatialNode.GlobalTranslation.z)));
_removedSpatialNodes.Add(spatialNode);
}
}