diff --git a/src/AcDream.App/AcDream.App.csproj b/src/AcDream.App/AcDream.App.csproj index 162ad58..a1140d6 100644 --- a/src/AcDream.App/AcDream.App.csproj +++ b/src/AcDream.App/AcDream.App.csproj @@ -7,6 +7,7 @@ latest true AcDream.App + true diff --git a/src/AcDream.App/Program.cs b/src/AcDream.App/Program.cs index dae3ff3..6412b46 100644 --- a/src/AcDream.App/Program.cs +++ b/src/AcDream.App/Program.cs @@ -1,5 +1,15 @@ using AcDream.App.Rendering; -var window = new GameWindow(); +var datDir = args.FirstOrDefault() + ?? Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR"); + +if (string.IsNullOrWhiteSpace(datDir)) +{ + Console.Error.WriteLine("usage: AcDream.App "); + Console.Error.WriteLine(" or: set ACDREAM_DAT_DIR and run with no args"); + return 2; +} + +using var window = new GameWindow(datDir); window.Run(); return 0; diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 9a52fb5..5569b70 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1,3 +1,7 @@ +using AcDream.Core.Terrain; +using DatReaderWriter; +using DatReaderWriter.DBObjs; +using DatReaderWriter.Options; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; @@ -7,9 +11,14 @@ namespace AcDream.App.Rendering; public sealed class GameWindow : IDisposable { + private readonly string _datDir; private IWindow? _window; private GL? _gl; private IInputContext? _input; + private TerrainRenderer? _terrain; + private DatCollection? _dats; + + public GameWindow(string datDir) => _datDir = datDir; public void Run() { @@ -45,15 +54,46 @@ public sealed class GameWindow : IDisposable }; _gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f); + _gl.Enable(EnableCap.DepthTest); + + _dats = new DatCollection(_datDir, DatAccessType.Read); + + // Find ANY landblock ending in 0xFFFF. Holtburg 0xA9B4FFFF is a + // good default; fall back to the first one we find. + uint landblockId = 0xA9B4FFFFu; + var block = _dats.Get(landblockId); + if (block is null) + { + foreach (var file in _dats.Cell.Tree) + { + if ((file.Id & 0xFFFFu) == 0xFFFFu) + { + landblockId = file.Id; + block = _dats.Get(landblockId); + break; + } + } + } + + if (block is null) + throw new InvalidOperationException("no landblock found in cell dat"); + + Console.WriteLine($"loaded landblock 0x{landblockId:X8}"); + + var meshData = LandblockMesh.Build(block); + _terrain = new TerrainRenderer(_gl, meshData); } private void OnRender(double deltaSeconds) { _gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + _terrain?.Draw(); } private void OnClosing() { + _terrain?.Dispose(); + _dats?.Dispose(); _input?.Dispose(); _gl?.Dispose(); } diff --git a/src/AcDream.App/Rendering/TerrainRenderer.cs b/src/AcDream.App/Rendering/TerrainRenderer.cs new file mode 100644 index 0000000..2c393ec --- /dev/null +++ b/src/AcDream.App/Rendering/TerrainRenderer.cs @@ -0,0 +1,61 @@ +using AcDream.Core.Terrain; +using Silk.NET.OpenGL; + +namespace AcDream.App.Rendering; + +public sealed unsafe class TerrainRenderer : IDisposable +{ + private readonly GL _gl; + private readonly uint _vao; + private readonly uint _vbo; + private readonly uint _ebo; + private readonly int _indexCount; + + public TerrainRenderer(GL gl, LandblockMeshData meshData) + { + _gl = gl; + _indexCount = meshData.Indices.Length; + + _vao = _gl.GenVertexArray(); + _gl.BindVertexArray(_vao); + + _vbo = _gl.GenBuffer(); + _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo); + fixed (void* p = meshData.Vertices) + _gl.BufferData(BufferTargetARB.ArrayBuffer, + (nuint)(meshData.Vertices.Length * sizeof(Vertex)), p, BufferUsageARB.StaticDraw); + + _ebo = _gl.GenBuffer(); + _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo); + fixed (void* p = meshData.Indices) + _gl.BufferData(BufferTargetARB.ElementArrayBuffer, + (nuint)(meshData.Indices.Length * sizeof(uint)), p, BufferUsageARB.StaticDraw); + + // vertex layout: position(3f), normal(3f), texcoord(2f) = 8 floats stride + uint stride = (uint)sizeof(Vertex); + _gl.EnableVertexAttribArray(0); + _gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, (void*)0); + _gl.EnableVertexAttribArray(1); + _gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float))); + _gl.EnableVertexAttribArray(2); + _gl.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float))); + + _gl.BindVertexArray(0); + } + + public void Draw() + { + // Shader binding + draw call come in Task 9. For this task, binding the + // VAO is enough to prove the buffers uploaded without GL errors. + _gl.BindVertexArray(_vao); + // intentionally no draw call yet + _gl.BindVertexArray(0); + } + + public void Dispose() + { + _gl.DeleteBuffer(_vbo); + _gl.DeleteBuffer(_ebo); + _gl.DeleteVertexArray(_vao); + } +}