diff --git a/Core/Game.cs b/Core/Game.cs index f6ea81a..5aeb658 100644 --- a/Core/Game.cs +++ b/Core/Game.cs @@ -3,76 +3,75 @@ using OpenTK.Mathematics; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; +using Serilog; +using VoxelEngine.Game; using VoxelEngine.Rendering; using VoxelEngine.World.Blocks; using VoxelEngine.World.Chunk; using VoxelEngine.World.Persistence; +using VoxelEngine.World.Streaming; using VoxelEngine.World.WorldGen; namespace VoxelEngine.Core; public sealed class Game : GameWindow { - private const float MoveSpeed = 8f; // units per second - private const float Sensitivity = 0.15f; // degrees per pixel - private const int Seed = 1337; - private const bool UseSeed = false; + private static readonly ILogger Log = Serilog.Log.ForContext(); + private readonly Configuration _cfg; private readonly BlockRegistry _registry = new(); + private Shader _shader = null!; private Texture _texture = null!; - private readonly Dictionary _renderer = new(); - private Camera _camera = null!; + private LoadingScreen _loading = null!; + private World.World _world = null!; + private InMemoryChunkModificationStore _modStore = null!; private WorldGenerator _generator = null!; + private ChunkStreamer _streamer = null!; + private Player _player = null!; - private World.World _world; - - public Game(GameWindowSettings gameSettings, NativeWindowSettings nativeSettings) - : base(gameSettings, nativeSettings) { } + public Game(Configuration cfg, GameWindowSettings gameSettings, NativeWindowSettings nativeSettings) + : base(gameSettings, nativeSettings) + { + this._cfg = cfg; + } protected override void OnLoad() { base.OnLoad(); + Log.Information("Game starting with seed={Seed} renderDistanceH={H} renderDistanceV={V}", + this._cfg.Seed, this._cfg.RenderDistanceHorizontal, this._cfg.RenderDistanceVertical); + GL.ClearColor(0.45f, 0.65f, 0.95f, 1.0f); GL.Enable(EnableCap.DepthTest); GL.Enable(EnableCap.CullFace); this._shader = new Shader("Assets/shaders/chunk.vert", "Assets/shaders/chunk.frag"); this._texture = new Texture("Assets/textures/VoxelShadeAtlas.png"); + this._loading = new LoadingScreen(); - this._world = new World.World(this._registry, new InMemoryChunkModificationStore()); + this._modStore = new InMemoryChunkModificationStore(); + this._world = new World.World(this._registry, this._modStore); - int seed = Game.UseSeed ? Game.Seed : new Random().Next(); - + int seed = this._cfg.Seed ?? new Random().Next(); this._generator = new WorldGenerator(seed, this._registry); - for (int cx = -4; cx < 4; cx++) - for (int cz = -4; cz < 4; cz++) - for (int cy = 0; cy < WorldConstants.WorldHeightChunks; cy++) - { - Vector3i pos = new(cx, cy, cz); - this._world.SetChunk(pos, this._generator.Generate(pos)); - } + // Spawn at terrain surface above (0, 0). + int surfaceY = this._generator.SurfaceHeightAt(0, 0); + Vector3 spawnPosition = new(0.5f, surfaceY + 2f, 0.5f); - ChunkMesher mesher = new(this._texture.Width, this._texture.Height); - foreach ((Vector3i pos, Chunk chunk) in this._world) - { - Vector3i origin = pos * Chunk.Size; - float[] mesh = mesher.Build( - chunk, - this._registry, - (nx, ny, nz) => this._world.GetBlock( - origin.X + nx, - origin.Y + ny, - origin.Z + nz).IsOpaque - ); - this._renderer[pos] = new ChunkRenderer(mesh); - } + this._player = new Player(this._cfg, spawnPosition, this.ClientSize.X / (float)this.ClientSize.Y); - this._camera = new Camera( - new Vector3(8f, WorldConstants.SeaLevel + 30f, 40f), - this.ClientSize.X / (float)this.ClientSize.Y); + var mesher = new ChunkMesher(this._texture.Width, this._texture.Height); + this._streamer = new ChunkStreamer( + this._cfg, + this._world, + this._generator.Generate, + mesher, + this._registry, + new ThreadPoolJobExecutor(), + mesh => new ChunkRenderer(mesh)); this.CursorState = CursorState.Grabbed; } @@ -82,24 +81,22 @@ public sealed class Game : GameWindow base.OnUpdateFrame(args); if (!this.IsFocused) return; - if (this.KeyboardState.IsKeyDown(Keys.Escape)) { - this.Close(); return; - } + if (this.KeyboardState.IsKeyDown(Keys.Escape)) { this.Close(); return; } - float speedMulti = 1f; - - if (this.KeyboardState.IsKeyDown(Keys.LeftShift)) speedMulti = 1.8f ; + float speedMulti = this.KeyboardState.IsKeyDown(Keys.LeftShift) ? 1.8f : 1f; + float distance = this._player.MoveSpeed * speedMulti * (float)args.Time; - float distance = Game.MoveSpeed * speedMulti * (float)args.Time; - if (this.KeyboardState.IsKeyDown(Keys.W)) this._camera.Move(this._camera.Front * distance); - if (this.KeyboardState.IsKeyDown(Keys.S)) this._camera.Move(-this._camera.Front * distance); - if (this.KeyboardState.IsKeyDown(Keys.A)) this._camera.Move(-this._camera.Right * distance); - if (this.KeyboardState.IsKeyDown(Keys.D)) this._camera.Move(this._camera.Right * distance); - if (this.KeyboardState.IsKeyDown(Keys.Space)) this._camera.Move(Vector3.UnitY * distance); - if (this.KeyboardState.IsKeyDown(Keys.LeftControl)) this._camera.Move(-Vector3.UnitY * distance); + if (this.KeyboardState.IsKeyDown(Keys.W)) this._player.Move(this._player.Camera.Front * distance); + if (this.KeyboardState.IsKeyDown(Keys.S)) this._player.Move(-this._player.Camera.Front * distance); + if (this.KeyboardState.IsKeyDown(Keys.A)) this._player.Move(-this._player.Camera.Right * distance); + if (this.KeyboardState.IsKeyDown(Keys.D)) this._player.Move(this._player.Camera.Right * distance); + if (this.KeyboardState.IsKeyDown(Keys.Space)) this._player.Move(Vector3.UnitY * distance); + if (this.KeyboardState.IsKeyDown(Keys.LeftControl)) this._player.Move(-Vector3.UnitY * distance); Vector2 delta = this.MouseState.Delta; - this._camera.AddRotation(delta.X * Game.Sensitivity, -delta.Y * Game.Sensitivity); + this._player.AddRotation(delta.X * this._player.MouseSensitivity, -delta.Y * this._player.MouseSensitivity); + + this._streamer.Tick(this._player.ChunkPosition); } protected override void OnRenderFrame(FrameEventArgs args) @@ -108,21 +105,25 @@ public sealed class Game : GameWindow GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - this._shader.Use(); - this._shader.SetMatrix4("view", this._camera.GetViewMatrix()); - this._shader.SetMatrix4("projection", this._camera.GetProjectionMatrix()); - this._shader.SetInt("atlas", 0); - this._texture.Use(); + if (!this._streamer.SpawnReady) + { + this._loading.Draw(); + this.SwapBuffers(); + return; + } - foreach ((Vector3i pos, ChunkRenderer renderer) in this._renderer) + this._shader.Use(); + this._shader.SetMatrix4("view", this._player.Camera.GetViewMatrix()); + this._shader.SetMatrix4("projection", this._player.Camera.GetProjectionMatrix()); + this._shader.SetInt("atlas", 0); + this._texture.Use(TextureUnit.Texture0); + + this._streamer.DrawAll((pos, renderer) => { this._shader.SetMatrix4("model", Matrix4.CreateTranslation( - pos.X * Chunk.Size, - pos.Y * Chunk.Size, - pos.Z * Chunk.Size - )); + pos.X * Chunk.Size, pos.Y * Chunk.Size, pos.Z * Chunk.Size)); renderer.Draw(); - } + }); this.SwapBuffers(); } @@ -131,18 +132,16 @@ public sealed class Game : GameWindow { base.OnResize(e); GL.Viewport(0, 0, e.Width, e.Height); - // OnResize can fire before OnLoad (where _camera is created), so guard against it. - if (this._camera is not null) this._camera.AspectRatio = e.Width / (float)e.Height; + if (this._player is not null) this._player.Camera.AspectRatio = e.Width / (float)e.Height; } protected override void OnUnload() { - foreach (ChunkRenderer rendererValue in this._renderer.Values) - { - rendererValue.Dispose(); - } - this._texture.Dispose(); - this._shader.Dispose(); + Log.Information("Game shutting down"); + this._streamer?.Dispose(); + this._loading?.Dispose(); + this._texture?.Dispose(); + this._shader?.Dispose(); base.OnUnload(); } } diff --git a/Program.cs b/Program.cs index f054a04..d6452d2 100644 --- a/Program.cs +++ b/Program.cs @@ -20,16 +20,18 @@ Log.Logger = new LoggerConfiguration() try { + var cfg = new Configuration(); + NativeWindowSettings nativeSettings = new() { - ClientSize = new Vector2i(1280, 720), + ClientSize = new Vector2i(cfg.WindowWidth, cfg.WindowHeight), Title = "VoxelEngine", APIVersion = new Version(3, 3), Profile = ContextProfile.Core, Flags = ContextFlags.ForwardCompatible }; - using Game game = new(GameWindowSettings.Default, nativeSettings); + using Game game = new(cfg, GameWindowSettings.Default, nativeSettings); game.Run(); } catch (Exception ex) diff --git a/World/World.cs b/World/World.cs index f6ea3b5..56044e7 100644 --- a/World/World.cs +++ b/World/World.cs @@ -1,4 +1,3 @@ -using System.Collections; using OpenTK.Mathematics; using VoxelEngine.Core; using VoxelEngine.World.Blocks; @@ -7,7 +6,7 @@ using VoxelEngine.World.Persistence; namespace VoxelEngine.World; public sealed class World(BlockRegistry registry, IChunkModificationStore modStore) - : IWorld, IEnumerable> + : IWorld { private readonly Dictionary _chunks = new(); @@ -60,7 +59,4 @@ public sealed class World(BlockRegistry registry, IChunkModificationStore modSto return true; } - public IEnumerator> GetEnumerator() => this._chunks.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } diff --git a/World/WorldGen/WorldGenerator.cs b/World/WorldGen/WorldGenerator.cs index 1e45c45..13837c3 100644 --- a/World/WorldGen/WorldGenerator.cs +++ b/World/WorldGen/WorldGenerator.cs @@ -23,6 +23,8 @@ public sealed class WorldGenerator this._terrain = new TerrainSampler(noise, this._biomeMap); } + public int SurfaceHeightAt(int worldX, int worldZ) => this._terrain.HeightAt(worldX, worldZ); + public VoxelChunk Generate(Vector3i chunkPos) { VoxelChunk chunk = new();