Files
VoxelGame/src/voxelgame/Voxels/Chunks/IVoxelChunk.cs
2026-04-04 13:04:05 -04:00

193 lines
6.5 KiB
C#

using System.Collections.Generic;
using Godot;
using SJK.Functional;
using System.Linq;
namespace SJK.Voxels;
public interface IVoxelChunk<TVoxel> : IReadOnlyVoxelChunk<TVoxel>
{
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<TVoxel, TChunk> : IVoxelChunk<TVoxel>, IReadOnlyVoxelChunk<TVoxel,TChunk> where TChunk : IChunkSize<TChunk>
{
void SetVoxel(VoxelPos<TChunk> position, TVoxel voxel);
}
// public interface IVoxelChunk : IReadOnlyVoxelChunk
// {
// }
// public interface IReadOnlyVoxelChunk
// {
// }
public interface IReadOnlyVoxelChunk<TVoxel>// : IReadOnlyVoxelChunk
{
TVoxel GetVoxel(VoxelPos voxelPos);
VoxelChunkEnumerator<TVoxel> GetEnumerator() => new VoxelChunkEnumerator<TVoxel>(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<TVoxel, TChunk> : IReadOnlyVoxelChunk<TVoxel> where TChunk : IChunkSize<TChunk>
{
TVoxel GetVoxel(VoxelPos<TChunk> position);
bool IsInBounds(Vector3I position) => IChunkSize<TChunk>.IsInBounds(position);
virtual bool ContainsPos(VoxelPos<TChunk> position) => Bounds.IsInBounds((Vector3I)position);
new VoxelChunkEnumerator<TVoxel,TChunk> GetEnumerator() => new VoxelChunkEnumerator<TVoxel,TChunk>(this);
}
public static class VoxelExtensions{
public static void SetVoxel<TVoxel>(this IVoxelChunk<TVoxel> voxelChunk, int x, int y, int z, TVoxel value) =>voxelChunk.SetVoxel(new VoxelPos(x,y,z),value);
public static TVoxel GetVoxel<TVoxel>(this IVoxelChunk<TVoxel> voxelChunk, int x, int y, int z) =>voxelChunk.GetVoxel(new VoxelPos(x,y,z));
}
public interface ILayerMap<TVoxel> {
bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk<TVoxel> chunk); // Untyped
void SetChunk(Vector3I chunkPos, IVoxelChunk<TVoxel> chunk);
}
public class LayerMap<TVoxel> : ILayerMap<TVoxel> {
private readonly Dictionary<Vector3I, IVoxelChunk<TVoxel>> chunks = new();
public bool TryGetChunk(Vector3I chunkPos, out IVoxelChunk<TVoxel> chunk)
{
var r =chunks.TryGetValue(chunkPos, out var result);
chunk = result;
return r;
}
public void SetChunk(Vector3I chunkPos, IVoxelChunk<TVoxel> 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;
}