From 61f8c7065e7ba7eb0d185bbead5be90cc15b038c Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Tue, 16 Jun 2026 21:14:32 +0300 Subject: [PATCH] feat: WASD free-fly camera with F toggle - Add ICameraController interface and FreeFlyCameraController. - WASD moves, Q/E up/down, Shift sprint, right-mouse + mouse look. - Toggle between Orbit and FreeFly with F key. - OrbitCameraController now implements ICameraController. - Update Program.cs to switch active controller on F. - Update CORTEX_ENGINE_ARCHITECTURE.md with camera controls. - Debug/Release/ReleaseAOT all build. --- CORTEX_ENGINE_ARCHITECTURE.md | 6 +- src/CortexEngine.App/Program.cs | 19 +++- src/Engine.Core/FreeFlyCameraController.cs | 114 +++++++++++++++++++++ src/Engine.Core/ICameraController.cs | 10 ++ src/Engine.Core/OrbitCameraController.cs | 4 +- 5 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 src/Engine.Core/FreeFlyCameraController.cs create mode 100644 src/Engine.Core/ICameraController.cs diff --git a/CORTEX_ENGINE_ARCHITECTURE.md b/CORTEX_ENGINE_ARCHITECTURE.md index ba11496..4332b45 100644 --- a/CORTEX_ENGINE_ARCHITECTURE.md +++ b/CORTEX_ENGINE_ARCHITECTURE.md @@ -529,7 +529,9 @@ In Release (NativeAOT), the MCP server and ASP.NET Core are excluded. The AI can │ │ ├── Sdl3Window.cs # SDL3 window wrapper │ │ ├── Timing.cs # DeltaTime, fixed timestep │ │ ├── InputMapping.cs # Keyboard, mouse, gamepad input +│ │ ├── ICameraController.cs # Camera controller interface │ │ ├── OrbitCameraController.cs # Mouse orbit camera +│ │ ├── FreeFlyCameraController.cs # WASD + mouse look camera │ │ └── Components/ # Transform, Camera, Light, Material, Mesh │ │ │ ├── Engine.Data/ @@ -760,7 +762,9 @@ dotnet run --project src/CortexEngine.App/CortexEngine.App.csproj - `SDL3 2026.520.0` API: `SDL_Init` returns `SDLBool`, `SDL_PollEvent` returns `SDLBool`, `evt.type` is `uint`. - Keyboard: `evt.key.key`; Mouse: `evt.motion.x`, `evt.motion.y`, `evt.wheel.y`. -- Orbit camera: right mouse drag rotates, mouse wheel zooms, `ESC` exits. +- **Orbit camera** (по умолчанию): правый клик + движение мыши — вращать, колесо — zoom. +- **FreeFly camera** (переключается клавишей `F`): `WASD` — двигаться, `Q`/`E` — вниз/вверх, `Shift` — ускорение, правый клик + мышь — осмотр. +- `ESC` — выход. ### 13.5 MCP Client Config diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs index a07c288..6520380 100644 --- a/src/CortexEngine.App/Program.cs +++ b/src/CortexEngine.App/Program.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Numerics; using Engine.AI; +using SDL; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -73,7 +74,12 @@ class Program .Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, Vector3.One)) .Set(new Light(new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0.15f, 0.15f, 0.2f), 0.3f)); - var orbit = new OrbitCameraController(cameraEntity, new Vector3(0.0f, 0.5f, 0.0f)); + ICameraController[] cameraControllers = + [ + new OrbitCameraController(cameraEntity, new Vector3(0.0f, 0.5f, 0.0f)), + new FreeFlyCameraController(cameraEntity) + ]; + var activeControllerIndex = 0; var texturePath = GenerateCheckerboardTexture("Content/checkerboard.png", 256); @@ -159,8 +165,15 @@ class Program camera.AspectRatio = (float)lastWidth / lastHeight; } - // Update orbit camera from mouse input. - orbit.Update(input, (float)timing.DeltaTime); + // Toggle camera controller with F. + if (input.IsKeyPressed(SDL_Keycode.SDLK_F)) + { + activeControllerIndex = (activeControllerIndex + 1) % cameraControllers.Length; + Console.WriteLine($"Camera controller: {cameraControllers[activeControllerIndex].Name}"); + } + + // Update active camera controller from input. + cameraControllers[activeControllerIndex].Update(input, (float)timing.DeltaTime); // Slowly rotate the model so we can see it in 3D. ref var modelTransform = ref model.Ensure(); diff --git a/src/Engine.Core/FreeFlyCameraController.cs b/src/Engine.Core/FreeFlyCameraController.cs new file mode 100644 index 0000000..4b9687d --- /dev/null +++ b/src/Engine.Core/FreeFlyCameraController.cs @@ -0,0 +1,114 @@ +using System; +using System.Numerics; +using Flecs.NET.Core; +using SDL; +using Engine.Core.Components; + +namespace Engine.Core; + +/// +/// First-person / free-fly camera controller. +/// WASD moves on the ground plane, Q/E move up/down, Shift boosts speed. +/// Mouse look while right mouse button is held. +/// +public sealed class FreeFlyCameraController : ICameraController +{ + private readonly Entity _cameraEntity; + private float _yaw; + private float _pitch; + private float _speed = 3.0f; + private float _fastSpeed = 8.0f; + private float _mouseSensitivity = 0.003f; + private int _lastMouseX; + private int _lastMouseY; + private bool _wasRightMouseDown; + private Vector3 _position; + + public string Name => "FreeFly"; + + public FreeFlyCameraController(Entity cameraEntity) + { + _cameraEntity = cameraEntity; + var camera = cameraEntity.Get(); + _position = camera.Position; + + var forward = Vector3.Normalize(camera.Target - camera.Position); + _pitch = MathF.Asin(-forward.Y); + _yaw = MathF.Atan2(forward.X, forward.Z); + } + + public void Update(InputMapping input, float deltaTime) + { + var move = Vector3.Zero; + var forward = new Vector3(MathF.Sin(_yaw), 0.0f, MathF.Cos(_yaw)); + var right = new Vector3(MathF.Cos(_yaw), 0.0f, -MathF.Sin(_yaw)); + var up = Vector3.UnitY; + + if (input.IsKeyDown(SDL_Keycode.SDLK_W)) + move += forward; + if (input.IsKeyDown(SDL_Keycode.SDLK_S)) + move -= forward; + if (input.IsKeyDown(SDL_Keycode.SDLK_A)) + move -= right; + if (input.IsKeyDown(SDL_Keycode.SDLK_D)) + move += right; + if (input.IsKeyDown(SDL_Keycode.SDLK_E)) + move += up; + if (input.IsKeyDown(SDL_Keycode.SDLK_Q)) + move -= up; + + if (move.LengthSquared() > 0.0f) + { + move = Vector3.Normalize(move); + var speed = input.IsKeyDown(SDL_Keycode.SDLK_LSHIFT) ? _fastSpeed : _speed; + _position += move * speed * deltaTime; + } + + if (input.MouseWheelDelta != 0) + { + _speed *= 1.0f + input.MouseWheelDelta * 0.1f; + _speed = Math.Clamp(_speed, 0.5f, 30.0f); + } + + if (input.MouseRight) + { + if (!_wasRightMouseDown) + { + _lastMouseX = input.MouseX; + _lastMouseY = input.MouseY; + _wasRightMouseDown = true; + } + else + { + var dx = input.MouseX - _lastMouseX; + var dy = input.MouseY - _lastMouseY; + _yaw += dx * _mouseSensitivity; + _pitch += dy * _mouseSensitivity; + _pitch = Math.Clamp(_pitch, -MathF.PI / 2.0f + 0.01f, MathF.PI / 2.0f - 0.01f); + _lastMouseX = input.MouseX; + _lastMouseY = input.MouseY; + } + } + else + { + _wasRightMouseDown = false; + } + + UpdateCamera(); + } + + private void UpdateCamera() + { + var camera = _cameraEntity.Get(); + camera.Position = _position; + + var direction = new Vector3( + MathF.Cos(_pitch) * MathF.Sin(_yaw), + -MathF.Sin(_pitch), + MathF.Cos(_pitch) * MathF.Cos(_yaw)); + + camera.Target = _position + direction; + camera.Up = Vector3.UnitY; + _cameraEntity.Set(camera); + } +} diff --git a/src/Engine.Core/ICameraController.cs b/src/Engine.Core/ICameraController.cs new file mode 100644 index 0000000..be141b5 --- /dev/null +++ b/src/Engine.Core/ICameraController.cs @@ -0,0 +1,10 @@ +namespace Engine.Core; + +/// +/// Common interface for camera controllers (orbit, free-fly, etc.). +/// +public interface ICameraController +{ + string Name { get; } + void Update(InputMapping input, float deltaTime); +} diff --git a/src/Engine.Core/OrbitCameraController.cs b/src/Engine.Core/OrbitCameraController.cs index 5f512ea..84135e7 100644 --- a/src/Engine.Core/OrbitCameraController.cs +++ b/src/Engine.Core/OrbitCameraController.cs @@ -8,7 +8,7 @@ namespace Engine.Core; /// Orbit camera controller. Right mouse drag rotates around the target, /// mouse wheel zooms in/out. /// -public sealed class OrbitCameraController +public sealed class OrbitCameraController : ICameraController { private readonly Entity _cameraEntity; private float _distance; @@ -19,6 +19,8 @@ public sealed class OrbitCameraController private int _lastMouseY; private bool _isDragging; + public string Name => "Orbit"; + public OrbitCameraController(Entity cameraEntity, Vector3? target = null) { _cameraEntity = cameraEntity;