213 lines
7.2 KiB
C#
213 lines
7.2 KiB
C#
using OpenTK.Mathematics;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
|
|
namespace Voxel
|
|
{
|
|
public class World
|
|
{
|
|
private Dictionary<(int, int), Chunk> _chunks;
|
|
|
|
private static readonly Dictionary<Orientation, (int x, int y)> _neighborOffsets = new()
|
|
{
|
|
{ Orientation.West, (1, 0) },
|
|
{ Orientation.East, (-1, 0) },
|
|
{ Orientation.North, (0, 1) },
|
|
{ Orientation.South, (0, -1) }
|
|
};
|
|
|
|
public World()
|
|
{
|
|
_chunks = new Dictionary<(int, int), Chunk>();
|
|
}
|
|
|
|
public Chunk GetChunk(int chunkX, int chunkZ)
|
|
{
|
|
_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk);
|
|
return chunk;
|
|
}
|
|
|
|
public void AddChunk(Chunk chunk)
|
|
{
|
|
_chunks[(chunk.X, chunk.Y)] = chunk;
|
|
|
|
UpdateNeighboringChunks(chunk);
|
|
chunk.UpdateChunkMesh();
|
|
}
|
|
|
|
public void RemoveChunk(int chunkX, int chunkZ)
|
|
{
|
|
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
|
{
|
|
var neighbors = GetChunkNeighbors(chunk).ToList();
|
|
_chunks.Remove((chunkX, chunkZ));
|
|
|
|
// 3. For each neighbor, remove reference to this chunk
|
|
foreach (var (orientation, neighbor) in neighbors)
|
|
{
|
|
var opposite = GetOppositeOrientation(orientation);
|
|
neighbor.Neighbors[opposite] = null;
|
|
neighbor.UpdateChunkMesh();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateNeighboringChunks(Chunk chunk)
|
|
{
|
|
chunk.Neighbors[Orientation.West] = null;
|
|
chunk.Neighbors[Orientation.East] = null;
|
|
chunk.Neighbors[Orientation.North] = null;
|
|
chunk.Neighbors[Orientation.South] = null;
|
|
|
|
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
|
{
|
|
Orientation opposite = GetOppositeOrientation(orientation);
|
|
neighbor.Neighbors[opposite] = chunk;
|
|
chunk.Neighbors[orientation] = neighbor;
|
|
neighbor.UpdateChunkMesh();
|
|
}
|
|
}
|
|
|
|
private Orientation GetOppositeOrientation(Orientation orientation)
|
|
{
|
|
return orientation switch
|
|
{
|
|
Orientation.West => Orientation.East,
|
|
Orientation.East => Orientation.West,
|
|
Orientation.North => Orientation.South,
|
|
Orientation.South => Orientation.North,
|
|
_ => orientation
|
|
};
|
|
}
|
|
|
|
public IEnumerable<Chunk> GetAllChunks()
|
|
{
|
|
return _chunks.Values;
|
|
}
|
|
|
|
public Blocks GetBlock(int worldX, int worldY, int worldZ)
|
|
{
|
|
int chunkX = worldX / Chunk.Size;
|
|
int chunkZ = worldZ / Chunk.Size;
|
|
Chunk chunk = GetChunk(chunkX, chunkZ);
|
|
if (chunk == null) return 0; // air if chunk not loaded
|
|
|
|
int localX = worldX % Chunk.Size;
|
|
int localZ = worldZ % Chunk.Size;
|
|
|
|
return chunk.GetBlock(localX, worldY, localZ);
|
|
}
|
|
|
|
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
|
|
{
|
|
var orientations = new List<Orientation>();
|
|
|
|
if (localX == size - 1) orientations.Add(Orientation.West);
|
|
if (localX == 0) orientations.Add(Orientation.East);
|
|
if (localZ == size - 1) orientations.Add(Orientation.North);
|
|
if (localZ == 0) orientations.Add(Orientation.South);
|
|
|
|
return orientations;
|
|
}
|
|
|
|
public void SetBlock(int worldX, int worldY, int worldZ, Blocks block)
|
|
{
|
|
int chunkX = worldX / Chunk.Size;
|
|
int chunkZ = worldZ / Chunk.Size;
|
|
Chunk chunk = GetChunk(chunkX, chunkZ);
|
|
if (chunk == null) return;
|
|
|
|
int localX = worldX % Chunk.Size;
|
|
int localZ = worldZ % Chunk.Size;
|
|
|
|
chunk.SetBlock(localX, worldY, localZ, block);
|
|
|
|
if (block == Blocks.Air && ((localX == Chunk.Size - 1 || localX == 0) || (localZ == Chunk.Size - 1 || localZ == 0)))
|
|
{
|
|
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
|
|
{
|
|
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
|
|
{
|
|
neighbor.UpdateChunkMesh();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
|
|
{
|
|
int chunkX = chunk.X;
|
|
int chunkY = chunk.Y;
|
|
|
|
foreach (var kv in _neighborOffsets)
|
|
{
|
|
Chunk neighbor = GetChunk(chunkX + kv.Value.x, chunkY + kv.Value.y);
|
|
if (neighbor != null)
|
|
yield return (kv.Key, neighbor);
|
|
}
|
|
}
|
|
|
|
public (bool success, Blocks block, int x, int y, int z, Vector3i normal) Raycast(Vector3 origin, Vector3 direction, float maxDistance)
|
|
{
|
|
int x = (int)MathF.Floor(origin.X);
|
|
int y = (int)MathF.Floor(origin.Y);
|
|
int z = (int)MathF.Floor(origin.Z);
|
|
|
|
int stepX = direction.X > 0 ? 1 : -1;
|
|
int stepY = direction.Y > 0 ? 1 : -1;
|
|
int stepZ = direction.Z > 0 ? 1 : -1;
|
|
|
|
float tDeltaX = direction.X != 0 ? MathF.Abs(1 / direction.X) : float.MaxValue;
|
|
float tDeltaY = direction.Y != 0 ? MathF.Abs(1 / direction.Y) : float.MaxValue;
|
|
float tDeltaZ = direction.Z != 0 ? MathF.Abs(1 / direction.Z) : float.MaxValue;
|
|
|
|
float tMaxX = direction.X > 0 ? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX
|
|
: (origin.X - MathF.Floor(origin.X)) * tDeltaX;
|
|
float tMaxY = direction.Y > 0 ? (MathF.Floor(origin.Y) + 1 - origin.Y) * tDeltaY
|
|
: (origin.Y - MathF.Floor(origin.Y)) * tDeltaY;
|
|
float tMaxZ = direction.Z > 0 ? (MathF.Floor(origin.Z) + 1 - origin.Z) * tDeltaZ
|
|
: (origin.Z - MathF.Floor(origin.Z)) * tDeltaZ;
|
|
|
|
float distance = 0f;
|
|
Vector3i normal = Vector3i.Zero;
|
|
|
|
while (distance <= maxDistance)
|
|
{
|
|
Blocks block = GetBlock(x, y, z);
|
|
|
|
if (block != Blocks.Air)
|
|
return (true, block, x, y, z, normal);
|
|
|
|
if (tMaxX < tMaxY && tMaxX < tMaxZ)
|
|
{
|
|
x += stepX;
|
|
normal = new Vector3i(-stepX, 0, 0);
|
|
distance = tMaxX;
|
|
tMaxX += tDeltaX;
|
|
}
|
|
else if (tMaxY < tMaxZ)
|
|
{
|
|
y += stepY;
|
|
normal = new Vector3i(0, -stepY, 0);
|
|
distance = tMaxY;
|
|
tMaxY += tDeltaY;
|
|
}
|
|
else
|
|
{
|
|
z += stepZ;
|
|
normal = new Vector3i(0, 0, -stepZ);
|
|
distance = tMaxZ;
|
|
tMaxZ += tDeltaZ;
|
|
}
|
|
}
|
|
|
|
return (false, Blocks.Air, 0, 0, 0, Vector3i.Zero);
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|