using OpenTK.Graphics.OpenGL4; using OpenTK.Mathematics; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; using VoxelEngine.Rendering; using VoxelEngine.World.Blocks; using VoxelEngine.World.Chunk; 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 readonly BlockRegistry _registry = new(); private Shader _shader = null!; private Texture _texture = null!; private readonly Dictionary _renderer = new(); private Camera _camera = null!; private World.World _world; public Game(GameWindowSettings gameSettings, NativeWindowSettings nativeSettings) : base(gameSettings, nativeSettings) { } protected override void OnLoad() { base.OnLoad(); 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/atlas.png"); this._world = new World.World(this._registry); for (int x = -2; x < 2; x++) for (int z = -2; z < 2; z++) { this._world.SetChunk(new Vector3i(x, 0, z), this.BuildChunk()); } ChunkMesher mesher = new(); 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._camera = new Camera(new Vector3(8f, 20f, 40f), this.ClientSize.X / (float)this.ClientSize.Y); this.CursorState = CursorState.Grabbed; } private Chunk BuildChunk() { Chunk chunk = new(); for (int x = 0; x < Chunk.Size; x++) for (int z = 0; z < Chunk.Size; z++) for (int y = 0; y < Chunk.Size; y++) { chunk[x, y, z] = y switch { Chunk.Size - 1 => this._registry.Grass.Id, >= Chunk.Size - 4 => this._registry.Dirt.Id, _ => this._registry.Stone.Id }; } return chunk; } protected override void OnUpdateFrame(FrameEventArgs args) { base.OnUpdateFrame(args); if (!this.IsFocused) return; if (this.KeyboardState.IsKeyDown(Keys.Escape)) { this.Close(); return; } float speedMulti = 1f; if (this.KeyboardState.IsKeyDown(Keys.LeftShift)) speedMulti = 1.8f ; 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); Vector2 delta = this.MouseState.Delta; this._camera.AddRotation(delta.X * Game.Sensitivity, -delta.Y * Game.Sensitivity); } protected override void OnRenderFrame(FrameEventArgs args) { base.OnRenderFrame(args); 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(TextureUnit.Texture0); foreach ((Vector3i pos, ChunkRenderer renderer) in this._renderer) { this._shader.SetMatrix4("model", Matrix4.CreateTranslation( pos.X * Chunk.Size, pos.Y * Chunk.Size, pos.Z * Chunk.Size )); renderer.Draw(); } this.SwapBuffers(); } protected override void OnResize(ResizeEventArgs e) { 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; } protected override void OnUnload() { foreach (ChunkRenderer rendererValue in this._renderer.Values) { rendererValue.Dispose(); } this._texture.Dispose(); this._shader.Dispose(); base.OnUnload(); } }