.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Serilog" Version="4.1.0"/>
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.10"/>
|
||||
<ProjectReference Include="..\Engine\Engine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
33
DoomDeathmatch/src/Component/ControllerComponent.cs
Normal file
33
DoomDeathmatch/src/Component/ControllerComponent.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
22
DoomDeathmatch/src/Component/RotateComponent.cs
Normal file
22
DoomDeathmatch/src/Component/RotateComponent.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
37
DoomDeathmatch/src/DoomDeathmatch.cs
Normal file
37
DoomDeathmatch/src/DoomDeathmatch.cs
Normal file
@@ -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<PerspectiveCamera>();
|
||||
cameraObject.AddComponent<ControllerComponent>();
|
||||
|
||||
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>();
|
||||
box2dRenderer.AddComponent<RotateComponent>();
|
||||
|
||||
var scene = new Scene();
|
||||
scene.Add(cameraObject);
|
||||
scene.Add(box2dRenderer);
|
||||
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Mesh.Vertex, uint>();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<Vector2>();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<Engine>();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Engine, IPresenter> parPresenterFunc)
|
||||
{
|
||||
_presenterFunc = parPresenterFunc;
|
||||
|
||||
@@ -8,14 +8,14 @@ namespace Engine.Graphics.Buffer;
|
||||
|
||||
public class VertexArray : OpenGlObject
|
||||
{
|
||||
// private IndexBuffer? _boundIndexBuffer;
|
||||
// private readonly Dictionary<int, VertexBuffer<IVertex>> _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<T>(VertexBuffer<T> 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<T>();
|
||||
IOrderedEnumerable<FieldInfo>? fields = IVertex.GetFields<T>();
|
||||
var fields = IVertex.GetFields<T>();
|
||||
|
||||
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
|
||||
|
||||
|
||||
@@ -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<FramebufferAttachment, IFramebufferAttachment> 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);
|
||||
|
||||
@@ -55,7 +55,7 @@ public class FramebufferBuilder(int parWidth, int parHeight)
|
||||
|
||||
public Framebuffer Build()
|
||||
{
|
||||
Dictionary<FramebufferAttachment, IFramebufferAttachment>? attachments = _attachments
|
||||
var attachments = _attachments
|
||||
.ToDictionary(
|
||||
parAttachment => parAttachment.FramebufferAttachment,
|
||||
parAttachment => parAttachment.Create(parWidth, parHeight)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ public abstract class InstancedRenderer<C, I>
|
||||
{
|
||||
protected readonly Renderer _renderer;
|
||||
|
||||
protected readonly IndexBuffer _indexBuffer;
|
||||
protected readonly VertexBuffer<C> _commonVertexBuffer;
|
||||
protected readonly VertexBuffer<I> _instanceVertexBuffer;
|
||||
protected readonly VertexArray _vertexArray;
|
||||
private readonly IndexBuffer _indexBuffer;
|
||||
private readonly VertexBuffer<C> _commonVertexBuffer;
|
||||
private readonly VertexBuffer<I> _instanceVertexBuffer;
|
||||
private readonly VertexArray _vertexArray;
|
||||
|
||||
protected readonly int _instanceCount;
|
||||
protected int _queuedInstanceCount;
|
||||
|
||||
54
Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs
Normal file
54
Engine/src/Graphics/Render/Mesh/GlobalMeshRenderer.cs
Normal file
@@ -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<Asset.Mesh.Mesh, MeshRenderer> _meshRenderers = new();
|
||||
private readonly HashSet<Asset.Mesh.Mesh> _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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>
|
||||
public class MeshRenderer(Renderer parRenderer, Asset.Mesh.Mesh parMesh, int parInstanceCount, Program parProgram)
|
||||
: InstancedRenderer<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Action> _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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Engine.Input;
|
||||
|
||||
public enum MouseButton
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
|
||||
Button4,
|
||||
Button5,
|
||||
Button6,
|
||||
Button7
|
||||
}
|
||||
10
Engine/src/Input/MouseButtonCode.cs
Normal file
10
Engine/src/Input/MouseButtonCode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Engine.Input;
|
||||
|
||||
public enum MouseButtonCode
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
|
||||
TotalCount = Middle + 1
|
||||
}
|
||||
99
Engine/src/Input/WindowInputHandler.cs
Normal file
99
Engine/src/Input/WindowInputHandler.cs
Normal file
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Engine.Graphics.Camera;
|
||||
using OpenTK.Mathematics;
|
||||
using Serilog;
|
||||
|
||||
namespace Engine.Scene.Component.BuiltIn;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
17
Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs
Normal file
17
Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
13
Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs
Normal file
13
Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ public sealed class GameObject : IUpdate, IRender
|
||||
|
||||
private readonly Queue<Action> _componentActions = new();
|
||||
|
||||
private readonly IList<Component.Component> _components = new List<Component.Component>();
|
||||
private readonly ISet<Type> _addedComponentTypes = new HashSet<Type>();
|
||||
private readonly List<Component.Component> _components = [];
|
||||
private readonly HashSet<Type> _addedComponentTypes = [];
|
||||
|
||||
public GameObject()
|
||||
{
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using Engine.Util;
|
||||
|
||||
namespace Engine.Scene;
|
||||
|
||||
public class Hierarchy<T> : IEnumerable<T>
|
||||
public class Hierarchy<T>
|
||||
where T : class
|
||||
{
|
||||
internal Dictionary<T, T?>.KeyCollection Objects => _parentLookup.Keys;
|
||||
|
||||
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
|
||||
private readonly Dictionary<T, T?> _parentLookup = new();
|
||||
|
||||
@@ -147,14 +148,4 @@ public class Hierarchy<T> : IEnumerable<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _parentLookup.Keys.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public class Scene : IUpdate, IRender
|
||||
public bool IsPlaying { get; private set; }
|
||||
public ICamera? MainCamera { get; private set; }
|
||||
|
||||
internal Hierarchy<GameObject> Hierarchy { get; } = [];
|
||||
internal Hierarchy<GameObject> Hierarchy { get; } = new();
|
||||
|
||||
private readonly Queue<Action> _sceneActions = [];
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Scene : IUpdate, IRender
|
||||
|
||||
public T? FindFirstComponent<T>() where T : Component.Component
|
||||
{
|
||||
return Hierarchy.Select(parGameObject => parGameObject.GetComponent<T>()).FirstOrDefault();
|
||||
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>()).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();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Engine.Scene.Component.BuiltIn;
|
||||
|
||||
namespace Engine.Scene;
|
||||
namespace Engine.Scene;
|
||||
|
||||
public class SceneManager : IUpdate, IRender
|
||||
{
|
||||
|
||||
@@ -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<ResizeEventArgs>? 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);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ public class HierarchyTests
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_hierarchy = [];
|
||||
_hierarchy = new Hierarchy<object>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
||||
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,126 +4,98 @@ namespace PresenterConsole;
|
||||
|
||||
public class ConsoleInputHandler : IInputHandler
|
||||
{
|
||||
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
||||
private readonly HashSet<ConsoleKey> _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)
|
||||
};
|
||||
}
|
||||
@@ -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<ResizeEventArgs>? 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<AsciiPixel>? _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<AsciiPixel>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
||||
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
||||
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
167
PresenterWpf/src/WPFInputHandler.cs
Normal file
167
PresenterWpf/src/WPFInputHandler.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user