feat(app): load landblock from dats and upload mesh to GPU
GameWindow now owns a DatCollection + TerrainRenderer. On load it opens the dat directory passed as argv[0] (or ACDREAM_DAT_DIR), finds Holtburg (landblock 0xA9B4FFFF) by default with a fallback to the first landblock in the cell b-tree, builds the CPU mesh from LandblockMesh.Build, and uploads VBO+EBO+VAO with a 3f/3f/2f attribute layout. No draw call yet — shader and matrix uniforms land in Task 9. Enabled AllowUnsafeBlocks on the App csproj so the fixed-buffer upload in TerrainRenderer compiles. Uses dats.Get<LandBlock>(id) instead of TryGet(..., out T) to sidestep the [MaybeNullWhen(false)] analysis that TreatWarningsAsErrors was flagging. Smoke verified against the real retail dats: prints "loaded landblock 0xA9B4FFFF" and the window stays alive with no GL errors or exceptions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6d18e0bd38
commit
8356fe65a0
4 changed files with 113 additions and 1 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<RootNamespace>AcDream.App</RootNamespace>
|
<RootNamespace>AcDream.App</RootNamespace>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
|
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
using AcDream.App.Rendering;
|
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 <dat-directory>");
|
||||||
|
Console.Error.WriteLine(" or: set ACDREAM_DAT_DIR and run with no args");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var window = new GameWindow(datDir);
|
||||||
window.Run();
|
window.Run();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
using AcDream.Core.Terrain;
|
||||||
|
using DatReaderWriter;
|
||||||
|
using DatReaderWriter.DBObjs;
|
||||||
|
using DatReaderWriter.Options;
|
||||||
using Silk.NET.Input;
|
using Silk.NET.Input;
|
||||||
using Silk.NET.Maths;
|
using Silk.NET.Maths;
|
||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
|
|
@ -7,9 +11,14 @@ namespace AcDream.App.Rendering;
|
||||||
|
|
||||||
public sealed class GameWindow : IDisposable
|
public sealed class GameWindow : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly string _datDir;
|
||||||
private IWindow? _window;
|
private IWindow? _window;
|
||||||
private GL? _gl;
|
private GL? _gl;
|
||||||
private IInputContext? _input;
|
private IInputContext? _input;
|
||||||
|
private TerrainRenderer? _terrain;
|
||||||
|
private DatCollection? _dats;
|
||||||
|
|
||||||
|
public GameWindow(string datDir) => _datDir = datDir;
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
|
|
@ -45,15 +54,46 @@ public sealed class GameWindow : IDisposable
|
||||||
};
|
};
|
||||||
|
|
||||||
_gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
_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<LandBlock>(landblockId);
|
||||||
|
if (block is null)
|
||||||
|
{
|
||||||
|
foreach (var file in _dats.Cell.Tree)
|
||||||
|
{
|
||||||
|
if ((file.Id & 0xFFFFu) == 0xFFFFu)
|
||||||
|
{
|
||||||
|
landblockId = file.Id;
|
||||||
|
block = _dats.Get<LandBlock>(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)
|
private void OnRender(double deltaSeconds)
|
||||||
{
|
{
|
||||||
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
|
_terrain?.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClosing()
|
private void OnClosing()
|
||||||
{
|
{
|
||||||
|
_terrain?.Dispose();
|
||||||
|
_dats?.Dispose();
|
||||||
_input?.Dispose();
|
_input?.Dispose();
|
||||||
_gl?.Dispose();
|
_gl?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/AcDream.App/Rendering/TerrainRenderer.cs
Normal file
61
src/AcDream.App/Rendering/TerrainRenderer.cs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue