using Dictionary = Godot.Collections.Dictionary; public readonly record struct TilePos(int X, int Y) { public TilePos GetNeighbor(Side side) => side switch { Side.Left => new(X - 1, Y), Side.Top => new(X, Y - 1), Side.Right => new(X + 1, Y), Side.Bottom => new(X, Y + 1), _ => throw new ArgumentException($"Invalid Side value '{side}'", nameof(side)), }; public TilePos GetNeighbor(Corner corner) => corner switch { Corner.TopLeft => new(X - 1, Y - 1), Corner.TopRight => new(X + 1, Y - 1), Corner.BottomRight => new(X + 1, Y + 1), Corner.BottomLeft => new(X - 1, Y + 1), _ => throw new ArgumentException($"Invalid Corner value '{corner}'", nameof(corner)), }; public TilePos Offset(Vector2I value) => Offset(value.X, value.Y); public TilePos Offset(int x, int y) => new(X + x, Y + y); public static TilePos From(Vector2I value) => new(value.X, value.Y); public static TilePos From(Vector2 value) => new(FloorToInt(value.X), FloorToInt(value.Y)); public Vector2I ToVector2I() => new(X, Y); public Vector2 ToCenter() => new(X + 0.5f, Y + 0.5f); } public struct Tile { public Corners Height; // TODO: Replace with enum or something more permanent? public int TexturePrimary; public int TextureSecondary; public int TextureBlend; public static Tile FromDictionary(Dictionary dict) { if (dict == null) return default; float topLeft, topRight, bottomRight, bottomLeft; switch (dict["heights"]) { case { VariantType: Variant.Type.Float } variant: var height = (float)variant; (topLeft, topRight, bottomRight, bottomLeft) = (height, height, height, height); break; case { VariantType: Variant.Type.PackedFloat32Array } variant: var heights = (float[])variant; (topLeft, topRight, bottomRight, bottomLeft) = (heights[0], heights[1], heights[2], heights[3]); break; default: throw new Exception("Invalid variant type"); }; int texturePrimary, textureSecondary, textureBlend; switch (dict["texture"]) { case { VariantType: Variant.Type.Int } variant: var texture = (int)variant; (texturePrimary, textureSecondary, textureBlend) = (texture, 0, 0); break; case { VariantType: Variant.Type.PackedInt32Array } variant: var textures = (int[])variant; (texturePrimary, textureSecondary, textureBlend) = (textures[0], textures[1], textures[2]); break; default: throw new Exception("Invalid variant type"); }; return new(){ Height = new(topLeft, topRight, bottomRight, bottomLeft), TexturePrimary = texturePrimary, TextureSecondary = textureSecondary, TextureBlend = textureBlend, }; } public readonly Dictionary ToDictionary() { if (Height.IsZeroApprox() && (TexturePrimary == 0) && (TextureBlend == 0)) return null; return new(){ ["heights"] = Height.IsEqualApprox() ? Height.TopLeft : new[]{ Height.TopLeft, Height.TopRight, Height.BottomRight, Height.BottomLeft }, ["texture"] = (TextureBlend == 0) ? TexturePrimary : new[]{ TexturePrimary, TextureSecondary, TextureBlend }, }; } } public struct Corners(T topLeft, T topRight, T bottomRight, T bottomLeft) { public T TopLeft = topLeft; public T TopRight = topRight; public T BottomRight = bottomRight; public T BottomLeft = bottomLeft; public Corners(T value) : this(value, value, value, value) { } public T this[Corner corner] { readonly get => corner switch { Corner.TopLeft => TopLeft, Corner.TopRight => TopRight, Corner.BottomRight => BottomRight, Corner.BottomLeft => BottomLeft, _ => throw new ArgumentException($"Invalid Corner value '{corner}'", nameof(corner)), }; set { switch (corner) { case Corner.TopLeft : TopLeft = value; break; case Corner.TopRight : TopRight = value; break; case Corner.BottomRight : BottomRight = value; break; case Corner.BottomLeft : BottomLeft = value; break; default: throw new ArgumentException($"Invalid Corner value '{corner}'", nameof(corner)); } } } public readonly void Deconstruct( out T topLeft , out T topRight, out T bottomRight, out T bottomLeft) { topLeft = TopLeft; topRight = TopRight; bottomRight = BottomRight; bottomLeft = BottomLeft; } } public static class CornersExtensions { public static void Adjust(this ref Corners self, float amount) { self.TopLeft += amount; self.TopRight += amount; self.BottomRight += amount; self.BottomLeft += amount; } public static bool IsZeroApprox(this Corners self) => Mathf.IsZeroApprox(self.TopLeft ) && Mathf.IsZeroApprox(self.TopRight ) && Mathf.IsZeroApprox(self.BottomRight) && Mathf.IsZeroApprox(self.BottomLeft); public static bool IsEqualApprox(this Corners self) => Mathf.IsEqualApprox(self.TopLeft, self.TopRight ) && Mathf.IsEqualApprox(self.TopLeft, self.BottomRight) && Mathf.IsEqualApprox(self.TopLeft, self.BottomLeft ); }