This commit is contained in:
2024-12-16 04:28:45 +03:00
parent ef922486eb
commit dbe7aebd4f
57 changed files with 895 additions and 374 deletions

View File

@@ -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;
}

View File

@@ -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>();

View File

@@ -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
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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; }

View File

@@ -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;
});
}
}

View File

@@ -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;

View 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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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
}

View File

@@ -1,13 +0,0 @@
namespace Engine.Input;
public enum MouseButton
{
Left,
Right,
Middle,
Button4,
Button5,
Button6,
Button7
}

View File

@@ -0,0 +1,10 @@
namespace Engine.Input;
public enum MouseButtonCode
{
Left,
Right,
Middle,
TotalCount = Middle + 1
}

View 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)
};
}
}

View File

@@ -1,5 +1,6 @@
using Engine.Graphics.Camera;
using OpenTK.Mathematics;
using Serilog;
namespace Engine.Scene.Component.BuiltIn;

View File

@@ -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)
{

View File

@@ -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)
{

View 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);
}
}

View 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);
}
}

View File

@@ -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;
}

View File

@@ -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()
{

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -1,6 +1,4 @@
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
namespace Engine.Scene;
public class SceneManager : IUpdate, IRender
{

View File

@@ -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);