using AcDream.Core.Terrain; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Options; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; using Silk.NET.Windowing; 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 Shader? _shader; private OrbitCamera? _camera; private DatCollection? _dats; private float _lastMouseX; private float _lastMouseY; public GameWindow(string datDir) => _datDir = datDir; public void Run() { var options = WindowOptions.Default with { Size = new Vector2D(1280, 720), Title = "acdream — phase 1", API = new GraphicsAPI( ContextAPI.OpenGL, ContextProfile.Core, ContextFlags.ForwardCompatible, new APIVersion(4, 3)), VSync = true, }; _window = Window.Create(options); _window.Load += OnLoad; _window.Render += OnRender; _window.Closing += OnClosing; _window.Run(); } private void OnLoad() { _gl = GL.GetApi(_window!); _input = _window!.CreateInput(); foreach (var kb in _input.Keyboards) kb.KeyDown += (_, key, _) => { if (key == Key.Escape) _window!.Close(); }; foreach (var mouse in _input.Mice) { mouse.MouseMove += (m, pos) => { if (m.IsButtonPressed(MouseButton.Left)) { _camera!.Yaw -= (pos.X - _lastMouseX) * 0.005f; _camera!.Pitch = Math.Clamp( _camera.Pitch + (pos.Y - _lastMouseY) * 0.005f, 0.1f, 1.5f); } _lastMouseX = pos.X; _lastMouseY = pos.Y; }; mouse.Scroll += (_, scroll) => _camera!.Distance = Math.Clamp(_camera.Distance - scroll.Y * 20f, 50f, 2000f); } _gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f); _gl.Enable(EnableCap.DepthTest); string shadersDir = Path.Combine(AppContext.BaseDirectory, "Rendering", "Shaders"); _shader = new Shader(_gl, Path.Combine(shadersDir, "terrain.vert"), Path.Combine(shadersDir, "terrain.frag")); _camera = new OrbitCamera { Aspect = _window!.Size.X / (float)_window.Size.Y, }; _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. Using Get // (returns null on miss) rather than TryGet to sidestep // [MaybeNullWhen(false)] nullable-generic analysis under // TreatWarningsAsErrors. 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, _shader); } private void OnRender(double deltaSeconds) { _gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); _terrain?.Draw(_camera!); } private void OnClosing() { _terrain?.Dispose(); _shader?.Dispose(); _dats?.Dispose(); _input?.Dispose(); _gl?.Dispose(); } public void Dispose() => _window?.Dispose(); }