using OpenTK.Mathematics; using System.Collections.Generic; using System.Drawing; namespace Voxel { public class World { private Dictionary<(int, int), Chunk> _chunks; 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; Dictionary oppositeOrientation = new() { {Orientation.West, Orientation.East}, {Orientation.East, Orientation.West}, {Orientation.North, Orientation.South}, {Orientation.South, Orientation.North}, }; foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk)) { neighbor.Neighbors[oppositeOrientation[orientation]] = chunk; chunk.Neighbors[orientation] = neighbor; neighbor.UpdateChunkMesh(); } chunk.UpdateChunkMesh(); } public void RemoveChunk(int chunkX, int chunkZ) { _chunks.Remove((chunkX, chunkZ)); } public IEnumerable 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 GetEdgeOrientations(int localX, int localZ, int size) { var orientations = new List(); 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 == 15 || localX == 0) || (localZ == 15 || 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) { Dictionary offsets = new() { { Orientation.West, (1, 0) }, { Orientation.East, (-1, 0) }, { Orientation.North, (0, 1) }, { Orientation.South, (0, -1) } }; foreach (var kv in offsets) { int nx = chunk.X + kv.Value.x; int ny = chunk.Y + kv.Value.y; Chunk neighbor = GetChunk(nx, ny); 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() { } } }