added input system and perspective camera

This commit is contained in:
2025-08-30 12:05:05 +02:00
parent 318091c337
commit 54411da2f3
10 changed files with 253 additions and 10 deletions

View File

@ -1,3 +1,8 @@
# EngineSharp
A second attempt at creating an engine using Silk.Net
A second attempt at creating an engine using Silk.Net
Using a Left-Handed Y-Up coordinate system

View File

@ -2,6 +2,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineSharp.Core", "EngineSharp.Core\EngineSharp.Core.csproj", "{A617B903-6114-448A-864F-17B157C58C53}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineSharp", "..\EngineSharp\EngineSharp.csproj", "{1D984FEE-1A61-4E35-9D00-0264D92A31F4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -12,5 +14,9 @@ Global
{A617B903-6114-448A-864F-17B157C58C53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A617B903-6114-448A-864F-17B157C58C53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A617B903-6114-448A-864F-17B157C58C53}.Release|Any CPU.Build.0 = Release|Any CPU
{1D984FEE-1A61-4E35-9D00-0264D92A31F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D984FEE-1A61-4E35-9D00-0264D92A31F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D984FEE-1A61-4E35-9D00-0264D92A31F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D984FEE-1A61-4E35-9D00-0264D92A31F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,7 @@
namespace EngineSharp.Core;
public static class NumberExtensions
{
public static float ToRadians(this float degrees) => degrees * MathF.PI / 180.0f;
public static double ToRadians(this double degrees) => degrees * Math.PI / 180.0;
}

View File

@ -0,0 +1,36 @@
using Silk.NET.Maths;
namespace EngineSharp.Core;
// TODO: Pretty useless currently as I am extending instances and not the class sadly
public static class VectorExtensions
{
public static Vector3D<T> Up<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => Vector3D<T>.UnitY;
public static Vector3D<T> Down<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => -Vector3D<T>.UnitY;
public static Vector3D<T> Left<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => Vector3D<T>.UnitX;
public static Vector3D<T> Right<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => -Vector3D<T>.UnitX;
public static Vector3D<T> Front<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => -Vector3D<T>.UnitZ;
public static Vector3D<T> Back<T>(this Vector3D<T> _) where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T> => -Vector3D<T>.UnitZ;
// Thanks to: https://stackoverflow.com/a/67920029
// public static Vector3D<T> Slerp<T>(this Vector3D<T> start, Vector3D<T> end, float percent)
// where T : unmanaged, IFormattable, IEquatable<T>, IComparable<T>
// {
// // the cosine of the angle between 2 vectors.
// var dot = Vector3D.Dot(start, end);
//
// // Clamp it to be in the range of Acos()
// // This may be unnecessary, but floating point precision can be a fickle mistress.
// Math.Clamp(dot, -1, 1);
//
// // Acos(dot) returns the angle between start and end,
// // And multiplying that by percent returns the angle between start and the final result.
// var theta = Math.Acos(dot) * percent;
// var relativeVec = end - start * dot;
// relativeVec = Vector3D.Normalize(relativeVec);
//
// // Orthonormal basis
// // The final result.
// return ((start * Math.Cos(theta)) + (relativeVec * Math.Sin(theta)));
// }
}

View File

@ -0,0 +1,28 @@
using Silk.NET.Windowing;
namespace EngineSharp.Core;
public interface IEngine
{
void Start();
void Stop();
}
public enum GraphicsAPI
{
OpenGL,
Vulkan,
}
public static class EngineFactory
{
public static IEngine Create(GraphicsAPI api, WindowOptions windowOptions)
{
return api switch
{
GraphicsAPI.OpenGL => new OpenGLEngine(windowOptions),
GraphicsAPI.Vulkan => throw new NotSupportedException($"The graphics API {api} is not currently supported"),
_ => throw new ArgumentException($"The graphics API {api} is unknown.", nameof(api))
};
}
}

View File

@ -0,0 +1,52 @@
using Silk.NET.Input;
namespace EngineSharp.Core;
public class Input
{
private readonly IInputContext _context;
private readonly IDictionary<IKeyboard, HashSet<Key>> _pressedKeys = new Dictionary<IKeyboard, HashSet<Key>>();
// TODO: Add mouse
internal Input(IInputContext inputContext)
{
_context = inputContext;
}
/// <summary>
/// Returns true if the given key has been pressed on any keyboard.
/// </summary>
public bool IsKeyPressed(Key key) => _pressedKeys.Values.Any(k => k.Contains(key));
/// <summary>
/// Returns true if the given key is pressed on the given keyboard.
/// </summary>
public bool IsKeyPressed(IKeyboard keyboard, Key key)
{
if (_pressedKeys.TryGetValue(keyboard, out var pressedKeys))
{
return pressedKeys.Contains(key);
}
return false;
}
private void OnKeyDown(IKeyboard keyboard, Key key, int keyCode)
{
if (_pressedKeys.TryGetValue(keyboard, out var existingSet))
{
existingSet.Add(key);
}
else
{
var set = new HashSet<Key> { key };
_pressedKeys.Add(keyboard, set);
}
}
private void OnKeyUp(IKeyboard keyboard, Key key, int keyCode)
{
if (_pressedKeys.TryGetValue(keyboard, out var existingSet))
{
existingSet.Remove(key);
}
}
}

View File

@ -1,15 +1,16 @@
using Silk.NET.Windowing;
using System.Drawing;
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
using Silk.NET.Windowing;
namespace EngineSharp.Core;
public interface IEngine
{
void Start();
}
public class OpenGLEngine : IEngine
internal class OpenGLEngine : IEngine
{
private readonly IWindow _window;
private GL _gl = null!; // because between constructing the engine and OnLoad being called nothing should be able to happen, we do this to get rid of warnings
private Input _input = null!;
public OpenGLEngine(WindowOptions windowOptions)
{
@ -17,16 +18,26 @@ public class OpenGLEngine : IEngine
_window.Load += OnLoad;
_window.Update += OnUpdate;
_window.Render += OnRender;
_window.Resize += OnResize;
}
public void Start()
{
_window.Run();
}
public void Stop()
{
_window.Close();
}
private void OnLoad()
{
// TODO: Create openGL context etc.
_gl = _window.CreateOpenGL();
_input = new Input(_window.CreateInput());
_gl.ClearColor(Color.Black);
_gl.Enable(EnableCap.DepthTest);
}
private void OnUpdate(double deltaTime)
@ -38,4 +49,9 @@ public class OpenGLEngine : IEngine
{
// TODO: Here render all meshes etc.
}
private void OnResize(Vector2D<int> obj)
{
// TODO: update aspect of camera; on entity component system the camera should probably read the aspect ratio from some location rather than the engine updating the aspect on the camera
}
}

View File

@ -0,0 +1,54 @@
// using System.Numerics;
using Silk.NET.Maths;
namespace EngineSharp.Core;
public class PerspectiveCamera
{
private readonly Vector3D<float> _position;
private readonly Vector3D<float> _worldUp;
private Matrix3X3<float> _intrinsicCoordinates; // right | up | front
private float _yaw;
private float _pitch;
private float _fov;
private float _nearClippingPlane;
private float _farClippingPlane;
public Matrix4X4<float> ViewMatrix => Matrix4X4.CreateLookAt(_position, _position + _intrinsicCoordinates.Column3, _worldUp); // TODO: I hope this is Left-Handed. If not I need to update the readme
public Matrix4X4<float> ProjectionMatrix => Matrix4X4.CreatePerspectiveFieldOfView(_fov, , _nearClippingPlane, _farClippingPlane);
public PerspectiveCamera(Vector3D<float> position, float yaw = -90.0f, float pitch = 0, float fov = 45.0f, float nearClippingPlane = 0.1f, float farClippingPlane = 100.0f)
{
_position = position;
_yaw = yaw;
_pitch = pitch;
_fov = fov;
_nearClippingPlane = nearClippingPlane;
_farClippingPlane = farClippingPlane;
_worldUp = Vector3D<float>.UnitY;
UpdateCameraVectors();
}
internal void UpdateAspect()
{
}
private void UpdateCameraVectors()
{
var front = Vector3D.Normalize(new Vector3D<float>(
MathF.Cos(_yaw.ToRadians()) * MathF.Cos(_pitch.ToRadians()),
MathF.Sin(_pitch.ToRadians()),
MathF.Sin(_yaw.ToRadians()) * MathF.Cos(_pitch.ToRadians())
));
var right = Vector3D.Normalize(Vector3D.Cross(front, _worldUp));
var up = Vector3D.Normalize(Vector3D.Cross(right, front));
_intrinsicCoordinates = new Matrix3X3<float>(
right.X, up.X, front.X,
right.Y, up.Y, front.Y,
right.Z, up.Z, front.Z
);
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\EngineSharp.Core\EngineSharp.Core\EngineSharp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Silk.NET.Windowing" Version="2.22.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using EngineSharp.Core;
using Silk.NET.Maths;
using Silk.NET.Windowing;
using GraphicsAPI = EngineSharp.Core.GraphicsAPI;
namespace EngineSharp;
static class Program
{
static void Main(string[] args)
{
var options = WindowOptions.Default with
{
Title = "EngineSharp Engine",
Size = new Vector2D<int>(800, 600),
};
var engine = EngineFactory.Create(GraphicsAPI.OpenGL, options);
engine.Start();
}
}