diff --git a/Blocks.cs b/Blocks.cs index f6dbfac..fb41c05 100644 --- a/Blocks.cs +++ b/Blocks.cs @@ -2,8 +2,11 @@ { public enum Blocks : byte { - Air = 0, - Stone = 1, + Air, + Stone, + Dirt, + OakPlanks, + Grass } public enum Orientation : uint @@ -16,8 +19,58 @@ South = 5, // - Z } - public enum Texture : uint + public class BlockDefinition { - Stone = 1 + public Blocks BlockType; + public Textures[] FaceTextures; + + public BlockDefinition(Blocks type, Textures singleTexture) + { + BlockType = type; + FaceTextures = new Voxel.Textures[6]; + for (int i = 0; i < 6; i++) FaceTextures[i] = singleTexture; + } + + public BlockDefinition( + Blocks type, + Textures west, + Textures east, + Textures top, + Textures bottom, + Textures north, + Textures south + ) + { + BlockType = type; + FaceTextures = new Textures[] + { + west, east, top, bottom, north, south + }; + } + } + + public static class BlockDefinitions + { + public static readonly Dictionary Blocks; + + static BlockDefinitions() + { + Blocks = new Dictionary + { + { Voxel.Blocks.Stone, new BlockDefinition(Voxel.Blocks.Stone, Voxel.Textures.Stone) }, + { Voxel.Blocks.Dirt, new BlockDefinition(Voxel.Blocks.Dirt, Voxel.Textures.Dirt) }, + { Voxel.Blocks.OakPlanks, new BlockDefinition(Voxel.Blocks.OakPlanks, Voxel.Textures.OakPlanks) }, + + { Voxel.Blocks.Grass, new BlockDefinition( + Voxel.Blocks.Grass, + Voxel.Textures.GrassSide, // West + Voxel.Textures.GrassSide, // East + Voxel.Textures.GrassTop, // Top + Voxel.Textures.Dirt, // Bottom + Voxel.Textures.GrassSide, // North + Voxel.Textures.GrassSide // South + )}, + }; + } } } diff --git a/Camera.cs b/Camera.cs index f1ee3cf..e3d7f72 100644 --- a/Camera.cs +++ b/Camera.cs @@ -4,11 +4,11 @@ namespace Voxel { static class Camera { - public static Vector3 Position; + public static Vector3 Position = new Vector3(-8, 16, -8); - public static float Pitch = 0f; - public static float Yaw = -90f; - public static float FOV = 70f; + public static float Pitch = -22.5f; + public static float Yaw = 45f; + public static float FOV = 60f; public static float Speed = 5f; public static float ShiftSpeed = 20f; @@ -56,18 +56,21 @@ namespace Voxel public static void UpdateMouse(Vector2 delta) { - float fov = MathHelper.DegreesToRadians(60f); - float aspectRatio = 800f / 600f; - float near = 0.1f; - float far = 100f; - - projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far); - float sensitivity = 0.1f; Yaw += delta.X * sensitivity; Pitch -= delta.Y * sensitivity; Pitch = MathHelper.Clamp(Pitch, -89f, 89f); } + + public static void UpdateProjection(int width, int height) + { + float fov = MathHelper.DegreesToRadians(FOV); + float aspectRatio = width / (float)height; + float near = 0.1f; + float far = 1000f; + + projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far); + } } } diff --git a/Chunk.cs b/Chunk.cs index ca7d4d9..9528265 100644 --- a/Chunk.cs +++ b/Chunk.cs @@ -8,7 +8,7 @@ public readonly int PositionY; private Dictionary _blockData; - private byte[] _blocks; + private Blocks[] _blocks; public Chunk(int positionX, int positionY) { @@ -16,18 +16,23 @@ PositionY = positionY; _blockData = new Dictionary(); - _blocks = new byte[Size * Size * Height]; + _blocks = new Blocks[Size * Size * Height]; Initialize(); } - public void SetBlock(int x, int y, int z, byte blockId) + public void SetBlock(int x, int y, int z, Blocks block) { int i = GetBlockIndex(x, y, z); - _blocks[i] = blockId; + _blocks[i] = block; } - public byte GetBlock(int x, int y, int z) + public void SetBlockIndex(int i, Blocks block) + { + _blocks[i] = block; + } + + public Blocks GetBlock(int x, int y, int z) { int i = GetBlockIndex(x, y, z); return _blocks[i]; @@ -40,23 +45,9 @@ private void Initialize() { - for (int x = 0; x < Size; x++) - { - for (int z = 0; z < Size; z++) - { - for (int y = 0; y < Size; y++) - { - byte blockId = 1; - SetBlock(x, y, z, blockId); - Console.WriteLine( - "Set block " - + x.ToString() + ", " - + y.ToString() + ", " - + z.ToString() + " " - ); - } - } - } + Random rng = new Random(); + for (int i = 0; i < Size * Size * 16; i++) + _blocks[i] = (Blocks)rng.Next(5); } // todo @@ -71,60 +62,25 @@ private int GetBlockIndex(int x, int y, int z) { - return x + y * Size + z * Size * Size; + return x + z * Size + y * Size * Size; } - public List GetFaces() + private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6] { - List list = new List(); - - for (byte i = 0; i < 6; i++) - { - FaceData faceData = new FaceData(); - - faceData.Facing = (Orientation)i; - - list.Add(faceData); - } - - for (byte i = 0; i < 6; i++) - { - FaceData faceData = new FaceData(); - - faceData.Facing = (Orientation)i; - - faceData.X = 1; - - list.Add(faceData); - } - - for (byte i = 0; i < 6; i++) - { - FaceData faceData = new FaceData(); - - faceData.Facing = (Orientation)i; + ( 1, 0, 0), // +X + (-1, 0, 0), // -X + ( 0, 1, 0), // +Y + ( 0, -1, 0), // -Y + ( 0, 0, 1), // +Z + ( 0, 0, -1) // -Z + }; - faceData.X = 1; - faceData.Y = 1; - faceData.Z = 1; + public ChunkMesh GetChunkMesh() + { + ChunkMesh chunkMesh = new ChunkMesh(Size * Size * Height / 2); - list.Add(faceData); - } - - for (byte i = 0; i < 6; i++) - { - FaceData faceData = new FaceData(); - - faceData.Facing = (Orientation)i; - - - faceData.X = 1; - faceData.Y = 2; - faceData.Z = 1; - - list.Add(faceData); - } + // offsets table for (int x = 0; x < Size; x++) { @@ -132,23 +88,40 @@ { for (int y = 0; y < Height; y++) { - for (byte i = 0; i < 6; i++) + for (byte face = 0; face < 6; face++) { + int indexBase = y * Size * Size + z * Size + x; + Blocks block = _blocks[indexBase]; + + if (block == Blocks.Air) continue; // ignore if air + + int nx = x + Offsets[face].dx; + int ny = y + Offsets[face].dy; + int nz = z + Offsets[face].dz; + + // check neighbor, ignore if at chunk edge + if (nx >= 0 && nx < Size && + ny >= 0 && ny < Height && + nz >= 0 && nz < Size && + _blocks[GetBlockIndex(nx, ny, nz)] != 0) + continue; + FaceData faceData = new FaceData(); - faceData.Facing = (Orientation)i; + faceData.Facing = (Orientation)face; + faceData.Texture = BlockDefinitions.Blocks[block].FaceTextures[face]; faceData.X = (byte)x; faceData.Y = (byte)y; faceData.Z = (byte)z; - //list.Add(faceData); + chunkMesh.Faces.Add(faceData); } } } } - return list; + return chunkMesh; } } } diff --git a/ChunkMesh.cs b/ChunkMesh.cs index 21eafcf..bf15bb6 100644 --- a/ChunkMesh.cs +++ b/ChunkMesh.cs @@ -1,17 +1,18 @@ -namespace Voxel +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Voxel { public class ChunkMesh { - private List _faces; + public List Faces; - public ChunkMesh(Chunk chunk) + public ChunkMesh(int maxBlocks) { - _faces = new List(); - } - - private void MeshBlock(int i) - { - + Faces = new List(maxBlocks * 6); } } } diff --git a/ChunkMesher.cs b/ChunkMesher.cs deleted file mode 100644 index 51f9e31..0000000 --- a/ChunkMesher.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Voxel -{ - static class ChunkMesher - { - static ChunkMesh MeshChunk(Chunk chunk) - { - ChunkMesh chunkMesh = new ChunkMesh(chunk); - - return chunkMesh; - } - } -} diff --git a/FaceData.cs b/FaceData.cs index 30420e5..81ba00d 100644 --- a/FaceData.cs +++ b/FaceData.cs @@ -2,22 +2,24 @@ namespace Voxel { - [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FaceData { public Orientation Facing; - public uint X, Y, Z; - public Texture Texture; + public byte X, Y, Z; + public Textures Texture; + public byte LightLevel; - public uint[] Pack() + public byte[] Pack() { - return new uint[] + return new byte[] { X, Y, Z, - (uint)Facing, - (uint)Texture + (byte)Facing, + (byte)Texture, + LightLevel, + 0,0 // two bits empty }; } } diff --git a/Program.cs b/Program.cs index d0d1b82..d4806e0 100644 --- a/Program.cs +++ b/Program.cs @@ -8,12 +8,15 @@ internal class Program int sizeY = 600; string title = "Game"; + Window window = new Window(sizeX, sizeY, title); + Chunk chunk = new Chunk(0, 0); - var faces = chunk.GetFaces(); - Renderer.AddFaces(faces); + ChunkMesh chunkMesh = chunk.GetChunkMesh(); + Renderer.AddFaces(chunkMesh.Faces); + + window.Chunk = chunk; - Window window = new Window(sizeX, sizeY, title); window.Run(); } } \ No newline at end of file diff --git a/Renderer.cs b/Renderer.cs index 814bbb5..098bf06 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -8,13 +8,19 @@ namespace Voxel private static int _vao; private static List _faces = new List(); private static Shader _shader; + private static Texture _texture; private static bool _needsUpdate = false; - public static void OnLoad() + static Renderer() { string vertexPath = "Shaders/shader.vert"; string fragmentPath = "Shaders/shader.frag"; + string texturePath = "atlas.png"; + _shader = new Shader(vertexPath, fragmentPath); + _texture = new Texture(texturePath); + + _shader.SetInt("uTexture", 0); _ssbo = GL.GenBuffer(); _vao = GL.GenVertexArray(); @@ -32,9 +38,8 @@ namespace Voxel if (_needsUpdate) { _needsUpdate = false; - Console.WriteLine("Update buffer"); - uint[] data = _faces.SelectMany(f => f.Pack()).ToArray(); - GL.BufferData(BufferTarget.ShaderStorageBuffer, data.Length * sizeof(uint), data, BufferUsageHint.StaticRead); + byte[] data = _faces.SelectMany(f => f.Pack()).ToArray(); + GL.BufferData(BufferTarget.ShaderStorageBuffer, data.Length, data, BufferUsageHint.StaticRead); } _shader.Use(); @@ -51,5 +56,10 @@ namespace Voxel _faces.AddRange(faces); _needsUpdate = true; } + + public static void ClearFaces() + { + _faces.Clear(); + } } } diff --git a/Shader.cs b/Shader.cs index 2f32279..4dcc110 100644 --- a/Shader.cs +++ b/Shader.cs @@ -67,6 +67,17 @@ namespace Voxel GL.UniformMatrix4(location, false, ref matrix); } + public void SetInt(string name, int value) + { + int location = GL.GetUniformLocation(_handle, name); + if (location == -1) + { + Console.WriteLine($"Uniform '{name}' not found in shader."); + return; + } + GL.Uniform1(location, value); + } + public void Use() { GL.UseProgram(_handle); diff --git a/Shaders/shader.frag b/Shaders/shader.frag index 0e84ed4..2b9b48d 100644 --- a/Shaders/shader.frag +++ b/Shaders/shader.frag @@ -1,9 +1,13 @@ #version 430 core out vec4 FragColor; -in vec3 fragColor; +in vec2 fragUV; +in float lighting; + +uniform sampler2D uTexture; void main() { - FragColor = vec4(fragColor, 1.0); + vec4 texColor = texture(uTexture, fragUV); + FragColor = vec4(texColor.rgb * lighting, texColor.a); } \ No newline at end of file diff --git a/Shaders/shader.vert b/Shaders/shader.vert index 0153833..6a3d0c4 100644 --- a/Shaders/shader.vert +++ b/Shaders/shader.vert @@ -6,101 +6,108 @@ struct FaceData { uint z; uint facing; // 0=+X,1=-X,2=+Y,3=-Y,4=+Z,5=-Z uint texture; + uint lightLevel; }; layout(std430, binding = 0) buffer FaceBuffer { - FaceData faces[]; + uint faces[]; }; -out vec3 fragColor; - -vec3 getVertexOffset(uint facing, uint vertIndex) { - if (facing == 0u) { // +X - vec3 offsets[6] = vec3[6]( - vec3(0.5, -0.5, -0.5), - vec3(0.5, 0.5, 0.5), - vec3(0.5, -0.5, 0.5), - vec3(0.5, 0.5, 0.5), - vec3(0.5, -0.5, -0.5), - vec3(0.5, 0.5, -0.5) - ); - return offsets[vertIndex]; - } else if (facing == 1u) { // -X - vec3 offsets[6] = vec3[6]( - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, 0.5, 0.5) - ); - return offsets[vertIndex]; - } else if (facing == 2u) { // +Y (top) - vec3 offsets[6] = vec3[6]( - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, 0.5, 0.5) - ); - return offsets[vertIndex]; - } else if (facing == 3u) { // -Y (bottom) - vec3 offsets[6] = vec3[6]( - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, -0.5, -0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, -0.5, -0.5), - vec3(-0.5, -0.5, 0.5), - vec3(-0.5, -0.5, -0.5) - ); - return offsets[vertIndex]; - } else if (facing == 4u) { // +Z (front) - vec3 offsets[6] = vec3[6]( - vec3(-0.5, -0.5, 0.5), - vec3( 0.5, -0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3( 0.5, 0.5, 0.5), - vec3(-0.5, 0.5, 0.5), - vec3(-0.5, -0.5, 0.5) - ); - return offsets[vertIndex]; - } else if (facing == 5u) { // -Z (back) - vec3 offsets[6] = vec3[6]( - vec3( 0.5, -0.5, -0.5), - vec3(-0.5, -0.5, -0.5), - vec3(-0.5, 0.5, -0.5), - vec3(-0.5, 0.5, -0.5), - vec3( 0.5, 0.5, -0.5), - vec3( 0.5, -0.5, -0.5) - ); - return offsets[vertIndex]; - } -} - uniform mat4 view; uniform mat4 projection; +out vec2 fragUV; +out float lighting; + +const float lightMult[6] = float[6](0.6, 0.6, 1.0, 0.5, 0.8, 0.8); + +const vec3 offsets[6][6] = vec3[6][6]( + vec3[6]( // +X + vec3(0.5, -0.5, -0.5), + vec3(0.5, 0.5, 0.5), + vec3(0.5, -0.5, 0.5), + vec3(0.5, 0.5, 0.5), + vec3(0.5, -0.5, -0.5), + vec3(0.5, 0.5, -0.5) + ), + vec3[6]( // -X + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, 0.5) + ), + vec3[6]( // +Y + vec3(-0.5, 0.5, -0.5), + vec3( 0.5, 0.5, 0.5), + vec3( 0.5, 0.5, -0.5), + vec3( 0.5, 0.5, 0.5), + vec3(-0.5, 0.5, -0.5), + vec3(-0.5, 0.5, 0.5) + ), + vec3[6]( + vec3(-0.5, -0.5, 0.5), + vec3( 0.5, -0.5, -0.5), + vec3( 0.5, -0.5, 0.5), + vec3( 0.5, -0.5, -0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, -0.5, -0.5) + ), + vec3[6]( // +Z + vec3( 0.5, -0.5, 0.5), + vec3(-0.5, 0.5, 0.5), + vec3(-0.5, -0.5, 0.5), + vec3(-0.5, 0.5, 0.5), + vec3( 0.5, -0.5, 0.5), + vec3( 0.5, 0.5, 0.5) + ), + vec3[6]( // -Z + vec3(-0.5, -0.5, -0.5), + vec3( 0.5, 0.5, -0.5), + vec3( 0.5, -0.5, -0.5), + vec3( 0.5, 0.5, -0.5), + vec3(-0.5, -0.5, -0.5), + vec3(-0.5, 0.5, -0.5) + ) +); + +const vec2 uvs[6] = vec2[6](vec2(0,0), vec2(1,1), vec2(1,0), vec2(1,1), vec2(0,0), vec2(0,1)); + void main() { - float[6] lightMult = float[6]( - 0.6, - 0.6, - 1.0, - 0.5, - 0.8, - 0.8 - ); + uint faceIndex = gl_VertexID / 6u; + uint vertIndex = gl_VertexID % 6u; - uint faceIndex = gl_VertexID / 6; - uint vertIndex = gl_VertexID % 6; + uint start = faceIndex * 2u; // 2 byte per face + + uint u0 = faces[start]; // data in uint 0 + uint u1 = faces[start + 1]; // data in uint 1 - FaceData f = faces[faceIndex]; - vec3 basePos = vec3(f.x, f.y, f.z); - vec4 worldPos = vec4(basePos + getVertexOffset(f.facing, vertIndex), 1.0); + // extract values from bits + uint x = u0 & 0xFFu; + uint y = (u0 >> 8) & 0xFFu; + uint z = (u0 >> 16) & 0xFFu; + uint facing = (u0 >> 24) & 0xFFu; + uint texture = u1 & 0xFFu; + uint lightLevel = (u1 >> 8) & 0xFFu; - - fragColor = vec3(1.0, 1.0, 1.0) * lightMult[f.facing]; + vec3 basePos = vec3(x, y, z); + vec4 worldPos = vec4(basePos + offsets[facing][vertIndex], 1.0); + float light = float(lightLevel) / 255.0; // use later for caves and stuff + + // UV mapping + uint col = texture & 15u; // texture % 16 + uint row = texture >> 4; // texture / 16 + row = 15u - row; // invert row so 0 is top + + // convert to float after int math, divide by 16 + vec2 uv = uvs[vertIndex] * 0.0625; + uv.x += float(col) * 0.0625; + uv.y += float(row) * 0.0625; + + fragUV = uv; + lighting = lightMult[facing]; gl_Position = projection * view * worldPos; } \ No newline at end of file diff --git a/Texture.cs b/Texture.cs new file mode 100644 index 0000000..8cf1417 --- /dev/null +++ b/Texture.cs @@ -0,0 +1,43 @@ +using OpenTK.Graphics.OpenGL4; +using StbImageSharp; + +namespace Voxel +{ + public class Texture + { + private int _handle; + private string _path; + + public Texture(string path) + { + _handle = GL.GenTexture(); + _path = path; + + LoadFromFile(); + } + + private void LoadFromFile() + { + StbImage.stbi_set_flip_vertically_on_load(1); + ImageResult image = ImageResult.FromStream(File.OpenRead(_path), ColorComponents.RedGreenBlueAlpha); + + GL.BindTexture(TextureTarget.Texture2D, _handle); + GL.TexImage2D( + TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, + image.Width, image.Height, 0, + PixelFormat.Rgba, PixelType.UnsignedByte, image.Data + ); + + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + } + + public void Bind(TextureUnit unit = TextureUnit.Texture0) + { + GL.ActiveTexture(unit); + GL.BindTexture(TextureTarget.Texture2D, _handle); + } + } +} \ No newline at end of file diff --git a/Textures.cs b/Textures.cs new file mode 100644 index 0000000..304df45 --- /dev/null +++ b/Textures.cs @@ -0,0 +1,44 @@ +namespace Voxel +{ + public enum Textures : uint + { + GrassTop, + Stone, + Dirt, + GrassSide, + OakPlanks, + SmoothStoneSlab, + SmoothStone, + Bricks, + TntSide, + TntTop, + TntBottom, + Cobweb, + Rose, + Dandelion, + Water, + OakSapling, + Cobblestone, + Bedrock, + Sand, + Gravel, + OakSide, + OakTop, + IronBlock, + GoldBlock, + DiamondBlock, + EmeraldBlock, + RedstoneBlock, + none0, + RedMushroom, + BrownMushroom, + JungleSapling, + none1, + GoldOre, + IronOre, + CoalOre, + Bookshelf, + MossyCobblestone, + Obsidian, + } +} diff --git a/Voxel.csproj b/Voxel.csproj index 207476f..d1caf0c 100644 --- a/Voxel.csproj +++ b/Voxel.csproj @@ -9,9 +9,13 @@ + + + Always + Always diff --git a/Window.cs b/Window.cs index 2ed4a47..7c0e9a1 100644 --- a/Window.cs +++ b/Window.cs @@ -11,6 +11,11 @@ namespace Voxel public uint frames = 0; public double timeElapsed = 0; + // testing + public Chunk Chunk; + private Dictionary _cache = new Dictionary(); + private bool chunkEmpty = false; + protected override void OnUpdateFrame(FrameEventArgs e) { base.OnUpdateFrame(e); @@ -19,6 +24,31 @@ namespace Voxel { Close(); } + + RemoveRandomBlocks(); + } + + private void RemoveRandomBlocks() + { + if (Chunk != null && !chunkEmpty) + { + Random rng = new Random(); + + int GetNum() + { + int max = 4096; + int num = rng.Next(max); + if (_cache.ContainsKey(num)) return GetNum(); + _cache[num] = true; + if (_cache.Count == max) chunkEmpty = true; + return num; + } + + Chunk.SetBlockIndex(GetNum(), Blocks.Air); + } + + Renderer.ClearFaces(); + Renderer.AddFaces(Chunk.GetChunkMesh().Faces); } protected override void OnRenderFrame(FrameEventArgs e) @@ -46,6 +76,8 @@ namespace Voxel { base.OnFramebufferResize(e); + Camera.UpdateProjection(e.Width, e.Height); + GL.Viewport(0, 0, e.Width, e.Height); } @@ -53,15 +85,17 @@ namespace Voxel { base.OnLoad(); - Renderer.OnLoad(); - GL.ClearColor(0.72f, 0.88f, 0.97f, 1f); GL.Enable(EnableCap.CullFace); GL.Enable(EnableCap.DepthTest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); CursorState = CursorState.Grabbed; VSync = VSyncMode.On; + + Camera.UpdateProjection(Width, Height); } protected override void OnMouseMove(MouseMoveEventArgs e) diff --git a/atlas.png b/atlas.png index f2d0775..c64f6f1 100644 Binary files a/atlas.png and b/atlas.png differ