using System.Collections.Generic; using Godot; using SJK.Functional; using System.Linq; namespace SJK.Voxels; public interface IVoxelChunk : IReadOnlyVoxelChunk { void SetVoxel(VoxelPos voxelPos, TVoxel voxel); virtual void Fill(TVoxel voxel) { for (int x = Bounds.Min.X; x < this.Bounds.Max.X; x++) { for (int y = Bounds.Min.Y; y < this.Bounds.Max.Y; y++) { for (int z = Bounds.Min.Z; z < Bounds.Max.Z; z++) { SetVoxel(new VoxelPos(x,y,z),voxel); } } } } } public interface IVoxelChunk : IVoxelChunk, IReadOnlyVoxelChunk where TChunk : IChunkSize { void SetVoxel(VoxelPos position, TVoxel voxel); } // public interface IVoxelChunk : IReadOnlyVoxelChunk // { // } // public interface IReadOnlyVoxelChunk // { // } public interface IReadOnlyVoxelChunk// : IReadOnlyVoxelChunk { TVoxel GetVoxel(VoxelPos voxelPos); VoxelChunkEnumerator GetEnumerator() => new VoxelChunkEnumerator(this); virtual bool ContainsPos(VoxelPos position) => Bounds.IsInBounds((Vector3I)position); VoxelBounds Bounds {get;} } public readonly record struct VoxelBounds(Vector3I Min, Vector3I Max) { public Vector3I Size => Max-Min; public bool IsInBounds(Vector3I pos) => !(pos.X < Min.X || pos.Y < Min.Y || pos.Z < Min.Z || pos.X > Max.X || pos.Y > Max.Y || pos.Z > Max.Z); } public interface IReadOnlyVoxelChunk : IReadOnlyVoxelChunk where TChunk : IChunkSize { TVoxel GetVoxel(VoxelPos position); bool IsInBounds(Vector3I position) => IChunkSize.IsInBounds(position); virtual bool ContainsPos(VoxelPos position) => Bounds.IsInBounds((Vector3I)position); new VoxelChunkEnumerator GetEnumerator() => new VoxelChunkEnumerator(this); } public static class VoxelExtensions{ public static void SetVoxel(this IVoxelChunk voxelChunk, int x, int y, int z, TVoxel value) =>voxelChunk.SetVoxel(new VoxelPos(x,y,z),value); public static TVoxel GetVoxel(this IVoxelChunk voxelChunk, int x, int y, int z) =>voxelChunk.GetVoxel(new VoxelPos(x,y,z)); } public interface ILayerMap { bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk chunk); // Untyped void SetChunk(Vector3I chunkPos, IVoxelChunk chunk); } public class LayerMap : ILayerMap { private readonly Dictionary> chunks = new(); public bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk chunk) { var r =chunks.TryGetValue(chunkPos, out var result); chunk = result; return r; } public void SetChunk(Vector3I chunkPos, IVoxelChunk chunk) => chunks[chunkPos] = chunk; } public interface IWorldWrapper { Vector3I Wrap(Vector3I position); Vector3I GetBounds(); AxisMode AxisX { get; } AxisMode AxisY {get;} AxisMode AxisZ {get;} bool IsInsideBounds(Vector3I position); enum AxisMode : byte { Constrined, Infinate, Wrapping } } public sealed class LimitedSizeWorld(Vector3I size) : IWorldWrapper { private readonly Vector3I size = size; public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Constrined; public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined; public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Constrined; public Vector3I GetBounds() => size; public bool IsInsideBounds(Vector3I position) => position.X >= 0 && position.X < size.X && position.Y >= 0 && position.Y < size.Y && position.Z >= 0 && position.Z < size.Z; public Vector3I Wrap(Vector3I position) { return position; } } public sealed class EndlessSizeXZWorld(int maxHeight) : IWorldWrapper { public int MaxHeight { get; } = maxHeight; public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Infinate; public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined; public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Infinate; public Vector3I Wrap(Vector3I position) => position; public Vector3I GetBounds() => new Vector3I(0, MaxHeight, 0); public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < MaxHeight; } public sealed class ToroidalWrapping(Vector3I size) : IWorldWrapper { private readonly Vector3I size = size; public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Wrapping; public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined; public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Wrapping; public Vector3I GetBounds() => size; public Vector3I Wrap(Vector3I position) => new Vector3I(Mod(position.X, size.X), position.Y, Mod(position.Z, size.Z)); public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < size.Y; private int Mod(int a, int b) { int r = a % b; return r < 0 ? r + b : r; } } public sealed class MirroredZWrappingXOffset(Vector3I size) : IWorldWrapper { private readonly Vector3I size = size; private readonly int halfX = size.X / 2; public IWorldWrapper.AxisMode AxisX => IWorldWrapper.AxisMode.Wrapping; public IWorldWrapper.AxisMode AxisY => IWorldWrapper.AxisMode.Constrined; public IWorldWrapper.AxisMode AxisZ => IWorldWrapper.AxisMode.Wrapping; public Vector3I GetBounds() => size; public Vector3I Wrap(Vector3I position) { var result = TwistedWrapInt(new Vector2I(position.X, position.Z), size.X, size.Z); return new(result.local.X, position.Y, result.local.Y); } private (Vector2I local, bool flipped) TwistedWrapInt(Vector2I global, int W, int H) { int gx = global.X; int gy = global.Y; int yWrap = FloorDiv(gy, H); bool flip = (yWrap & 1) != 0; int tileY = gy % H; if (tileY < 0) tileY += H; int ly = flip ? (H - 1 - tileY) : tileY; int xOffset = flip ? W / 2 : 0; int wrappedX = gx + xOffset; int tileX = wrappedX % W; if (tileX < 0) tileX += W; int lx = flip ? (W - 1 - tileX) : tileX; return (new Vector2I(lx, ly), flip); } private int FloorDiv(int a, int b) { return (a >= 0) ? a / b : ((a + 1) / b) - 1; } public bool IsInsideBounds(Vector3I position) => position.Y >= 0 && position.Y < size.Y; }