From dbe7aebd4fcc30471049bef7a6cccdf4ced37169 Mon Sep 17 00:00:00 2001 From: lionarius Date: Mon, 16 Dec 2024 04:28:45 +0300 Subject: [PATCH] . --- DoomDeathmatch.sln | 12 +- DoomDeathmatch/DoomDeathmatch.csproj | 10 +- .../src/Component/ControllerComponent.cs | 33 ++++ .../src/Component/RotateComponent.cs | 22 +++ DoomDeathmatch/src/DoomDeathmatch.cs | 37 ++++ DoomDeathmatch/src/Program.cs | 12 -- DoomDeathmatch/src/QuadVertex.cs | 17 -- Engine/assets/shader/mesh.shader | 27 ++- Engine/src/Asset/Mesh/Loader/IMeshLoader.cs | 4 +- .../Asset/Mesh/Loader/MeshLoaderParameters.cs | 10 +- Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs | 24 +-- Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs | 21 ++- Engine/src/Engine.cs | 68 +++++-- Engine/src/EngineBuilder.cs | 8 - Engine/src/Graphics/Buffer/VertexArray.cs | 17 +- .../src/Graphics/Framebuffer/Framebuffer.cs | 27 +-- .../Framebuffer/FramebufferBuilder.cs | 2 +- .../Framebuffer/IFramebufferAttachment.cs | 11 +- .../src/Graphics/Framebuffer/Renderbuffer.cs | 7 +- Engine/src/Graphics/IPresenter.cs | 2 +- Engine/src/Graphics/OpenGLObject.cs | 14 +- .../src/Graphics/Render/InstancedRenderer.cs | 8 +- .../Render/Mesh/GlobalMeshRenderer.cs | 54 ++++++ .../Render/Mesh/MeshInstanceVertex.cs | 1 + .../src/Graphics/Render/Mesh/MeshRenderer.cs | 38 +++- Engine/src/Graphics/Renderer.cs | 28 ++- Engine/src/Graphics/Texture/DynamicTexture.cs | 7 +- Engine/src/Input/IInputHandler.cs | 9 +- .../{KeyCode.cs => KeyboardButtonCode.cs} | 6 +- Engine/src/Input/MouseButton.cs | 13 -- Engine/src/Input/MouseButtonCode.cs | 10 + Engine/src/Input/WindowInputHandler.cs | 99 ++++++++++ Engine/src/Scene/Component/BuiltIn/Camera.cs | 1 + .../Component/BuiltIn/OrthographicCamera.cs | 8 +- .../Component/BuiltIn/PerspectiveCamera.cs | 8 +- .../BuiltIn/Renderer/Box2DRenderer.cs | 17 ++ .../BuiltIn/Renderer/MeshRenderer.cs | 13 ++ .../src/Scene/Component/BuiltIn/Transform.cs | 17 +- Engine/src/Scene/GameObject.cs | 4 +- Engine/src/Scene/Hierarchy.cs | 17 +- Engine/src/Scene/Scene.cs | 12 +- Engine/src/Scene/SceneManager.cs | 4 +- Engine/src/Window.cs | 18 +- EngineTests/src/Scene/HierarchyTests.cs | 2 +- PresenterConsole/PresenterConsole.csproj | 1 + PresenterConsole/assets/shader/ascii.shader | 18 +- PresenterConsole/src/ConsoleFastOutput.cs | 7 +- PresenterConsole/src/ConsoleInputHandler.cs | 176 ++++++++---------- PresenterConsole/src/ConsolePresenter.cs | 43 ++--- PresenterConsole/src/Program.cs | 9 +- PresenterConsole/src/WindowsFFI.cs | 7 + PresenterNative/PresenterNative.csproj | 1 + PresenterNative/src/Program.cs | 4 + PresenterWpf/PresenterWpf.csproj | 1 + PresenterWpf/src/App.xaml.cs | 50 +++++ PresenterWpf/src/MainWindow.xaml.cs | 6 +- PresenterWpf/src/WPFInputHandler.cs | 167 +++++++++++++++++ 57 files changed, 895 insertions(+), 374 deletions(-) create mode 100644 DoomDeathmatch/src/Component/ControllerComponent.cs create mode 100644 DoomDeathmatch/src/Component/RotateComponent.cs create mode 100644 DoomDeathmatch/src/DoomDeathmatch.cs delete mode 100644 DoomDeathmatch/src/Program.cs delete mode 100644 DoomDeathmatch/src/QuadVertex.cs create mode 100644 Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs rename Engine/src/Input/{KeyCode.cs => KeyboardButtonCode.cs} (81%) delete mode 100644 Engine/src/Input/MouseButton.cs create mode 100644 Engine/src/Input/MouseButtonCode.cs create mode 100644 Engine/src/Input/WindowInputHandler.cs create mode 100644 Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs create mode 100644 Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs create mode 100644 PresenterWpf/src/WPFInputHandler.cs diff --git a/DoomDeathmatch.sln b/DoomDeathmatch.sln index 15066e3..3dc2ea0 100644 --- a/DoomDeathmatch.sln +++ b/DoomDeathmatch.sln @@ -1,7 +1,5 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatch", "DoomDeathmatch\DoomDeathmatch.csproj", "{7AE4D009-6590-4E44-9B68-FE37B6650F70}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{5EE134DE-2275-40C0-8B9D-4EFF22474F63}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{CC28C26C-0998-4C13-8855-978658B8B0D6}" @@ -12,16 +10,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterConsole", "Present EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterNative", "PresenterNative\PresenterNative.csproj", "{3B8B7867-5B38-4013-A366-159083F5C095}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatch", "DoomDeathmatch\DoomDeathmatch.csproj", "{4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7AE4D009-6590-4E44-9B68-FE37B6650F70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7AE4D009-6590-4E44-9B68-FE37B6650F70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7AE4D009-6590-4E44-9B68-FE37B6650F70}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7AE4D009-6590-4E44-9B68-FE37B6650F70}.Release|Any CPU.Build.0 = Release|Any CPU {5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Debug|Any CPU.Build.0 = Debug|Any CPU {5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -42,5 +38,9 @@ Global {3B8B7867-5B38-4013-A366-159083F5C095}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B8B7867-5B38-4013-A366-159083F5C095}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B8B7867-5B38-4013-A366-159083F5C095}.Release|Any CPU.Build.0 = Release|Any CPU + {4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/DoomDeathmatch/DoomDeathmatch.csproj b/DoomDeathmatch/DoomDeathmatch.csproj index 025d582..e1baa34 100644 --- a/DoomDeathmatch/DoomDeathmatch.csproj +++ b/DoomDeathmatch/DoomDeathmatch.csproj @@ -1,21 +1,13 @@  - Exe net8.0 enable enable - - - - - - - - + diff --git a/DoomDeathmatch/src/Component/ControllerComponent.cs b/DoomDeathmatch/src/Component/ControllerComponent.cs new file mode 100644 index 0000000..fbb0131 --- /dev/null +++ b/DoomDeathmatch/src/Component/ControllerComponent.cs @@ -0,0 +1,33 @@ +using Engine.Input; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Component; + +public class ControllerComponent : Engine.Scene.Component.Component +{ + public float Speed { get; set; } = 10.0f; + + private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + + public override void Update(double parDeltaTime) + { + var movement = Vector3.Zero; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.W)) + movement.Z += 1; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.S)) + movement.Z -= 1; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.A)) + movement.X -= 1; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.D)) + movement.X += 1; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Space)) + movement.Y += 1; + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Shift)) + movement.Y -= 1; + + if (movement.LengthSquared > 0) + movement.Normalize(); + + GameObject.Transform.Translation += movement * Speed * (float)parDeltaTime; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Component/RotateComponent.cs b/DoomDeathmatch/src/Component/RotateComponent.cs new file mode 100644 index 0000000..d961dd9 --- /dev/null +++ b/DoomDeathmatch/src/Component/RotateComponent.cs @@ -0,0 +1,22 @@ +using Engine.Input; +using OpenTK.Mathematics; + +namespace DoomDeathmatch.Component; + +public class RotateComponent : Engine.Scene.Component.Component +{ + private readonly IInputHandler _inputHandler = Engine.Engine.Instance.InputHandler!; + + public override void Update(double parDeltaTime) + { + if (_inputHandler.IsKeyPressed(KeyboardButtonCode.Q)) + GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, (float)parDeltaTime * 0.5f); + else if (_inputHandler.IsKeyPressed(KeyboardButtonCode.E)) + GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, -(float)parDeltaTime * 0.5f); + + if (_inputHandler.IsMouseButtonPressed(MouseButtonCode.Left)) + GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, (float)parDeltaTime * 0.5f); + else if (_inputHandler.IsMouseButtonPressed(MouseButtonCode.Right)) + GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, -(float)parDeltaTime * 0.5f); + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/DoomDeathmatch.cs b/DoomDeathmatch/src/DoomDeathmatch.cs new file mode 100644 index 0000000..e4a3b16 --- /dev/null +++ b/DoomDeathmatch/src/DoomDeathmatch.cs @@ -0,0 +1,37 @@ +using DoomDeathmatch.Component; +using Engine.Asset.Mesh.Loader; +using Engine.Scene; +using Engine.Scene.Component.BuiltIn; +using Engine.Scene.Component.BuiltIn.Renderer; +using OpenTK.Mathematics; + +namespace DoomDeathmatch; + +public static class DoomDeathmatch +{ + public static void Initialize(Engine.Engine parEngine) + { + parEngine.SceneManager.TransitionTo(MainScene()); + } + + private static Scene MainScene() + { + var cameraObject = new GameObject(); + cameraObject.Transform.Translation.Z = -6; + cameraObject.AddComponent(); + cameraObject.AddComponent(); + + using var reader = new StreamReader("../DoomDeathmatch/asset/model/test2.obj"); + + var box2dRenderer = new GameObject(); + box2dRenderer.AddComponent(new MeshRenderer { Mesh = ObjMeshLoader.Load(reader) }); + box2dRenderer.AddComponent(); + box2dRenderer.AddComponent(); + + var scene = new Scene(); + scene.Add(cameraObject); + scene.Add(box2dRenderer); + + return scene; + } +} \ No newline at end of file diff --git a/DoomDeathmatch/src/Program.cs b/DoomDeathmatch/src/Program.cs deleted file mode 100644 index 5f53be4..0000000 --- a/DoomDeathmatch/src/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Diagnostics; -using System.Drawing; -using System.Runtime.InteropServices; - -namespace DoomDeathmatch; - -internal abstract class Program -{ - public static void Main(string[] args) - { - } -} \ No newline at end of file diff --git a/DoomDeathmatch/src/QuadVertex.cs b/DoomDeathmatch/src/QuadVertex.cs deleted file mode 100644 index f26909f..0000000 --- a/DoomDeathmatch/src/QuadVertex.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Engine.Graphics.Buffer.Vertex; -using OpenTK.Graphics.OpenGL; -using OpenTK.Mathematics; -using Half = System.Half; - -namespace DoomDeathmatch; - -public struct QuadVertex : IVertex -{ - [Vertex(VertexAttribType.Float, 2)] public Vector2 Position2; - - [Vertex(VertexAttribType.Float, 2)] public Vector2 Position; - - [Vertex(VertexAttribType.Float, 2)] public Vector2 Position4; - - [Vertex(VertexAttribType.Float, 2)] public Vector2 Position3; -} \ No newline at end of file diff --git a/Engine/assets/shader/mesh.shader b/Engine/assets/shader/mesh.shader index 3d4ec80..89fccde 100644 --- a/Engine/assets/shader/mesh.shader +++ b/Engine/assets/shader/mesh.shader @@ -6,29 +6,40 @@ uniform mat4 uViewMatrix; layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; -layout (location = 3) in mat4 aModelMatrix; +layout (location = 2) in vec2 aUV; +layout (location = 3) in int aTextureId; +layout (location = 4) in mat4 aModelMatrix; -layout (location = 0) out vec2 oTexCoords; +layout (location = 0) out vec2 oUV; +layout (location = 1) out int oTextureId; +layout (location = 2) out vec3 oNormal; void main() { + oUV = aUV; + oTextureId = aTextureId; + oNormal = aNormal; gl_Position = uProjectionMatrix * uViewMatrix * aModelMatrix * vec4(aPos, 1.0); - oTexCoords = aTexCoords; } // #type fragment #version 460 core -uniform sampler2D uTexture; +uniform sampler2D uTexture[16]; -layout (location = 0) in vec2 iTexCoords; +layout (location = 0) in vec2 iUV; +layout (location = 1) flat in int iTextureId; +layout (location = 2) in vec3 iNormal; layout (location = 0) out vec4 FragColor; void main() { - FragColor = texture(uTexture, iTexCoords); + vec3 lightColor = vec3(iUV, 0); + FragColor = vec4(lightColor, 1.0); + if (iTextureId >= 0) + FragColor *= texture(uTexture[iTextureId], iUV); + if (FragColor.a == 0.0) - discard; + discard; } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs index c5871ec..b62a427 100644 --- a/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs @@ -2,9 +2,9 @@ public interface IMeshLoader { - public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default); + public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default); - public static Mesh Optimize(Mesh parMesh) + internal static Mesh Optimize(Mesh parMesh) { var optimizedMesh = new Mesh(); var vertexMap = new Dictionary(); diff --git a/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs b/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs index 09a8d79..995be3e 100644 --- a/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs +++ b/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs @@ -3,10 +3,10 @@ [Flags] public enum MeshLoaderParameters { - _none = 0, - _loadNormals = 1 << 0, - _loadUVs = 1 << 1, - _optimize = 1 << 2, + None = 0, + LoadNormals = 1 << 0, + LoadUVs = 1 << 1, + Optimize = 1 << 2, - _default = _loadNormals | _loadUVs | _optimize + Default = LoadNormals | LoadUVs | Optimize } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs index ff3fc6b..655bfad 100644 --- a/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs @@ -7,12 +7,16 @@ public class ObjMeshLoader : IMeshLoader { private static readonly ObjMeshLoader _instance = new(); - public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default) + public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) { - return _instance.LoadMesh(parPath, parAmeters); + return _instance.LoadMesh(parReader, parParameters); } - public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default) + private ObjMeshLoader() + { + } + + public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) { var mesh = new Mesh(); @@ -21,13 +25,11 @@ public class ObjMeshLoader : IMeshLoader var tempUVs = new List(); var index = 0u; - var loadNormals = parAmeters.HasFlag(MeshLoaderParameters._loadNormals); - var loadUVs = parAmeters.HasFlag(MeshLoaderParameters._loadUVs); - - using var reader = new StreamReader(parPath); - while (reader.ReadLine() is { } line) + var loadNormals = parParameters.HasFlag(MeshLoaderParameters.LoadNormals); + var loadUVs = parParameters.HasFlag(MeshLoaderParameters.LoadUVs); + while (parReader.ReadLine() is { } line) { - string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 0 || parts[0].StartsWith('#')) { @@ -62,7 +64,7 @@ public class ObjMeshLoader : IMeshLoader case "f": for (var i = 1; i <= 3; i++) { - string[]? faceComponents = parts[i].Split('/'); + var faceComponents = parts[i].Split('/'); var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] }; if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "") @@ -83,7 +85,7 @@ public class ObjMeshLoader : IMeshLoader } } - if (parAmeters.HasFlag(MeshLoaderParameters._optimize)) + if (parParameters.HasFlag(MeshLoaderParameters.Optimize)) { mesh = IMeshLoader.Optimize(mesh); } diff --git a/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs index 28078ae..91e05da 100644 --- a/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs @@ -7,20 +7,25 @@ public class StlMeshLoader : IMeshLoader { private static readonly StlMeshLoader _instance = new(); - public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default) + public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) { - return _instance.LoadMesh(parPath, parAmeters); + return _instance.LoadMesh(parReader, parParameters); } - public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default) + private StlMeshLoader() + { + } + + public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) { var mesh = new Mesh(); + var loadNormals = parParameters.HasFlag(MeshLoaderParameters.LoadNormals); + var currentNormal = new Vector3(); var index = 0u; - using var reader = new StreamReader(parPath); - while (reader.ReadLine() is { } line) + while (parReader.ReadLine() is { } line) { line = line.Trim(); @@ -35,11 +40,11 @@ public class StlMeshLoader : IMeshLoader break; } - string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); switch (parts[0]) { - case "facet" when parts[1] == "normal" && parAmeters.HasFlag(MeshLoaderParameters._loadNormals): + case "facet" when parts[1] == "normal" && loadNormals: currentNormal = new Vector3( float.Parse(parts[2], CultureInfo.InvariantCulture), float.Parse(parts[3], CultureInfo.InvariantCulture), @@ -61,7 +66,7 @@ public class StlMeshLoader : IMeshLoader } } - if (parAmeters.HasFlag(MeshLoaderParameters._optimize)) + if (parParameters.HasFlag(MeshLoaderParameters.Optimize)) { mesh = IMeshLoader.Optimize(mesh); } diff --git a/Engine/src/Engine.cs b/Engine/src/Engine.cs index 1ec77b1..8bf215b 100644 --- a/Engine/src/Engine.cs +++ b/Engine/src/Engine.cs @@ -12,6 +12,8 @@ namespace Engine; public sealed class Engine { + public static Engine Instance { get; private set; } = null!; + public Renderer Renderer { get; } public SceneManager SceneManager { get; } = new(); @@ -36,10 +38,10 @@ public sealed class Engine } } - internal IInputHandler? InputHandler + public IInputHandler? InputHandler { get => _inputHandler; - set => _inputHandler = value; + internal set => _inputHandler = value; } private readonly ILogger _logger; @@ -51,6 +53,13 @@ public sealed class Engine public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, ILogger parLogger) { + if (Instance != null) + { + throw new InvalidOperationException("Engine is already running"); + } + + Instance = this; + var settings = new NativeWindowSettings { ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight), @@ -67,46 +76,69 @@ public sealed class Engine _logger = Log.ForContext(); } + private readonly object _sceneLock = new(); + public void Run() { - _updateThread = new Thread(RunUpdate); + _updateThread = new Thread(RunUpdate) { Name = "UpdateThread" }; _updateThread.Start(); - var timer = Stopwatch.StartNew(); - var deltaTime = 0.0; + RunRender(); + + _updateThread.Join(); + } + + private void RunRender() + { while (!Presenter?.IsExiting ?? false) { Renderer.StartFrame(); - GL.ClearColor(0.6f, 0.0f, 0.6f, 1.0f); - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + var view = Matrix4.Identity; + var projection = Matrix4.Identity; + lock (_sceneLock) + { + var camera = SceneManager.CurrentScene?.MainCamera; + if (camera != null) + { + camera.ScreenSize = new Vector2i(Renderer.ViewportWidth, Renderer.ViewportHeight); + view = camera.View; + projection = camera.Projection; + } - Renderer.QuadRenderer.Commit( - Matrix4.CreateRotationZ((float)deltaTime), - new Vector4((MathF.Sin((float)deltaTime) + 1.0f) / 2, (MathF.Sin((float)deltaTime) + 1.0f) / 2, - (MathF.Sin((float)deltaTime) + 1.0f) / 2, 1.0f)); - Renderer.QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity); + SceneManager.Render(); + } + + Renderer.QuadRenderer.Render(projection, view); Renderer.QuadRenderer.Reset(); + Renderer.GlobalMeshRenderer.Render(projection, view); + Renderer.GlobalMeshRenderer.Reset(); + Renderer.EndFrame(); Presenter!.Present(Renderer.TextureInternal); - Presenter!.Update(deltaTime); - deltaTime = timer.Elapsed.TotalSeconds; + Presenter!.Render(); } - - _updateThread.Join(); } private void RunUpdate() { var timer = Stopwatch.StartNew(); var deltaTime = 0.0; + while (!Presenter?.IsExiting ?? false) { - SceneManager.Update(deltaTime); - timer.Restart(); + Presenter!.Update(deltaTime); + InputHandler?.Update(deltaTime); + + lock (_sceneLock) + { + SceneManager.Update(deltaTime); + } + deltaTime = timer.Elapsed.TotalSeconds; + timer.Restart(); } } diff --git a/Engine/src/EngineBuilder.cs b/Engine/src/EngineBuilder.cs index 264a3b2..21efb22 100644 --- a/Engine/src/EngineBuilder.cs +++ b/Engine/src/EngineBuilder.cs @@ -23,8 +23,6 @@ public sealed class EngineBuilder private string? _logFilePath; private LogEventLevel _logLevel = LogEventLevel.Information; - public EngineBuilder() { } - public EngineBuilder Title(string parTitle) { _title = parTitle; @@ -53,12 +51,6 @@ public sealed class EngineBuilder return this; } - public EngineBuilder Presenter(IPresenter parPresenter) - { - _presenterFunc = _ => parPresenter; - return this; - } - public EngineBuilder Presenter(Func parPresenterFunc) { _presenterFunc = parPresenterFunc; diff --git a/Engine/src/Graphics/Buffer/VertexArray.cs b/Engine/src/Graphics/Buffer/VertexArray.cs index 9a5d0e9..1851176 100644 --- a/Engine/src/Graphics/Buffer/VertexArray.cs +++ b/Engine/src/Graphics/Buffer/VertexArray.cs @@ -8,14 +8,14 @@ namespace Engine.Graphics.Buffer; public class VertexArray : OpenGlObject { - // private IndexBuffer? _boundIndexBuffer; - // private readonly Dictionary> _boundVertexBuffers = new(); private int _enabledAttribs = 0; public VertexArray() { GL.CreateVertexArrays(1, out int handle); Handle = handle; + + Log.Debug("Vertex array {Handle} created", Handle); } public void BindIndexBuffer(IndexBuffer parBuffer) @@ -28,18 +28,11 @@ public class VertexArray : OpenGlObject public void BindVertexBuffer(VertexBuffer parBuffer, int parBindingIndex = 0, int parDivisor = 0) where T : struct, IVertex { - if (parBindingIndex < 0) - { - throw new ArgumentException("Binding index must be greater than 0"); - } - - if (parDivisor < 0) - { - throw new ArgumentException("Divisor must be greater than 0"); - } + ArgumentOutOfRangeException.ThrowIfNegative(parBindingIndex); + ArgumentOutOfRangeException.ThrowIfNegative(parDivisor); var stride = Marshal.SizeOf(); - IOrderedEnumerable? fields = IVertex.GetFields(); + var fields = IVertex.GetFields(); GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride); diff --git a/Engine/src/Graphics/Framebuffer/Framebuffer.cs b/Engine/src/Graphics/Framebuffer/Framebuffer.cs index 3fe3a38..c5c5436 100644 --- a/Engine/src/Graphics/Framebuffer/Framebuffer.cs +++ b/Engine/src/Graphics/Framebuffer/Framebuffer.cs @@ -10,12 +10,9 @@ public class Framebuffer : OpenGlObject public int Width { get => _width; - protected set + private set { - if (value <= 0) - { - throw new ArgumentException("Width must be greater than 0"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); _width = value; } @@ -24,12 +21,9 @@ public class Framebuffer : OpenGlObject public int Height { get => _height; - protected set + private set { - if (value <= 0) - { - throw new ArgumentException("Height must be greater than 0"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value); _height = value; } @@ -51,18 +45,9 @@ public class Framebuffer : OpenGlObject GL.CreateFramebuffers(1, out int handle); Handle = handle; - foreach (KeyValuePair attachment in _attachments) + foreach (var attachment in _attachments) { - switch (attachment.Value.Type) - { - case IFramebufferAttachment.AttachmentType.Texture: - GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0); - break; - case IFramebufferAttachment.AttachmentType.Renderbuffer: - GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer, - attachment.Value.Handle); - break; - } + attachment.Value.Attach(this, attachment.Key); } var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer); diff --git a/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs b/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs index a5cf4b6..0f8c0ef 100644 --- a/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs +++ b/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs @@ -55,7 +55,7 @@ public class FramebufferBuilder(int parWidth, int parHeight) public Framebuffer Build() { - Dictionary? attachments = _attachments + var attachments = _attachments .ToDictionary( parAttachment => parAttachment.FramebufferAttachment, parAttachment => parAttachment.Create(parWidth, parHeight) diff --git a/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs b/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs index 5ae762d..bdcb4e6 100644 --- a/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs +++ b/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs @@ -1,15 +1,12 @@ -namespace Engine.Graphics.Framebuffer; +using OpenTK.Graphics.OpenGL; + +namespace Engine.Graphics.Framebuffer; public interface IFramebufferAttachment { - public AttachmentType Type { get; } internal int Handle { get; } public void Resize(int parWidth, int parHeight); - public enum AttachmentType - { - Texture, - Renderbuffer - } + internal void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment); } \ No newline at end of file diff --git a/Engine/src/Graphics/Framebuffer/Renderbuffer.cs b/Engine/src/Graphics/Framebuffer/Renderbuffer.cs index 301aeda..2075ebe 100644 --- a/Engine/src/Graphics/Framebuffer/Renderbuffer.cs +++ b/Engine/src/Graphics/Framebuffer/Renderbuffer.cs @@ -7,8 +7,6 @@ public class Renderbuffer : OpenGlObject, IFramebufferAttachment public int Width { get; private set; } public int Height { get; private set; } - public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Renderbuffer; - private readonly RenderbufferStorage _format; public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat) @@ -36,6 +34,11 @@ public class Renderbuffer : OpenGlObject, IFramebufferAttachment GL.NamedRenderbufferStorage(Handle, _format, Width, Height); } + public void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment) + { + GL.NamedFramebufferRenderbuffer(parFramebuffer.Handle, parAttachment, RenderbufferTarget.Renderbuffer, Handle); + } + public override void Bind() { GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle); diff --git a/Engine/src/Graphics/IPresenter.cs b/Engine/src/Graphics/IPresenter.cs index 445301b..90ea680 100644 --- a/Engine/src/Graphics/IPresenter.cs +++ b/Engine/src/Graphics/IPresenter.cs @@ -4,7 +4,7 @@ using OpenTK.Windowing.Common; namespace Engine.Graphics; -public interface IPresenter : IUpdate +public interface IPresenter : IUpdate, IRender { public bool IsExiting { get; } public int Width { get; } diff --git a/Engine/src/Graphics/OpenGLObject.cs b/Engine/src/Graphics/OpenGLObject.cs index 2d69d00..6d4a1ac 100644 --- a/Engine/src/Graphics/OpenGLObject.cs +++ b/Engine/src/Graphics/OpenGLObject.cs @@ -13,10 +13,18 @@ public abstract class OpenGlObject ~OpenGlObject() { - Destroy(); + if (Handle == -1) + { + return; + } - Log.Debug("OpenGL object {Handle} destroyed", Handle); + Engine.Instance.Renderer.Schedule(() => + { + Destroy(); - Handle = -1; + Log.Debug("OpenGL object {Handle} destroyed", Handle); + + Handle = -1; + }); } } \ No newline at end of file diff --git a/Engine/src/Graphics/Render/InstancedRenderer.cs b/Engine/src/Graphics/Render/InstancedRenderer.cs index e0a0e9e..56b418c 100644 --- a/Engine/src/Graphics/Render/InstancedRenderer.cs +++ b/Engine/src/Graphics/Render/InstancedRenderer.cs @@ -12,10 +12,10 @@ public abstract class InstancedRenderer { protected readonly Renderer _renderer; - protected readonly IndexBuffer _indexBuffer; - protected readonly VertexBuffer _commonVertexBuffer; - protected readonly VertexBuffer _instanceVertexBuffer; - protected readonly VertexArray _vertexArray; + private readonly IndexBuffer _indexBuffer; + private readonly VertexBuffer _commonVertexBuffer; + private readonly VertexBuffer _instanceVertexBuffer; + private readonly VertexArray _vertexArray; protected readonly int _instanceCount; protected int _queuedInstanceCount; diff --git a/Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs new file mode 100644 index 0000000..0b784d4 --- /dev/null +++ b/Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs @@ -0,0 +1,54 @@ +using Engine.Graphics.Shader; +using OpenTK.Mathematics; + +namespace Engine.Graphics.Render.Mesh; + +public class GlobalMeshRenderer(Renderer parRenderer, int parMaxInstanceCount) +{ + private readonly Dictionary _meshRenderers = new(); + private readonly HashSet _frameMeshes = []; + + private readonly Program _program = ProgramLoader.LoadFromSource(ShaderResource.Mesh); + + public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix) + { + if (_meshRenderers.TryGetValue(parMesh, out var meshRenderer)) + { + meshRenderer.Commit(parModelMatrix); + } + else + { + var newMeshRenderer = new MeshRenderer(parRenderer, parMesh, parMaxInstanceCount, _program); + newMeshRenderer.Commit(parModelMatrix); + + _meshRenderers.Add(parMesh, newMeshRenderer); + } + + _frameMeshes.Add(parMesh); + } + + public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) + { + foreach (var meshRenderer in _meshRenderers.Values) + { + meshRenderer.Render(parProjectionMatrix, parViewMatrix); + } + } + + public void Reset() + { + foreach (var meshRenderer in _meshRenderers.Values) + { + meshRenderer.Reset(); + } + + var meshes = _meshRenderers.Keys; + var unusedMeshes = meshes.Where(parMesh => !_frameMeshes.Contains(parMesh)); + foreach (var unusedMesh in unusedMeshes) + { + _meshRenderers.Remove(unusedMesh); + } + + _frameMeshes.Clear(); + } +} \ No newline at end of file diff --git a/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs b/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs index 49d8a6e..a521dd2 100644 --- a/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs +++ b/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs @@ -6,5 +6,6 @@ namespace Engine.Graphics.Render.Mesh; public struct MeshInstanceVertex : IVertex { + [Vertex(VertexAttribType.Int)] public int _textureId; [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix; } \ No newline at end of file diff --git a/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs index 1b60b0e..49521c0 100644 --- a/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs +++ b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs @@ -1,26 +1,48 @@ using Engine.Graphics.Shader; +using Engine.Graphics.Texture; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; namespace Engine.Graphics.Render.Mesh; -public class MeshRenderer : InstancedRenderer +public class MeshRenderer(Renderer parRenderer, Asset.Mesh.Mesh parMesh, int parInstanceCount, Program parProgram) + : InstancedRenderer( + parRenderer, PrimitiveType.Triangles, + parInstanceCount, + parMesh.Indices.ToArray(), + parMesh.Vertices.ToArray(), + parProgram + ) { - public MeshRenderer(Renderer parRenderer, int parInstanceCount, Asset.Mesh.Mesh parMesh) - : base(parRenderer, PrimitiveType.Triangles, parInstanceCount, parMesh.Indices.ToArray(), - parMesh.Vertices.ToArray(), - ProgramLoader.LoadFromSource(ShaderResource.Mesh)) - { - } + private readonly TextureUnitMap _textureUnitMap = new(16); + private readonly int[] _textureUnitIndices = new int[16]; - public void Commit(Matrix4 parModelMatrix) + public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null) { if (_queuedInstanceCount >= _instanceCount) { throw new InvalidOperationException("Instance count exceeded"); } + var textureId = -1; + if (parTexture != null) + { + textureId = _textureUnitMap.GetUnit(parTexture); + } + + _instanceVertices[_queuedInstanceCount]._textureId = textureId; _instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix; _queuedInstanceCount++; } + + protected override void SetAdditionalUniforms(Program parProgram) + { + foreach (var (texture, unit) in _textureUnitMap.Textures) + { + texture.BindUnit(unit); + _textureUnitIndices[unit] = unit; + } + + parProgram.SetUniform("uTexture", _textureUnitIndices); + } } \ No newline at end of file diff --git a/Engine/src/Graphics/Renderer.cs b/Engine/src/Graphics/Renderer.cs index 5f91f69..b328b78 100644 --- a/Engine/src/Graphics/Renderer.cs +++ b/Engine/src/Graphics/Renderer.cs @@ -1,4 +1,5 @@ using Engine.Graphics.Pixel; +using Engine.Graphics.Render.Mesh; using Engine.Graphics.Render.Quad; using OpenTK.Graphics.OpenGL; @@ -9,11 +10,15 @@ public class Renderer internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!; public QuadRenderer QuadRenderer { get; } + public GlobalMeshRenderer GlobalMeshRenderer { get; } + public int ViewportWidth => _framebuffer.Width; + public int ViewportHeight => _framebuffer.Height; private readonly Framebuffer.Framebuffer _framebuffer; - private readonly Thread _renderThread; + private readonly Queue _scheduleActions = new(); + public Renderer(int parWidth, int parHeight) { Thread.CurrentThread.Name = "RendererThread"; @@ -28,6 +33,7 @@ public class Renderer .Build(); QuadRenderer = new QuadRenderer(this, 1024 * 8); + GlobalMeshRenderer = new GlobalMeshRenderer(this, 1024); } private void InitializeOpenGl(int parWidth, int parHeight) @@ -54,15 +60,25 @@ public class Renderer return; } - throw new InvalidOperationException("Renderer is not on render thread"); + throw new InvalidOperationException("Not on render thread"); #endif } + internal void Schedule(Action parAction) + { + _scheduleActions.Enqueue(parAction); + } + internal void StartFrame() { EnsureRenderThread(); + RunScheduledActions(); + _framebuffer.Bind(); + + GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); } internal void EndFrame() @@ -77,4 +93,12 @@ public class Renderer _framebuffer.Resize(parWidth, parHeight); GL.Viewport(0, 0, parWidth, parHeight); } + + private void RunScheduledActions() + { + while (_scheduleActions.TryDequeue(out var action)) + { + action(); + } + } } \ No newline at end of file diff --git a/Engine/src/Graphics/Texture/DynamicTexture.cs b/Engine/src/Graphics/Texture/DynamicTexture.cs index 7ba4e03..725065e 100644 --- a/Engine/src/Graphics/Texture/DynamicTexture.cs +++ b/Engine/src/Graphics/Texture/DynamicTexture.cs @@ -7,8 +7,6 @@ namespace Engine.Graphics.Texture; public class DynamicTexture : Texture, IFramebufferAttachment { - public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Texture; - private readonly PixelFormat _format; private readonly PixelType _type; private readonly PixelInternalFormat _internalFormat; @@ -49,4 +47,9 @@ public class DynamicTexture : Texture, IFramebufferAttachment Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height); } + + public void Attach(Framebuffer.Framebuffer parFramebuffer, FramebufferAttachment parAttachment) + { + GL.NamedFramebufferTexture(parFramebuffer.Handle, parAttachment, Handle, 0); + } } \ No newline at end of file diff --git a/Engine/src/Input/IInputHandler.cs b/Engine/src/Input/IInputHandler.cs index 3002ec9..c5a065c 100644 --- a/Engine/src/Input/IInputHandler.cs +++ b/Engine/src/Input/IInputHandler.cs @@ -4,10 +4,9 @@ namespace Engine.Input; public interface IInputHandler : IUpdate { - bool IsKeyPressed(KeyCode parKeyCode); - bool IsKeyJustPressed(KeyCode parKeyCode); - bool IsKeyRepeat(KeyCode parKeyCode); + bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode); + bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode); - bool IsMouseButtonPressed(MouseButton parButton); - bool IsMouseButtonJustPressed(MouseButton parButton); + bool IsMouseButtonPressed(MouseButtonCode parButtonCode); + bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode); } \ No newline at end of file diff --git a/Engine/src/Input/KeyCode.cs b/Engine/src/Input/KeyboardButtonCode.cs similarity index 81% rename from Engine/src/Input/KeyCode.cs rename to Engine/src/Input/KeyboardButtonCode.cs index 1b43403..baff356 100644 --- a/Engine/src/Input/KeyCode.cs +++ b/Engine/src/Input/KeyboardButtonCode.cs @@ -1,6 +1,6 @@ namespace Engine.Input; -public enum KeyCode +public enum KeyboardButtonCode { A, B, @@ -48,5 +48,7 @@ public enum KeyCode Home, End, PageUp, - PageDown + PageDown, + + TotalCount = PageDown + 1 } \ No newline at end of file diff --git a/Engine/src/Input/MouseButton.cs b/Engine/src/Input/MouseButton.cs deleted file mode 100644 index 41712a4..0000000 --- a/Engine/src/Input/MouseButton.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Engine.Input; - -public enum MouseButton -{ - Left, - Right, - Middle, - - Button4, - Button5, - Button6, - Button7 -} \ No newline at end of file diff --git a/Engine/src/Input/MouseButtonCode.cs b/Engine/src/Input/MouseButtonCode.cs new file mode 100644 index 0000000..4fcc558 --- /dev/null +++ b/Engine/src/Input/MouseButtonCode.cs @@ -0,0 +1,10 @@ +namespace Engine.Input; + +public enum MouseButtonCode +{ + Left, + Right, + Middle, + + TotalCount = Middle + 1 +} \ No newline at end of file diff --git a/Engine/src/Input/WindowInputHandler.cs b/Engine/src/Input/WindowInputHandler.cs new file mode 100644 index 0000000..95b8843 --- /dev/null +++ b/Engine/src/Input/WindowInputHandler.cs @@ -0,0 +1,99 @@ +using OpenTK.Windowing.GraphicsLibraryFramework; + +namespace Engine.Input; + +public class WindowInputHandler(Window parWindow) : IInputHandler +{ + private KeyboardState _previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); + private KeyboardState _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); + + public void Update(double parDeltaTime) + { + _previousKeyboardState = _keyboardState; + _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); + } + + public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) + { + return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)); + } + + public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) + { + return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)) && + !_previousKeyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)); + } + + public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) + { + return parWindow.NativeWindow.MouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)); + } + + public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) + { + return parWindow.NativeWindow.MouseState.IsButtonPressed(MapMouseButtonCode(parButtonCode)); + } + + private static MouseButton MapMouseButtonCode(MouseButtonCode parButton) + { + return parButton switch + { + MouseButtonCode.Left => MouseButton.Left, + MouseButtonCode.Right => MouseButton.Right, + MouseButtonCode.Middle => MouseButton.Middle, + _ => throw new ArgumentOutOfRangeException(nameof(parButton), parButton, null) + }; + } + + private static Keys MapKeyCode(KeyboardButtonCode parKeyboardButtonCode) + { + return parKeyboardButtonCode switch + { + KeyboardButtonCode.A => Keys.A, + KeyboardButtonCode.B => Keys.B, + KeyboardButtonCode.C => Keys.C, + KeyboardButtonCode.D => Keys.D, + KeyboardButtonCode.E => Keys.E, + KeyboardButtonCode.F => Keys.F, + KeyboardButtonCode.G => Keys.G, + KeyboardButtonCode.H => Keys.H, + KeyboardButtonCode.I => Keys.I, + KeyboardButtonCode.J => Keys.J, + KeyboardButtonCode.K => Keys.K, + KeyboardButtonCode.L => Keys.L, + KeyboardButtonCode.M => Keys.M, + KeyboardButtonCode.N => Keys.N, + KeyboardButtonCode.O => Keys.O, + KeyboardButtonCode.P => Keys.P, + KeyboardButtonCode.Q => Keys.Q, + KeyboardButtonCode.R => Keys.R, + KeyboardButtonCode.S => Keys.S, + KeyboardButtonCode.T => Keys.T, + KeyboardButtonCode.U => Keys.U, + KeyboardButtonCode.V => Keys.V, + KeyboardButtonCode.W => Keys.W, + KeyboardButtonCode.X => Keys.X, + KeyboardButtonCode.Y => Keys.Y, + KeyboardButtonCode.Z => Keys.Z, + KeyboardButtonCode.Ctrl => Keys.LeftControl, + KeyboardButtonCode.Alt => Keys.LeftAlt, + KeyboardButtonCode.Shift => Keys.LeftShift, + KeyboardButtonCode.Up => Keys.Up, + KeyboardButtonCode.Down => Keys.Down, + KeyboardButtonCode.Left => Keys.Left, + KeyboardButtonCode.Right => Keys.Right, + KeyboardButtonCode.Escape => Keys.Escape, + KeyboardButtonCode.Enter => Keys.Enter, + KeyboardButtonCode.Space => Keys.Space, + KeyboardButtonCode.Tab => Keys.Tab, + KeyboardButtonCode.Backspace => Keys.Backspace, + KeyboardButtonCode.Delete => Keys.Delete, + KeyboardButtonCode.Insert => Keys.Insert, + KeyboardButtonCode.Home => Keys.Home, + KeyboardButtonCode.End => Keys.End, + KeyboardButtonCode.PageUp => Keys.PageUp, + KeyboardButtonCode.PageDown => Keys.PageDown, + _ => throw new ArgumentOutOfRangeException(nameof(parKeyboardButtonCode), parKeyboardButtonCode, null) + }; + } +} \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/Camera.cs b/Engine/src/Scene/Component/BuiltIn/Camera.cs index 94072d2..7d012be 100644 --- a/Engine/src/Scene/Component/BuiltIn/Camera.cs +++ b/Engine/src/Scene/Component/BuiltIn/Camera.cs @@ -1,5 +1,6 @@ using Engine.Graphics.Camera; using OpenTK.Mathematics; +using Serilog; namespace Engine.Scene.Component.BuiltIn; diff --git a/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs b/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs index 232bb1a..210f931 100644 --- a/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs +++ b/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs @@ -4,8 +4,8 @@ using OpenTK.Mathematics; namespace Engine.Scene.Component.BuiltIn; public class OrthographicCamera( - float parNearPlane = 0.1f, - float parFarPlane = 1000f, + float parNearPlane = -10000f, + float parFarPlane = 10000f, float parSize = 10f, OrthographicCamera.Axis parAxis = OrthographicCamera.Axis.Y ) @@ -17,8 +17,8 @@ public class OrthographicCamera( public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted(); public override Matrix4 Projection => FixedAxis == Axis.X - ? Matrix4.CreateOrthographic(Size, Size / AspectRatio, NearPlane, FarPlane) - : Matrix4.CreateOrthographic(Size * AspectRatio, Size, NearPlane, FarPlane); + ? Matrix4.CreateOrthographic(Size, -Size / AspectRatio, NearPlane, FarPlane) + : Matrix4.CreateOrthographic(Size * AspectRatio, -Size, NearPlane, FarPlane); public override Vector3 ScreenToWorld(Vector2 parScreenPosition) { diff --git a/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs b/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs index e26e9b4..be3632f 100644 --- a/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs +++ b/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs @@ -1,11 +1,12 @@ using Engine.Util; using OpenTK.Mathematics; +using Math = System.Math; namespace Engine.Scene.Component.BuiltIn; public class PerspectiveCamera( float parFieldOfView = 90.0f, - float parNearPlane = 0.1f, + float parNearPlane = 0.01f, float parFarPlane = 1000f ) : Camera(parNearPlane, parFarPlane) @@ -19,14 +20,15 @@ public class PerspectiveCamera( var transformMatrix = GameObject.Transform.TransformMatrix; var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix); var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix); - var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized(); + var up = (new Vector4(0, -1, 0, 1).MulProject(transformMatrix) - eye).Normalized(); return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz); } } public override Matrix4 Projection => - Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); + Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(FieldOfView), AspectRatio, + NearPlane, FarPlane); public override Vector3 ScreenToWorld(Vector2 parScreenPosition) { diff --git a/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs b/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs new file mode 100644 index 0000000..e6acdf2 --- /dev/null +++ b/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs @@ -0,0 +1,17 @@ +using Engine.Graphics.Texture; +using OpenTK.Mathematics; + +namespace Engine.Scene.Component.BuiltIn.Renderer; + +public class Box2DRenderer : Component +{ + public ref Vector4 Color => ref _color; + public Texture? Texture { get; set; } = null; + + private Vector4 _color = Vector4.One; + + public override void Render() + { + Engine.Instance.Renderer.QuadRenderer.Commit(GameObject.Transform.TransformMatrix, Color, Texture); + } +} \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs b/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs new file mode 100644 index 0000000..a8e100f --- /dev/null +++ b/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs @@ -0,0 +1,13 @@ +using Engine.Asset.Mesh; + +namespace Engine.Scene.Component.BuiltIn.Renderer; + +public class MeshRenderer : Component +{ + public Mesh Mesh { get; set; } = null!; + + public override void Render() + { + Engine.Instance.Renderer.GlobalMeshRenderer.Commit(Mesh, GameObject.Transform.TransformMatrix); + } +} \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/Transform.cs b/Engine/src/Scene/Component/BuiltIn/Transform.cs index d8f1925..895566a 100644 --- a/Engine/src/Scene/Component/BuiltIn/Transform.cs +++ b/Engine/src/Scene/Component/BuiltIn/Transform.cs @@ -4,14 +4,19 @@ namespace Engine.Scene.Component.BuiltIn; public class Transform : Component { - public Vector3 Position { get; set; } = Vector3.Zero; - public Quaternion Rotation { get; set; } = Quaternion.Identity; - public Vector3 Scale { get; set; } = Vector3.One; - public Vector3 LocalScale { get; set; } = Vector3.One; + private Vector3 _translation = Vector3.Zero; + private Quaternion _rotation = Quaternion.Identity; + private Vector3 _scale = Vector3.One; + private Vector3 _localScale = Vector3.One; + + public ref Vector3 Translation => ref _translation; + public ref Quaternion Rotation => ref _rotation; + public ref Vector3 Scale => ref _scale; + public ref Vector3 LocalScale => ref _localScale; public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) * Matrix4.CreateFromQuaternion(Rotation) * - Matrix4.CreateTranslation(Position); + Matrix4.CreateTranslation(Translation); public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix; @@ -27,7 +32,7 @@ public class Transform : Component public Transform Clone() { var clone = - new Transform { Position = Position, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale }; + new Transform { Translation = Translation, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale }; return clone; } diff --git a/Engine/src/Scene/GameObject.cs b/Engine/src/Scene/GameObject.cs index 8801c1d..b19e25b 100644 --- a/Engine/src/Scene/GameObject.cs +++ b/Engine/src/Scene/GameObject.cs @@ -13,8 +13,8 @@ public sealed class GameObject : IUpdate, IRender private readonly Queue _componentActions = new(); - private readonly IList _components = new List(); - private readonly ISet _addedComponentTypes = new HashSet(); + private readonly List _components = []; + private readonly HashSet _addedComponentTypes = []; public GameObject() { diff --git a/Engine/src/Scene/Hierarchy.cs b/Engine/src/Scene/Hierarchy.cs index 063118c..841f8c8 100644 --- a/Engine/src/Scene/Hierarchy.cs +++ b/Engine/src/Scene/Hierarchy.cs @@ -1,12 +1,13 @@ -using System.Collections; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Engine.Util; namespace Engine.Scene; -public class Hierarchy : IEnumerable +public class Hierarchy where T : class { + internal Dictionary.KeyCollection Objects => _parentLookup.Keys; + private readonly Dictionary, IList> _childrenLookup = new(); private readonly Dictionary _parentLookup = new(); @@ -147,14 +148,4 @@ public class Hierarchy : IEnumerable } } } - - public IEnumerator GetEnumerator() - { - return _parentLookup.Keys.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } \ No newline at end of file diff --git a/Engine/src/Scene/Scene.cs b/Engine/src/Scene/Scene.cs index 1eef787..a32b379 100644 --- a/Engine/src/Scene/Scene.cs +++ b/Engine/src/Scene/Scene.cs @@ -8,7 +8,7 @@ public class Scene : IUpdate, IRender public bool IsPlaying { get; private set; } public ICamera? MainCamera { get; private set; } - internal Hierarchy Hierarchy { get; } = []; + internal Hierarchy Hierarchy { get; } = new(); private readonly Queue _sceneActions = []; @@ -28,7 +28,7 @@ public class Scene : IUpdate, IRender public T? FindFirstComponent() where T : Component.Component { - return Hierarchy.Select(parGameObject => parGameObject.GetComponent()).FirstOrDefault(); + return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent()).FirstOrDefault(); } public void Update(double parDeltaTime) @@ -40,7 +40,7 @@ public class Scene : IUpdate, IRender ProcessChanges(); - foreach (var gameObject in Hierarchy) + foreach (var gameObject in Hierarchy.Objects) { gameObject.Update(parDeltaTime); } @@ -53,7 +53,7 @@ public class Scene : IUpdate, IRender throw new InvalidOperationException("Scene is not playing"); } - foreach (var gameObject in Hierarchy) + foreach (var gameObject in Hierarchy.Objects) { gameObject.Render(); } @@ -66,7 +66,7 @@ public class Scene : IUpdate, IRender throw new InvalidOperationException("Scene is not playing"); } - foreach (var gameObject in Hierarchy) + foreach (var gameObject in Hierarchy.Objects) { gameObject.Destroy(); } @@ -114,7 +114,7 @@ public class Scene : IUpdate, IRender action(); } - foreach (var gameObject in Hierarchy) + foreach (var gameObject in Hierarchy.Objects) { gameObject.ProcessChanges(); } diff --git a/Engine/src/Scene/SceneManager.cs b/Engine/src/Scene/SceneManager.cs index 62ef2ad..3c34dca 100644 --- a/Engine/src/Scene/SceneManager.cs +++ b/Engine/src/Scene/SceneManager.cs @@ -1,6 +1,4 @@ -using Engine.Scene.Component.BuiltIn; - -namespace Engine.Scene; +namespace Engine.Scene; public class SceneManager : IUpdate, IRender { diff --git a/Engine/src/Window.cs b/Engine/src/Window.cs index d315603..6d2fb70 100644 --- a/Engine/src/Window.cs +++ b/Engine/src/Window.cs @@ -1,6 +1,4 @@ -using Engine.Asset; -using Engine.Graphics; -using Engine.Graphics.Pixel; +using Engine.Graphics; using Engine.Graphics.Texture; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; @@ -17,6 +15,8 @@ public class Window : IPresenter public int Height { get; private set; } public event Action? Resize; + internal NativeWindow NativeWindow => _window; + private readonly Engine _engine; private readonly NativeWindow _window; private readonly bool _headless; @@ -36,21 +36,33 @@ public class Window : IPresenter Height = parArgs.Height; Resize?.Invoke(parArgs); }; + + _window.VSync = VSyncMode.On; } public void Update(double parDeltaTime) + { + } + + public void Render() { if (_headless) { return; } + _window.NewInputFrame(); NativeWindow.ProcessWindowEvents(false); _window.SwapBuffers(); } public void Present(IConstTexture parTexture) { + if (_headless) + { + return; + } + GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); diff --git a/EngineTests/src/Scene/HierarchyTests.cs b/EngineTests/src/Scene/HierarchyTests.cs index 508bf98..4bb7257 100644 --- a/EngineTests/src/Scene/HierarchyTests.cs +++ b/EngineTests/src/Scene/HierarchyTests.cs @@ -9,7 +9,7 @@ public class HierarchyTests [SetUp] public void Setup() { - _hierarchy = []; + _hierarchy = new Hierarchy(); } [Test] diff --git a/PresenterConsole/PresenterConsole.csproj b/PresenterConsole/PresenterConsole.csproj index be125a4..d13d7d6 100644 --- a/PresenterConsole/PresenterConsole.csproj +++ b/PresenterConsole/PresenterConsole.csproj @@ -9,6 +9,7 @@ + diff --git a/PresenterConsole/assets/shader/ascii.shader b/PresenterConsole/assets/shader/ascii.shader index 2bce69c..87e1d76 100644 --- a/PresenterConsole/assets/shader/ascii.shader +++ b/PresenterConsole/assets/shader/ascii.shader @@ -37,7 +37,8 @@ const vec3 ConsoleColorVec3[16] = vec3[]( vec3(1.0, 1.0, 1.0) // White ); -vec3 f(vec3 x) { +// RGB to LAB conversion (simplified approximation) +vec3 rgb2lab(vec3 x) { const float epsilon = 0.008856; const float k = 903.3; vec3 fx; @@ -49,10 +50,8 @@ vec3 f(vec3 x) { // Perceptually weighted color difference (CIEDE2000-inspired) float perceptualColorDistance(vec3 color1, vec3 color2) { - // RGB to LAB conversion (simplified approximation) - - vec3 lab1 = f(color1); - vec3 lab2 = f(color2); + vec3 lab1 = rgb2lab(color1); + vec3 lab2 = rgb2lab(color2); // Compute LAB-like color difference with perceptual weighting float deltaL = lab1.x - lab2.x; @@ -98,7 +97,6 @@ float interleavedGradientNoise(vec2 pixel) { } uniform sampler2D uInputTexture; -uniform vec2 uResolution; layout (location = 0) in vec2 iUV; layout (location = 0) out vec4 FragColor; @@ -114,9 +112,9 @@ void main() { // Output with high precision color mapping FragColor = vec4( - luminance, // Red: Perceptual luminance - float(colorIndex) / 15.0, // Green: Normalized color index - 0.0, // Blue: Unused - 1.0 // Alpha + luminance, // Red: Perceptual luminance + float(colorIndex) / 15.0, // Green: Normalized color index + 0.0, // Blue: Unused + 1.0 // Alpha ); } \ No newline at end of file diff --git a/PresenterConsole/src/ConsoleFastOutput.cs b/PresenterConsole/src/ConsoleFastOutput.cs index c2d2b9f..ef8c077 100644 --- a/PresenterConsole/src/ConsoleFastOutput.cs +++ b/PresenterConsole/src/ConsoleFastOutput.cs @@ -34,13 +34,10 @@ public sealed class ConsoleFastOutput : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteChar(char parCharacter, int parX, int parY, ConsoleColor parForeground, ConsoleColor parBackground) { - if (parX < 0 || parX >= _width || parY < 0 || parY >= _height) - return; - var index = parX + parY * _width; ref var charInfo = ref _buffer[index]; charInfo.Char.UnicodeChar = parCharacter; - charInfo.Attributes = (short)((int)parForeground | ((int)parBackground << 4)); + charInfo.Attributes = (short)((ushort)parForeground | ((ushort)parBackground << 4)); } public void Flush() @@ -60,7 +57,7 @@ public sealed class ConsoleFastOutput : IDisposable _width = parWidth; _height = parHeight; - _buffer = new WindowsFFI.CharInfo[parWidth * parHeight]; + _buffer = new WindowsFFI.CharInfo[_width * _height]; _bufferSize = new WindowsFFI.Coord((short)_width, (short)_height); } diff --git a/PresenterConsole/src/ConsoleInputHandler.cs b/PresenterConsole/src/ConsoleInputHandler.cs index ed57f26..18c9d59 100644 --- a/PresenterConsole/src/ConsoleInputHandler.cs +++ b/PresenterConsole/src/ConsoleInputHandler.cs @@ -4,126 +4,98 @@ namespace PresenterConsole; public class ConsoleInputHandler : IInputHandler { - private readonly HashSet _currentKeys = []; - private readonly HashSet _previousKeys = []; - private ConsoleModifiers _currentModifiers; - private ConsoleModifiers _previousModifiers; + private readonly bool[] _currentKeys = new bool[256]; + private readonly bool[] _previousKeys = new bool[256]; public void Update(double parDeltaTime) { - // Save previous state - _previousKeys.Clear(); - foreach (var key in _currentKeys) + for (var i = 0; i < _currentKeys.Length; i++) { - _previousKeys.Add(key); - } - - _previousModifiers = _currentModifiers; - - // Clear current state - _currentKeys.Clear(); - _currentModifiers = 0; - - // Read keys - while (Console.KeyAvailable) - { - var keyInfo = Console.ReadKey(intercept: true); - _currentKeys.Add(keyInfo.Key); - _currentModifiers |= keyInfo.Modifiers; + _previousKeys[i] = _currentKeys[i]; + _currentKeys[i] = (WindowsFFI.GetAsyncKeyState(i) & 0x8000) != 0; } } - public bool IsKeyPressed(KeyCode keyCode) + public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) { - if (IsModifierKey(keyCode)) - return IsModifierActive(keyCode); - - return _currentKeys.Contains(ConvertToConsoleKey(keyCode)); + var consoleKey = ConvertToConsoleKey(parKeyboardButtonCode); + return _currentKeys[consoleKey]; } - public bool IsKeyJustPressed(KeyCode keyCode) + public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) { - if (IsModifierKey(keyCode)) - return IsModifierActive(keyCode) && !WasModifierActive(keyCode); - - var consoleKey = ConvertToConsoleKey(keyCode); - return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey); + var consoleKey = ConvertToConsoleKey(parKeyboardButtonCode); + return _currentKeys[consoleKey] && !_previousKeys[consoleKey]; } - public bool IsKeyRepeat(KeyCode keyCode) + public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) { - if (IsModifierKey(keyCode)) - return IsModifierActive(keyCode) && WasModifierActive(keyCode); - - var consoleKey = ConvertToConsoleKey(keyCode); - return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey); + var consoleKey = ConvertToConsoleKey(parButtonCode); + return _currentKeys[consoleKey]; } - public bool IsMouseButtonPressed(MouseButton button) + public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) { - return false; + var consoleKey = ConvertToConsoleKey(parButtonCode); + return _currentKeys[consoleKey] && !_previousKeys[consoleKey]; } - public bool IsMouseButtonJustPressed(MouseButton button) - { - return false; - } + private static int ConvertToConsoleKey(MouseButtonCode parMouseButtonCode) => + parMouseButtonCode switch + { + MouseButtonCode.Left => 0x01, + MouseButtonCode.Right => 0x02, + MouseButtonCode.Middle => 0x04, + _ => throw new ArgumentOutOfRangeException(nameof(parMouseButtonCode), parMouseButtonCode, null) + }; - private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift; - - private bool IsModifierActive(KeyCode keyCode) => keyCode switch - { - KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0, - KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0, - KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0, - _ => false - }; - - private bool WasModifierActive(KeyCode keyCode) => keyCode switch - { - KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0, - KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0, - KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0, - _ => false - }; - - private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch - { - KeyCode.A => ConsoleKey.A, - KeyCode.B => ConsoleKey.B, - KeyCode.C => ConsoleKey.C, - KeyCode.D => ConsoleKey.D, - KeyCode.E => ConsoleKey.E, - KeyCode.F => ConsoleKey.F, - KeyCode.G => ConsoleKey.G, - KeyCode.H => ConsoleKey.H, - KeyCode.I => ConsoleKey.I, - KeyCode.J => ConsoleKey.J, - KeyCode.K => ConsoleKey.K, - KeyCode.L => ConsoleKey.L, - KeyCode.M => ConsoleKey.M, - KeyCode.N => ConsoleKey.N, - KeyCode.O => ConsoleKey.O, - KeyCode.P => ConsoleKey.P, - KeyCode.Q => ConsoleKey.Q, - KeyCode.R => ConsoleKey.R, - KeyCode.S => ConsoleKey.S, - KeyCode.T => ConsoleKey.T, - KeyCode.U => ConsoleKey.U, - KeyCode.V => ConsoleKey.V, - KeyCode.W => ConsoleKey.W, - KeyCode.X => ConsoleKey.X, - KeyCode.Y => ConsoleKey.Y, - KeyCode.Z => ConsoleKey.Z, - KeyCode.Enter => ConsoleKey.Enter, - KeyCode.Escape => ConsoleKey.Escape, - KeyCode.Space => ConsoleKey.Spacebar, - KeyCode.Tab => ConsoleKey.Tab, - KeyCode.Backspace => ConsoleKey.Backspace, - KeyCode.Up => ConsoleKey.UpArrow, - KeyCode.Down => ConsoleKey.DownArrow, - KeyCode.Left => ConsoleKey.LeftArrow, - KeyCode.Right => ConsoleKey.RightArrow, - _ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}") - }; + private static int ConvertToConsoleKey(KeyboardButtonCode parKeyboardButtonCode) => + parKeyboardButtonCode switch + { + KeyboardButtonCode.A => 0x41, + KeyboardButtonCode.B => 0x42, + KeyboardButtonCode.C => 0x43, + KeyboardButtonCode.D => 0x44, + KeyboardButtonCode.E => 0x45, + KeyboardButtonCode.F => 0x46, + KeyboardButtonCode.G => 0x47, + KeyboardButtonCode.H => 0x48, + KeyboardButtonCode.I => 0x49, + KeyboardButtonCode.J => 0x4A, + KeyboardButtonCode.K => 0x4B, + KeyboardButtonCode.L => 0x4C, + KeyboardButtonCode.M => 0x4D, + KeyboardButtonCode.N => 0x4E, + KeyboardButtonCode.O => 0x4F, + KeyboardButtonCode.P => 0x50, + KeyboardButtonCode.Q => 0x51, + KeyboardButtonCode.R => 0x52, + KeyboardButtonCode.S => 0x53, + KeyboardButtonCode.T => 0x54, + KeyboardButtonCode.U => 0x55, + KeyboardButtonCode.V => 0x56, + KeyboardButtonCode.W => 0x57, + KeyboardButtonCode.X => 0x58, + KeyboardButtonCode.Y => 0x59, + KeyboardButtonCode.Z => 0x5A, + KeyboardButtonCode.Enter => 0x0D, + KeyboardButtonCode.Escape => 0x1B, + KeyboardButtonCode.Space => 0x20, + KeyboardButtonCode.Tab => 0x09, + KeyboardButtonCode.Backspace => 0x08, + KeyboardButtonCode.Up => 0x26, + KeyboardButtonCode.Down => 0x28, + KeyboardButtonCode.Left => 0x25, + KeyboardButtonCode.Right => 0x27, + KeyboardButtonCode.Shift => 0x10, + KeyboardButtonCode.Ctrl => 0x11, + KeyboardButtonCode.Alt => 0x12, + KeyboardButtonCode.Delete => 0x2E, + KeyboardButtonCode.Insert => 0x2D, + KeyboardButtonCode.Home => 0x24, + KeyboardButtonCode.End => 0x23, + KeyboardButtonCode.PageUp => 0x21, + KeyboardButtonCode.PageDown => 0x22, + _ => throw new ArgumentOutOfRangeException(nameof(parKeyboardButtonCode), parKeyboardButtonCode, null) + }; } \ No newline at end of file diff --git a/PresenterConsole/src/ConsolePresenter.cs b/PresenterConsole/src/ConsolePresenter.cs index 56e9965..781bf8b 100644 --- a/PresenterConsole/src/ConsolePresenter.cs +++ b/PresenterConsole/src/ConsolePresenter.cs @@ -2,7 +2,6 @@ using Engine.Graphics; using Engine.Graphics.Buffer; using Engine.Graphics.Framebuffer; -using Engine.Graphics.Pixel; using Engine.Graphics.Shader; using Engine.Graphics.Texture; using OpenTK.Graphics.OpenGL; @@ -14,14 +13,10 @@ namespace PresenterConsole; public class ConsolePresenter : IPresenter { public bool IsExiting => false; - public int Width => Console.WindowWidth; - public int Height => Console.WindowHeight; + public int Width { get; private set; } = 2; + public int Height { get; private set; } = 1; public event Action? Resize; - private int _cachedWidth; - private int _cachedHeight; - - private readonly Engine.Engine _engine; private readonly Framebuffer _framebuffer; private readonly Engine.Graphics.Shader.Program _asciiProgram; private Image? _asciiImage; @@ -31,14 +26,13 @@ public class ConsolePresenter : IPresenter private readonly VertexArray _vertexArray; private readonly ConsoleFastOutput _consoleOutput; - private static readonly char[] LIGHTMAP = " .:-+=#%@".Reverse().ToArray(); + private static readonly char[] LIGHTMAP = " .,:;=*#%@".Reverse().ToArray(); - public ConsolePresenter(Engine.Engine parEngine) + public ConsolePresenter() { - _engine = parEngine; _asciiProgram = ProgramLoader.LoadFromSource(Resource.ShaderResource.Ascii); - _framebuffer = Framebuffer.Builder(Width, Height) + _framebuffer = Framebuffer.Builder(Width / 2, Height) .AddColorAttachment() .Build(); @@ -63,9 +57,6 @@ public class ConsolePresenter : IPresenter _framebuffer.Bind(); - GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - openglTexture.BindUnit(); _asciiProgram.Bind(); @@ -94,10 +85,10 @@ public class ConsolePresenter : IPresenter for (var x = 0; x < parImage.Width; x++) { var pixel = parImage[y, x]; - var lightnessIndex = (int)(pixel.Luminance / 255.0f * (LIGHTMAP.Length - 1)); - var colorIndex = (int)(pixel.Color / 255.0f * 15.0f); - _consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], (short)x, (short)y, 0, - (ConsoleColor)colorIndex); + var lightnessIndex = (byte)(pixel.Luminance / 255.0f * (LIGHTMAP.Length - 1)); + var colorIndex = (ConsoleColor)(pixel.Color / 255.0f * 15.0f); + _consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x, y, 0, colorIndex); + _consoleOutput.WriteChar(LIGHTMAP[lightnessIndex], 2 * x + 1, y, 0, colorIndex); } } @@ -106,14 +97,20 @@ public class ConsolePresenter : IPresenter public void Update(double parDeltaTime) { - if (_cachedWidth != Width || _cachedHeight != Height) + } + + public void Render() + { + var consoleWidth = Console.WindowWidth; + var consoleHeight = Console.WindowHeight; + if (Width != consoleWidth || Height != consoleHeight) { - _cachedWidth = Width; - _cachedHeight = Height; + Width = consoleWidth; + Height = consoleHeight; - Resize?.Invoke(new ResizeEventArgs(Width, Height)); + Resize?.Invoke(new ResizeEventArgs(Width / 2, Height)); - _framebuffer.Resize(Width, Height); + _framebuffer.Resize(Width / 2, Height); _consoleOutput.Resize(Width, Height); } } diff --git a/PresenterConsole/src/Program.cs b/PresenterConsole/src/Program.cs index 3ea092b..5d50765 100644 --- a/PresenterConsole/src/Program.cs +++ b/PresenterConsole/src/Program.cs @@ -1,6 +1,4 @@ -// See https://aka.ms/new-console-template for more information - -using Serilog.Events; +using Serilog.Events; namespace PresenterConsole; @@ -12,9 +10,12 @@ internal static class Program .Headless() .LogToFile(true, "log.txt") .LogLevel(LogEventLevel.Debug) - .Presenter(parEngine => new ConsolePresenter(parEngine)) + .Presenter(_ => new ConsolePresenter()) + .InputHandler(_ => new ConsoleInputHandler()) .Build(); + DoomDeathmatch.DoomDeathmatch.Initialize(engine); + engine.Run(); } } \ No newline at end of file diff --git a/PresenterConsole/src/WindowsFFI.cs b/PresenterConsole/src/WindowsFFI.cs index c43f868..6a04923 100644 --- a/PresenterConsole/src/WindowsFFI.cs +++ b/PresenterConsole/src/WindowsFFI.cs @@ -60,4 +60,11 @@ public static partial class WindowsFFI Coord parDwBufferCoord, ref SmallRect parLpWriteRegion ); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetKeyboardState")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool GetKeyboardState(byte[] parKeyboardState); + + [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "GetAsyncKeyState")] + public static partial short GetAsyncKeyState(int parKeyCode); } \ No newline at end of file diff --git a/PresenterNative/PresenterNative.csproj b/PresenterNative/PresenterNative.csproj index 7e4bc5f..1fb4d59 100644 --- a/PresenterNative/PresenterNative.csproj +++ b/PresenterNative/PresenterNative.csproj @@ -8,6 +8,7 @@ + diff --git a/PresenterNative/src/Program.cs b/PresenterNative/src/Program.cs index 84c59bc..7d2aa12 100644 --- a/PresenterNative/src/Program.cs +++ b/PresenterNative/src/Program.cs @@ -1,4 +1,5 @@ using Engine; +using Engine.Input; using Serilog.Events; namespace PresenterNative; @@ -16,8 +17,11 @@ internal static class Program .LogToFile(true, "log.txt") .LogLevel(LogEventLevel.Debug) .Presenter(parEngine => parEngine.Window) + .InputHandler(parEngine => new WindowInputHandler(parEngine.Window)) .Build(); + DoomDeathmatch.DoomDeathmatch.Initialize(engine); + engine.Run(); } } \ No newline at end of file diff --git a/PresenterWpf/PresenterWpf.csproj b/PresenterWpf/PresenterWpf.csproj index 9588090..e50914d 100644 --- a/PresenterWpf/PresenterWpf.csproj +++ b/PresenterWpf/PresenterWpf.csproj @@ -9,6 +9,7 @@ + diff --git a/PresenterWpf/src/App.xaml.cs b/PresenterWpf/src/App.xaml.cs index bb13be4..cb15c2e 100644 --- a/PresenterWpf/src/App.xaml.cs +++ b/PresenterWpf/src/App.xaml.cs @@ -4,6 +4,7 @@ using System.Windows; using Engine; using Engine.Graphics; using Engine.Graphics.Texture; +using Engine.Input; using OpenTK.Windowing.Common; using Serilog.Events; @@ -18,9 +19,11 @@ public partial class App : Application protected override void OnStartup(StartupEventArgs parEventArgs) { var presenter = new PresenterWrapper(); + var inputHandler = new InputHandlerWrapper(); var engine = new EngineBuilder() .Headless() .Presenter(_ => presenter) + .InputHandler(_ => inputHandler) .LogToConsole() .LogToFile(true, "log.txt") .LogLevel(LogEventLevel.Debug) @@ -31,6 +34,7 @@ public partial class App : Application { var window = new MainWindow(engine); presenter.Presenter = window; + inputHandler.InputHandler = new WpfInputHandler(window); window.Show(); System.Windows.Threading.Dispatcher.Run(); }); @@ -39,12 +43,53 @@ public partial class App : Application thread.IsBackground = true; thread.Start(); + DoomDeathmatch.DoomDeathmatch.Initialize(engine); + engine.Run(); // Shutdown WPF Shutdown(); } + private class InputHandlerWrapper : IInputHandler + { + private IInputHandler? _inputHandler; + + public IInputHandler? InputHandler + { + get => _inputHandler; + set + { + _inputHandler = value; + } + } + + public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) + { + return _inputHandler?.IsKeyPressed(parKeyboardButtonCode) ?? false; + } + + public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) + { + return _inputHandler?.IsKeyJustPressed(parKeyboardButtonCode) ?? false; + } + + public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) + { + return _inputHandler?.IsMouseButtonPressed(parButtonCode) ?? false; + } + + public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) + { + return _inputHandler?.IsMouseButtonJustPressed(parButtonCode) ?? false; + } + + public void Update(double parDeltaTime) + { + _inputHandler?.Update(parDeltaTime); + } + } + private class PresenterWrapper : IPresenter { private IPresenter? _presenter; @@ -83,6 +128,11 @@ public partial class App : Application Presenter?.Update(parDeltaTime); } + public void Render() + { + Presenter?.Render(); + } + private void PresenterResize(ResizeEventArgs e) { Resize?.Invoke(e); diff --git a/PresenterWpf/src/MainWindow.xaml.cs b/PresenterWpf/src/MainWindow.xaml.cs index 8a8736f..b60605c 100644 --- a/PresenterWpf/src/MainWindow.xaml.cs +++ b/PresenterWpf/src/MainWindow.xaml.cs @@ -43,8 +43,10 @@ public partial class MainWindow : Window, IPresenter _scheduledResize = true; } }); + } - // Resizes are lazy so resizing only happens when the window's size actually changes + public void Render() + { if (_scheduledResize) { _scheduledResize = false; @@ -77,7 +79,7 @@ public partial class MainWindow : Window, IPresenter try { _bitmap!.Lock(); - _bitmap.WritePixels(new Int32Rect(0, 0, parImage.Width, parImage.Height), parImage.Pixels, parImage.Width * 3, 0, + _bitmap!.WritePixels(new Int32Rect(0, 0, parImage.Width, parImage.Height), parImage.Pixels, parImage.Width * 3, 0, 0); } finally diff --git a/PresenterWpf/src/WPFInputHandler.cs b/PresenterWpf/src/WPFInputHandler.cs new file mode 100644 index 0000000..b6f49df --- /dev/null +++ b/PresenterWpf/src/WPFInputHandler.cs @@ -0,0 +1,167 @@ +using System.Windows; +using System.Windows.Input; +using Engine.Input; + +namespace PresenterWpf; + +public class WpfInputHandler : IInputHandler +{ + private readonly Window _window; + + private readonly bool[] _currentKeys = new bool[(int)KeyboardButtonCode.TotalCount]; + private readonly bool[] _previousKeys = new bool[(int)KeyboardButtonCode.TotalCount]; + private readonly bool[] _currentMouseButtons = new bool[(int)MouseButtonCode.TotalCount]; + private readonly bool[] _previousMouseButtons = new bool[(int)MouseButtonCode.TotalCount]; + + public WpfInputHandler(Window parWindow) + { + _window = parWindow; + + _window.PreviewKeyDown += Window_PreviewKeyDown; + _window.PreviewKeyUp += Window_PreviewKeyUp; + _window.PreviewMouseDown += Window_PreviewMouseDown; + _window.PreviewMouseUp += Window_PreviewMouseUp; + } + + public void Update(double parDeltaTime) + { + for (var i = 0; i < _currentKeys.Length; i++) + { + _previousKeys[i] = _currentKeys[i]; + } + + for (var i = 0; i < _currentMouseButtons.Length; i++) + { + _previousMouseButtons[i] = _currentMouseButtons[i]; + } + } + + private void Window_PreviewKeyDown(object parSender, KeyEventArgs parEventArgs) + { + var keyCode = ConvertToKeyboardButtonCode(parEventArgs.Key); + if (keyCode >= 0 && keyCode < _currentKeys.Length) + { + _currentKeys[keyCode] = true; + } + } + + private void Window_PreviewKeyUp(object parSender, KeyEventArgs parEventArgs) + { + var keyCode = ConvertToKeyboardButtonCode(parEventArgs.Key); + if (keyCode >= 0 && keyCode < _currentKeys.Length) + { + _currentKeys[keyCode] = false; + } + } + + private void Window_PreviewMouseDown(object parSender, MouseButtonEventArgs parEventArgs) + { + var buttonCode = ConvertToMouseButtonCode(parEventArgs.ChangedButton); + if (buttonCode >= 0 && buttonCode < _currentMouseButtons.Length) + { + _currentMouseButtons[buttonCode] = true; + } + } + + private void Window_PreviewMouseUp(object parSender, MouseButtonEventArgs parEventArgs) + { + var buttonCode = ConvertToMouseButtonCode(parEventArgs.ChangedButton); + if (buttonCode >= 0 && buttonCode < _currentMouseButtons.Length) + { + _currentMouseButtons[buttonCode] = false; + } + } + + public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) + { + var keyCode = (int)parKeyboardButtonCode; + return _currentKeys[keyCode]; + } + + public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) + { + var keyCode = (int)parKeyboardButtonCode; + return _currentKeys[keyCode] && !_previousKeys[keyCode]; + } + + public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) + { + var buttonCode = (int)parButtonCode; + return _currentMouseButtons[buttonCode]; + } + + public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) + { + var buttonCode = (int)parButtonCode; + return _currentMouseButtons[buttonCode] && !_previousMouseButtons[buttonCode]; + } + + private static int ConvertToKeyboardButtonCode(Key parKey) => parKey switch + { + Key.A => (int)KeyboardButtonCode.A, + Key.B => (int)KeyboardButtonCode.B, + Key.C => (int)KeyboardButtonCode.C, + Key.D => (int)KeyboardButtonCode.D, + Key.E => (int)KeyboardButtonCode.E, + Key.F => (int)KeyboardButtonCode.F, + Key.G => (int)KeyboardButtonCode.G, + Key.H => (int)KeyboardButtonCode.H, + Key.I => (int)KeyboardButtonCode.I, + Key.J => (int)KeyboardButtonCode.J, + Key.K => (int)KeyboardButtonCode.K, + Key.L => (int)KeyboardButtonCode.L, + Key.M => (int)KeyboardButtonCode.M, + Key.N => (int)KeyboardButtonCode.N, + Key.O => (int)KeyboardButtonCode.O, + Key.P => (int)KeyboardButtonCode.P, + Key.Q => (int)KeyboardButtonCode.Q, + Key.R => (int)KeyboardButtonCode.R, + Key.S => (int)KeyboardButtonCode.S, + Key.T => (int)KeyboardButtonCode.T, + Key.U => (int)KeyboardButtonCode.U, + Key.V => (int)KeyboardButtonCode.V, + Key.W => (int)KeyboardButtonCode.W, + Key.X => (int)KeyboardButtonCode.X, + Key.Y => (int)KeyboardButtonCode.Y, + Key.Z => (int)KeyboardButtonCode.Z, + Key.LeftCtrl => (int)KeyboardButtonCode.Ctrl, + Key.LeftAlt => (int)KeyboardButtonCode.Alt, + Key.LeftShift => (int)KeyboardButtonCode.Shift, + Key.RightCtrl => (int)KeyboardButtonCode.Ctrl, + Key.RightAlt => (int)KeyboardButtonCode.Alt, + Key.RightShift => (int)KeyboardButtonCode.Shift, + Key.Space => (int)KeyboardButtonCode.Space, + Key.Tab => (int)KeyboardButtonCode.Tab, + Key.Back => (int)KeyboardButtonCode.Backspace, + Key.Return => (int)KeyboardButtonCode.Enter, + Key.Escape => (int)KeyboardButtonCode.Escape, + Key.PageUp => (int)KeyboardButtonCode.PageUp, + Key.PageDown => (int)KeyboardButtonCode.PageDown, + Key.Up => (int)KeyboardButtonCode.Up, + Key.Down => (int)KeyboardButtonCode.Down, + Key.Left => (int)KeyboardButtonCode.Left, + Key.Right => (int)KeyboardButtonCode.Right, + Key.Delete => (int)KeyboardButtonCode.Delete, + Key.Insert => (int)KeyboardButtonCode.Insert, + Key.Home => (int)KeyboardButtonCode.Home, + Key.End => (int)KeyboardButtonCode.End, + _ => -1 + }; + + private static int ConvertToMouseButtonCode(MouseButton parButton) => + parButton switch + { + MouseButton.Left => (int)MouseButtonCode.Left, + MouseButton.Right => (int)MouseButtonCode.Right, + MouseButton.Middle => (int)MouseButtonCode.Middle, + _ => -1 + }; + + ~WpfInputHandler() + { + _window.PreviewKeyDown -= Window_PreviewKeyDown; + _window.PreviewKeyUp -= Window_PreviewKeyUp; + _window.PreviewMouseDown -= Window_PreviewMouseDown; + _window.PreviewMouseUp -= Window_PreviewMouseUp; + } +} \ No newline at end of file