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);
+ }
+}