feat: FreeFlyCameraController — interactive WASD + mouse look
- Program.cs: create Camera entity in ECS World, FreeFlyCameraController with WASD movement, Q/E up/down, Shift boost, right-click mouse look - VulkanRenderer.RenderWorld: queries Camera from World, computes VP from Camera.GetViewMatrix() * GetProjectionMatrix() - Camera aspect ratio auto-updated on window resize - Fallback to hardcoded VP if no Camera entity found - Torus knot still rotating via push constant model matrix - 0 validation errors
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
|
using System.Numerics;
|
||||||
using Engine.Core;
|
using Engine.Core;
|
||||||
|
using Engine.Core.Components;
|
||||||
using Engine.Graphics;
|
using Engine.Graphics;
|
||||||
using Engine.Graphics.Vulkan;
|
using Engine.Graphics.Vulkan;
|
||||||
using Flecs.NET.Core;
|
using Flecs.NET.Core;
|
||||||
@@ -9,17 +11,31 @@ class Program
|
|||||||
{
|
{
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Cortex Engine — Vulkan Triangle (pure P/Invoke)...");
|
Console.WriteLine("Cortex Engine — Vulkan (pure P/Invoke)...");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
VulkanBackendRegistrar.EnsureRegistered();
|
VulkanBackendRegistrar.EnsureRegistered();
|
||||||
using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true);
|
using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true);
|
||||||
var window = renderContext.Window;
|
var window = renderContext.Window;
|
||||||
|
var input = window.Input;
|
||||||
using var renderer = renderContext.CreateRenderer();
|
using var renderer = renderContext.CreateRenderer();
|
||||||
|
|
||||||
using var world = World.Create();
|
using var world = World.Create();
|
||||||
|
|
||||||
|
var cameraEntity = world.Entity("Camera")
|
||||||
|
.Set(new Camera(
|
||||||
|
new Vector3(0, 0, -6),
|
||||||
|
new Vector3(0, 0, 0),
|
||||||
|
Vector3.UnitY,
|
||||||
|
MathF.PI / 4f,
|
||||||
|
1280f / 720f,
|
||||||
|
0.1f,
|
||||||
|
100f));
|
||||||
|
|
||||||
|
var cameraController = new FreeFlyCameraController(cameraEntity);
|
||||||
|
Console.WriteLine("Camera: FreeFly (WASD + right-click mouse look, Q/E up/down, Shift boost)");
|
||||||
|
|
||||||
var lastWidth = window.Width;
|
var lastWidth = window.Width;
|
||||||
var lastHeight = window.Height;
|
var lastHeight = window.Height;
|
||||||
var frames = 0;
|
var frames = 0;
|
||||||
@@ -30,14 +46,20 @@ class Program
|
|||||||
{
|
{
|
||||||
timing.Tick();
|
timing.Tick();
|
||||||
window.PumpEvents();
|
window.PumpEvents();
|
||||||
|
input.BeginFrame();
|
||||||
|
|
||||||
if (window.Width != lastWidth || window.Height != lastHeight)
|
if (window.Width != lastWidth || window.Height != lastHeight)
|
||||||
{
|
{
|
||||||
lastWidth = window.Width;
|
lastWidth = window.Width;
|
||||||
lastHeight = window.Height;
|
lastHeight = window.Height;
|
||||||
renderContext.Resize(lastWidth, lastHeight);
|
renderContext.Resize(lastWidth, lastHeight);
|
||||||
|
ref var cam = ref cameraEntity.Ensure<Camera>();
|
||||||
|
cam.AspectRatio = (float)lastWidth / lastHeight;
|
||||||
|
cameraEntity.Set(cam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cameraController.Update(input, (float)timing.DeltaTime);
|
||||||
|
|
||||||
renderer.RenderWorld(world);
|
renderer.RenderWorld(world);
|
||||||
|
|
||||||
frames++;
|
frames++;
|
||||||
|
|||||||
@@ -49,28 +49,29 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
|||||||
ctx.GraphicsQueue, ctx, mesh.Indices);
|
ctx.GraphicsQueue, ctx, mesh.Indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mesh LoadMesh(string path)
|
|
||||||
{
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
|
||||||
var altPath = Path.Combine(AppContext.BaseDirectory, path);
|
|
||||||
if (!File.Exists(altPath))
|
|
||||||
{
|
|
||||||
altPath = Path.Combine(AppContext.BaseDirectory, "Content", Path.GetFileName(path));
|
|
||||||
if (!File.Exists(altPath))
|
|
||||||
throw new FileNotFoundException($"Mesh file not found: {path}");
|
|
||||||
}
|
|
||||||
return ObjLoader.Load(altPath, new Vector3(0.8f, 0.6f, 0.3f));
|
|
||||||
}
|
|
||||||
return ObjLoader.Load(path, new Vector3(0.8f, 0.6f, 0.3f));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RenderWorld(World world)
|
public void RenderWorld(World world)
|
||||||
{
|
{
|
||||||
Render();
|
var vp = Matrix4x4.Identity;
|
||||||
|
var found = false;
|
||||||
|
world.Each((Entity e, ref Camera cam) =>
|
||||||
|
{
|
||||||
|
var aspect = (float)_swapchain.Extent.Width / (float)_swapchain.Extent.Height;
|
||||||
|
cam.AspectRatio = aspect;
|
||||||
|
vp = cam.GetViewMatrix() * cam.GetProjectionMatrix();
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
var aspect = (float)_swapchain.Extent.Width / (float)_swapchain.Extent.Height;
|
||||||
|
vp = Matrix4x4.CreateLookAt(new Vector3(0, 0, -6), Vector3.Zero, Vector3.UnitY)
|
||||||
|
* Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 4f, aspect, 0.1f, 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Render(vp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render()
|
private void Render(Matrix4x4 vp)
|
||||||
{
|
{
|
||||||
_frameResources.WaitFrame(_frameIndex);
|
_frameResources.WaitFrame(_frameIndex);
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
|||||||
{
|
{
|
||||||
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
||||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
||||||
Render();
|
Render(vp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +92,6 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
|||||||
|
|
||||||
_totalTime += 0.016f;
|
_totalTime += 0.016f;
|
||||||
|
|
||||||
var vp = ComputeViewProjection();
|
|
||||||
_frameResources.UpdateUbo(_frameIndex, &vp, VulkanFrameResources.UboSize);
|
_frameResources.UpdateUbo(_frameIndex, &vp, VulkanFrameResources.UboSize);
|
||||||
|
|
||||||
var cmd = _frameResources.CommandBuffers[_frameIndex];
|
var cmd = _frameResources.CommandBuffers[_frameIndex];
|
||||||
@@ -263,14 +263,6 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
|||||||
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
_frameIndex = (_frameIndex + 1) % VulkanFrameResources.MaxFramesInFlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Matrix4x4 ComputeViewProjection()
|
|
||||||
{
|
|
||||||
var aspect = (float)_swapchain.Extent.Width / (float)_swapchain.Extent.Height;
|
|
||||||
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 4f, aspect, 0.1f, 100f);
|
|
||||||
var view = Matrix4x4.CreateLookAt(new Vector3(0, 0, -6), Vector3.Zero, Vector3.UnitY);
|
|
||||||
return view * proj;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
private static void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
||||||
VkImageLayout oldLayout, VkImageLayout newLayout,
|
VkImageLayout oldLayout, VkImageLayout newLayout,
|
||||||
ulong srcStage, ulong srcAccess,
|
ulong srcStage, ulong srcAccess,
|
||||||
@@ -337,6 +329,22 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
|||||||
Vk.vkCmdPipelineBarrier2(cmd, &depInfo);
|
Vk.vkCmdPipelineBarrier2(cmd, &depInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Mesh LoadMesh(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
var altPath = Path.Combine(AppContext.BaseDirectory, path);
|
||||||
|
if (!File.Exists(altPath))
|
||||||
|
{
|
||||||
|
altPath = Path.Combine(AppContext.BaseDirectory, "Content", Path.GetFileName(path));
|
||||||
|
if (!File.Exists(altPath))
|
||||||
|
throw new FileNotFoundException($"Mesh file not found: {path}");
|
||||||
|
}
|
||||||
|
return ObjLoader.Load(altPath, new Vector3(0.8f, 0.6f, 0.3f));
|
||||||
|
}
|
||||||
|
return ObjLoader.Load(path, new Vector3(0.8f, 0.6f, 0.3f));
|
||||||
|
}
|
||||||
|
|
||||||
private static byte[] LoadShader(string path)
|
private static byte[] LoadShader(string path)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
|
|||||||
Reference in New Issue
Block a user