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.
This commit is contained in:
@@ -529,7 +529,9 @@ In Release (NativeAOT), the MCP server and ASP.NET Core are excluded. The AI can
|
|||||||
│ │ ├── Sdl3Window.cs # SDL3 window wrapper
|
│ │ ├── Sdl3Window.cs # SDL3 window wrapper
|
||||||
│ │ ├── Timing.cs # DeltaTime, fixed timestep
|
│ │ ├── Timing.cs # DeltaTime, fixed timestep
|
||||||
│ │ ├── InputMapping.cs # Keyboard, mouse, gamepad input
|
│ │ ├── InputMapping.cs # Keyboard, mouse, gamepad input
|
||||||
|
│ │ ├── ICameraController.cs # Camera controller interface
|
||||||
│ │ ├── OrbitCameraController.cs # Mouse orbit camera
|
│ │ ├── OrbitCameraController.cs # Mouse orbit camera
|
||||||
|
│ │ ├── FreeFlyCameraController.cs # WASD + mouse look camera
|
||||||
│ │ └── Components/ # Transform, Camera, Light, Material, Mesh
|
│ │ └── Components/ # Transform, Camera, Light, Material, Mesh
|
||||||
│ │
|
│ │
|
||||||
│ ├── Engine.Data/
|
│ ├── 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`.
|
- `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`.
|
- 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
|
### 13.5 MCP Client Config
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Engine.AI;
|
using Engine.AI;
|
||||||
|
using SDL;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
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 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));
|
.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);
|
var texturePath = GenerateCheckerboardTexture("Content/checkerboard.png", 256);
|
||||||
|
|
||||||
@@ -159,8 +165,15 @@ class Program
|
|||||||
camera.AspectRatio = (float)lastWidth / lastHeight;
|
camera.AspectRatio = (float)lastWidth / lastHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update orbit camera from mouse input.
|
// Toggle camera controller with F.
|
||||||
orbit.Update(input, (float)timing.DeltaTime);
|
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.
|
// Slowly rotate the model so we can see it in 3D.
|
||||||
ref var modelTransform = ref model.Ensure<Transform>();
|
ref var modelTransform = ref model.Ensure<Transform>();
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Flecs.NET.Core;
|
||||||
|
using SDL;
|
||||||
|
using Engine.Core.Components;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<Camera>();
|
||||||
|
_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>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for camera controllers (orbit, free-fly, etc.).
|
||||||
|
/// </summary>
|
||||||
|
public interface ICameraController
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
void Update(InputMapping input, float deltaTime);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace Engine.Core;
|
|||||||
/// Orbit camera controller. Right mouse drag rotates around the target,
|
/// Orbit camera controller. Right mouse drag rotates around the target,
|
||||||
/// mouse wheel zooms in/out.
|
/// mouse wheel zooms in/out.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class OrbitCameraController
|
public sealed class OrbitCameraController : ICameraController
|
||||||
{
|
{
|
||||||
private readonly Entity _cameraEntity;
|
private readonly Entity _cameraEntity;
|
||||||
private float _distance;
|
private float _distance;
|
||||||
@@ -19,6 +19,8 @@ public sealed class OrbitCameraController
|
|||||||
private int _lastMouseY;
|
private int _lastMouseY;
|
||||||
private bool _isDragging;
|
private bool _isDragging;
|
||||||
|
|
||||||
|
public string Name => "Orbit";
|
||||||
|
|
||||||
public OrbitCameraController(Entity cameraEntity, Vector3? target = null)
|
public OrbitCameraController(Entity cameraEntity, Vector3? target = null)
|
||||||
{
|
{
|
||||||
_cameraEntity = cameraEntity;
|
_cameraEntity = cameraEntity;
|
||||||
|
|||||||
Reference in New Issue
Block a user