feat(app): add CameraController with F toggle and cursor capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7cf6ea267a
commit
22f684e8c6
2 changed files with 100 additions and 15 deletions
31
src/AcDream.App/Rendering/CameraController.cs
Normal file
31
src/AcDream.App/Rendering/CameraController.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
// src/AcDream.App/Rendering/CameraController.cs
|
||||||
|
namespace AcDream.App.Rendering;
|
||||||
|
|
||||||
|
public sealed class CameraController
|
||||||
|
{
|
||||||
|
public OrbitCamera Orbit { get; }
|
||||||
|
public FlyCamera Fly { get; }
|
||||||
|
public ICamera Active { get; private set; }
|
||||||
|
public bool IsFlyMode => Active == Fly;
|
||||||
|
|
||||||
|
public event Action<bool>? ModeChanged;
|
||||||
|
|
||||||
|
public CameraController(OrbitCamera orbit, FlyCamera fly)
|
||||||
|
{
|
||||||
|
Orbit = orbit;
|
||||||
|
Fly = fly;
|
||||||
|
Active = orbit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleFly()
|
||||||
|
{
|
||||||
|
Active = IsFlyMode ? (ICamera)Orbit : Fly;
|
||||||
|
ModeChanged?.Invoke(IsFlyMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAspect(float aspect)
|
||||||
|
{
|
||||||
|
Orbit.Aspect = aspect;
|
||||||
|
Fly.Aspect = aspect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,8 @@ public sealed class GameWindow : IDisposable
|
||||||
private IInputContext? _input;
|
private IInputContext? _input;
|
||||||
private TerrainRenderer? _terrain;
|
private TerrainRenderer? _terrain;
|
||||||
private Shader? _shader;
|
private Shader? _shader;
|
||||||
private OrbitCamera? _camera;
|
private CameraController? _cameraController;
|
||||||
|
private IMouse? _capturedMouse;
|
||||||
private DatCollection? _dats;
|
private DatCollection? _dats;
|
||||||
private float _lastMouseX;
|
private float _lastMouseX;
|
||||||
private float _lastMouseY;
|
private float _lastMouseY;
|
||||||
|
|
@ -42,6 +43,7 @@ public sealed class GameWindow : IDisposable
|
||||||
|
|
||||||
_window = Window.Create(options);
|
_window = Window.Create(options);
|
||||||
_window.Load += OnLoad;
|
_window.Load += OnLoad;
|
||||||
|
_window.Update += OnUpdate;
|
||||||
_window.Render += OnRender;
|
_window.Render += OnRender;
|
||||||
_window.Closing += OnClosing;
|
_window.Closing += OnClosing;
|
||||||
|
|
||||||
|
|
@ -55,26 +57,49 @@ public sealed class GameWindow : IDisposable
|
||||||
foreach (var kb in _input.Keyboards)
|
foreach (var kb in _input.Keyboards)
|
||||||
kb.KeyDown += (_, key, _) =>
|
kb.KeyDown += (_, key, _) =>
|
||||||
{
|
{
|
||||||
if (key == Key.Escape)
|
if (key == Key.F)
|
||||||
|
_cameraController?.ToggleFly();
|
||||||
|
else if (key == Key.Escape)
|
||||||
|
{
|
||||||
|
if (_cameraController?.IsFlyMode == true)
|
||||||
|
_cameraController.ToggleFly(); // exit fly, release cursor
|
||||||
|
else
|
||||||
_window!.Close();
|
_window!.Close();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var mouse in _input.Mice)
|
foreach (var mouse in _input.Mice)
|
||||||
{
|
{
|
||||||
mouse.MouseMove += (m, pos) =>
|
mouse.MouseMove += (m, pos) =>
|
||||||
|
{
|
||||||
|
if (_cameraController is null) return;
|
||||||
|
|
||||||
|
if (_cameraController.IsFlyMode)
|
||||||
|
{
|
||||||
|
// Raw cursor mode: Silk.NET gives deltas via position. Compute delta from last.
|
||||||
|
float dx = pos.X - _lastMouseX;
|
||||||
|
float dy = pos.Y - _lastMouseY;
|
||||||
|
_cameraController.Fly.Look(dx, dy);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (m.IsButtonPressed(MouseButton.Left))
|
if (m.IsButtonPressed(MouseButton.Left))
|
||||||
{
|
{
|
||||||
_camera!.Yaw -= (pos.X - _lastMouseX) * 0.005f;
|
_cameraController.Orbit.Yaw -= (pos.X - _lastMouseX) * 0.005f;
|
||||||
_camera!.Pitch = Math.Clamp(
|
_cameraController.Orbit.Pitch = Math.Clamp(
|
||||||
_camera.Pitch + (pos.Y - _lastMouseY) * 0.005f,
|
_cameraController.Orbit.Pitch + (pos.Y - _lastMouseY) * 0.005f,
|
||||||
0.1f, 1.5f);
|
0.1f, 1.5f);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_lastMouseX = pos.X;
|
_lastMouseX = pos.X;
|
||||||
_lastMouseY = pos.Y;
|
_lastMouseY = pos.Y;
|
||||||
};
|
};
|
||||||
mouse.Scroll += (_, scroll) =>
|
mouse.Scroll += (_, scroll) =>
|
||||||
_camera!.Distance = Math.Clamp(_camera.Distance - scroll.Y * 20f, 50f, 2000f);
|
{
|
||||||
|
if (_cameraController is null || _cameraController.IsFlyMode) return;
|
||||||
|
_cameraController.Orbit.Distance = Math.Clamp(
|
||||||
|
_cameraController.Orbit.Distance - scroll.Y * 20f, 50f, 2000f);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
_gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
||||||
|
|
@ -89,10 +114,10 @@ public sealed class GameWindow : IDisposable
|
||||||
Path.Combine(shadersDir, "mesh.vert"),
|
Path.Combine(shadersDir, "mesh.vert"),
|
||||||
Path.Combine(shadersDir, "mesh.frag"));
|
Path.Combine(shadersDir, "mesh.frag"));
|
||||||
|
|
||||||
_camera = new OrbitCamera
|
var orbit = new OrbitCamera { Aspect = _window!.Size.X / (float)_window.Size.Y };
|
||||||
{
|
var fly = new FlyCamera { Aspect = _window.Size.X / (float)_window.Size.Y };
|
||||||
Aspect = _window!.Size.X / (float)_window.Size.Y,
|
_cameraController = new CameraController(orbit, fly);
|
||||||
};
|
_cameraController.ModeChanged += OnCameraModeChanged;
|
||||||
|
|
||||||
_dats = new DatCollection(_datDir, DatAccessType.Read);
|
_dats = new DatCollection(_datDir, DatAccessType.Read);
|
||||||
|
|
||||||
|
|
@ -199,11 +224,40 @@ public sealed class GameWindow : IDisposable
|
||||||
Console.WriteLine($"hydrated {_entities.Count} entities");
|
Console.WriteLine($"hydrated {_entities.Count} entities");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnUpdate(double dt)
|
||||||
|
{
|
||||||
|
if (_cameraController is null || _input is null) return;
|
||||||
|
if (!_cameraController.IsFlyMode) return;
|
||||||
|
|
||||||
|
var kb = _input.Keyboards[0];
|
||||||
|
_cameraController.Fly.Update(
|
||||||
|
dt,
|
||||||
|
w: kb.IsKeyPressed(Key.W),
|
||||||
|
a: kb.IsKeyPressed(Key.A),
|
||||||
|
s: kb.IsKeyPressed(Key.S),
|
||||||
|
d: kb.IsKeyPressed(Key.D),
|
||||||
|
up: kb.IsKeyPressed(Key.Space),
|
||||||
|
down: kb.IsKeyPressed(Key.ControlLeft));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCameraModeChanged(bool isFlyMode)
|
||||||
|
{
|
||||||
|
if (_input is null) return;
|
||||||
|
var mouse = _input.Mice.FirstOrDefault();
|
||||||
|
if (mouse is null) return;
|
||||||
|
|
||||||
|
mouse.Cursor.CursorMode = isFlyMode ? CursorMode.Raw : CursorMode.Normal;
|
||||||
|
_capturedMouse = isFlyMode ? mouse : null;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnRender(double deltaSeconds)
|
private void OnRender(double deltaSeconds)
|
||||||
{
|
{
|
||||||
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
_terrain?.Draw(_camera!);
|
if (_cameraController is not null)
|
||||||
_staticMesh?.Draw(_camera!, _entities);
|
{
|
||||||
|
_terrain?.Draw(_cameraController.Active);
|
||||||
|
_staticMesh?.Draw(_cameraController.Active, _entities);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnClosing()
|
private void OnClosing()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue