From a3d39af85c0ab27950a2b6f81788eb49c45a639f Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Wed, 26 Mar 2025 01:15:46 +0200 Subject: [PATCH 01/28] #26 Editor improvements --- Editor/EditorWindow.cs | 78 ++++++++++++++++++++++++++++- Editor/Windows/ContextMenuWindow.cs | 6 ++- Editor/Windows/ImGuiWindowBase.cs | 24 +++++---- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index d46c5b5..b943caa 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -99,6 +99,31 @@ protected override void AfterRender(double deltaTime) { base.AfterRender(deltaTime); + EnableDocking(); + RenderMenuBar(); + + bool visible = true; + ImGui.Begin("Unsaved changes", ref visible, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove); + ImGui.Text("There are unsaved changes in the project. Are you sure you want to exist without saving?"); + + if (ImGui.Button("Save")) + { + // Save the scene and exit + } + + if (ImGui.Button("Exit without saving")) + { + // TODO: shut down the application + } + + ImGui.End(); + + foreach (var window in _windows) + window.RenderWindow(); + } + + private static void EnableDocking() + { // Enable docking ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; @@ -123,9 +148,58 @@ protected override void AfterRender(double deltaTime) ImGui.DockSpace(dockSpaceId, Vector2.Zero, ImGuiDockNodeFlags.PassthruCentralNode); ImGui.End(); + } - foreach (var window in _windows) - window.RenderWindow(); + private void RenderMenuBar() + { + // Add a main menu bar + if (ImGui.BeginMainMenuBar()) + { + RenderFileMenu(); + + if (ImGui.BeginMenu("Edit")) + { + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Windows")) + { + foreach (var window in _windows) + { + if (ImGui.MenuItem(window.Name)) + window.Open(); + } + + ImGui.EndMenu(); + } + + ImGui.EndMainMenuBar(); + } + } + + private static void RenderFileMenu() + { + if (ImGui.BeginMenu("File")) + { + if (ImGui.MenuItem("New")) + { + // Handle New action + } + if (ImGui.MenuItem("Open")) + { + // Handle Open action + } + if (ImGui.MenuItem("Save")) + { + // Handle Save action + } + if (ImGui.MenuItem("Exit")) + { + // Handle Exit action + } + + ImGui.EndMenu(); + } } /// diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs index 8b10d97..01005fe 100644 --- a/Editor/Windows/ContextMenuWindow.cs +++ b/Editor/Windows/ContextMenuWindow.cs @@ -20,7 +20,11 @@ public class ContextMenuWindow : ImGuiWindowBase public override string Name => "Context Menu"; /// - public override ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.AlwaysAutoResize; + public override ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.NoTitleBar | + ImGuiWindowFlags.NoResize | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.AlwaysAutoResize | + ImGuiWindowFlags.NoDocking; /// /// Shows the context menu. diff --git a/Editor/Windows/ImGuiWindowBase.cs b/Editor/Windows/ImGuiWindowBase.cs index a75b2b3..7b3e369 100644 --- a/Editor/Windows/ImGuiWindowBase.cs +++ b/Editor/Windows/ImGuiWindowBase.cs @@ -17,6 +17,15 @@ public abstract class ImGuiWindowBase /// Gets or sets the currently active scene. public Scene Scene { get; private set; } + /// Gets the name of the window. + public abstract string Name { get; } + + /// + public virtual ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.None; + + private readonly Dictionary _previousDockingStates = []; + private bool _isVisible = true; + /// /// Initializes a new instance of . /// @@ -26,14 +35,6 @@ public ImGuiWindowBase() Project = new Project(); } - private readonly Dictionary _previousDockingStates = []; - - /// Gets the name of the window. - public abstract string Name { get; } - - /// - public virtual ImGuiWindowFlags ImGuiWindowFlags => ImGuiWindowFlags.None; - /// /// Sets the current scene. /// @@ -46,6 +47,8 @@ public ImGuiWindowBase() /// The new project. public void SetProject(Project project) => Project = project; + public void Open() => _isVisible = true; + /// /// Executes operations required before rendering the ImGui window. /// @@ -56,9 +59,12 @@ public virtual void PreRender() { } /// public void RenderWindow() { + if (!_isVisible) + return; + PreRender(); - ImGui.Begin(Name, ImGuiWindowFlags); + ImGui.Begin(Name, ref _isVisible, ImGuiWindowFlags); Render(); From 661113fa172e89cb4894b34438facb34573b9248 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Thu, 27 Mar 2025 17:54:17 +0200 Subject: [PATCH 02/28] Method for opening multiple windows --- Examples/MultipleWindows/Program.cs | 95 +++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs index 0f3a748..b6ad037 100644 --- a/Examples/MultipleWindows/Program.cs +++ b/Examples/MultipleWindows/Program.cs @@ -1,39 +1,98 @@ using Silk.NET.Core.Native; using Silk.NET.GLFW; +using Silk.NET.Input; +using Silk.NET.Maths; using Silk.NET.OpenGL; using Silk.NET.Windowing; +using System.Collections.Concurrent; -internal class Program +// An example provided a lovely person in this thread: +// https://github.com/dotnet/Silk.NET/issues/2436#issuecomment-2752966073 +public static partial class Program { - static void Main() + private static readonly List _windows = []; + private static readonly List _inputContexts = []; + private static readonly ConcurrentQueue _windowQueue = []; + private static readonly CancellationTokenSource _cancellationTokenSource = new(); + + public static void Main(string[] _) { - var options1 = WindowOptions.Default with { Title = "Window 1", WindowClass = "MyWindowClass1" }; - var options2 = WindowOptions.Default with { Title = "Window 2", WindowClass = "MyWindowClass2" }; + StartWindowQueue(); - var window1 = Window.Create(options1); - var window2 = Window.Create(options2); + while (!_cancellationTokenSource.IsCancellationRequested) + { + for (int i = 0; i < _windows.Count; i++) + UpdateWindow(ref i); - StartWindow(window1); - StartWindow(window2); + DequeueWindows(); + } } - static void StartWindow(IWindow window) + private static void UpdateWindow(ref int i) { - window.Load += () => + var window = _windows[i]; + if (window is null) + return; + + window.DoEvents(); + window.DoUpdate(); + window.DoRender(); + + if (window.IsClosing) { - Console.WriteLine($"Hello from window: " + window.Title); + window.Reset(); + window.Dispose(); - var gl = GL.GetApi(window); + i--; // Adjust the index to account for the removed item + _windows.RemoveAt(i); + + if (_windows.Count == 0) + _cancellationTokenSource.Cancel(); + } + } + + private static void DequeueWindows() + { + if (_cancellationTokenSource.IsCancellationRequested) + return; + + while (_windowQueue.TryDequeue(out var options)) + CreateWindow(options); + } - unsafe + private static void StartWindowQueue() + => Task.Run(async () => + { + _windowQueue.Enqueue(WindowOptions.Default); + while (!_cancellationTokenSource.IsCancellationRequested) { - var versionPtr = gl.GetString(StringName.Version); - string? version = SilkMarshal.PtrToString((nint)versionPtr); - Console.WriteLine($"OpenGL version: {version}"); + await Task.Delay(1000); + Console.WriteLine("Running loop on background thread..."); } + }); + + private static void CreateWindow(WindowOptions options) + { + options.Title = "Window" + _windows.Count; + + // This is to make sure the windows don't overlap + options.Position = new Vector2D( + x: 500 + (50 * _windows.Count), + y: 400 + (50 * _windows.Count)); - }; + var window = Window.Create(options); + window.Initialize(); - window.Run(); + var inputContext = window.CreateInput(); + foreach (var mouse in inputContext.Mice) + mouse.Click += Mouse_Click; + + _inputContexts.Add(inputContext); + _windows.Add(window); + } + + private static void Mouse_Click(IMouse args1, Silk.NET.Input.MouseButton arg2, System.Numerics.Vector2 arg3) + { + _windowQueue.Enqueue(WindowOptions.Default); } } From dd0838e484f7dbb710a1bb278afc7dd1c2f9e3d5 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Thu, 27 Mar 2025 19:43:36 +0200 Subject: [PATCH 03/28] Small refactoring --- Examples/MultipleWindows/Program.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs index b6ad037..e270b17 100644 --- a/Examples/MultipleWindows/Program.cs +++ b/Examples/MultipleWindows/Program.cs @@ -43,8 +43,8 @@ private static void UpdateWindow(ref int i) window.Reset(); window.Dispose(); - i--; // Adjust the index to account for the removed item _windows.RemoveAt(i); + i--; // Adjust the index to account for the removed item if (_windows.Count == 0) _cancellationTokenSource.Cancel(); @@ -71,18 +71,27 @@ private static void StartWindowQueue() } }); - private static void CreateWindow(WindowOptions options) + private static IWindow CreateWindow() { - options.Title = "Window" + _windows.Count; + var options = WindowOptions.Default with + { + Title = "Window" + _windows.Count, - // This is to make sure the windows don't overlap - options.Position = new Vector2D( + // This is to make sure the windows don't overlap + Position = new Vector2D( x: 500 + (50 * _windows.Count), - y: 400 + (50 * _windows.Count)); + y: 400 + (50 * _windows.Count)) + }; var window = Window.Create(options); window.Initialize(); + return window; + } + + private static void CreateWindow(WindowOptions options) + { + var window = CreateWindow(); var inputContext = window.CreateInput(); foreach (var mouse in inputContext.Mice) mouse.Click += Mouse_Click; From f2b9534018897139dd6fe6bfec6fb910fe79633d Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Thu, 27 Mar 2025 19:59:36 +0200 Subject: [PATCH 04/28] Opening SharpEngine windows works --- Core/SilkWindow.cs | 40 +++++++++++++++---- .../MultipleWindows/MultipleWindows.csproj | 4 ++ Examples/MultipleWindows/Program.cs | 27 ++++++++----- 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/Core/SilkWindow.cs b/Core/SilkWindow.cs index bcc1936..a166a2f 100644 --- a/Core/SilkWindow.cs +++ b/Core/SilkWindow.cs @@ -28,22 +28,38 @@ public abstract class SilkWindow : IWindow public IMonitor? Monitor { get; set; } /// - public bool IsClosing { get; set; } + public bool IsClosing + { + get => CurrentWindow.IsClosing; + set => CurrentWindow.IsClosing = value; + } /// Gets or sets the input context for the window. public IInputContext? Input { get; protected set; } /// - public virtual Rectangle BorderSize { get; } + public virtual Rectangle BorderSize => CurrentWindow.BorderSize; /// - public bool IsVisible { get; set; } + public bool IsVisible + { + get => CurrentWindow.IsVisible; + set => CurrentWindow.IsVisible = value; + } /// - public Vector2D Position { get; set; } + public Vector2D Position + { + get => CurrentWindow.Position; + set => CurrentWindow.Position = value; + } /// - public Vector2D Size { get; set; } + public Vector2D Size + { + get => CurrentWindow.Size; + set => CurrentWindow.Size = value; + } /// public string Title @@ -56,13 +72,21 @@ public string Title public WindowState WindowState { get; set; } /// - public WindowBorder WindowBorder { get; set; } + public WindowBorder WindowBorder + { + get => CurrentWindow.WindowBorder; + set => CurrentWindow.WindowBorder = value; + } /// - public bool TransparentFramebuffer { get; } + public bool TransparentFramebuffer => CurrentWindow.TransparentFramebuffer; /// - public bool TopMost { get; set; } + public bool TopMost + { + get => CurrentWindow.TopMost; + set => CurrentWindow.TopMost = value; + } /// public IGLContext? SharedContext => CurrentWindow.SharedContext; diff --git a/Examples/MultipleWindows/MultipleWindows.csproj b/Examples/MultipleWindows/MultipleWindows.csproj index f87a3eb..c40ef83 100644 --- a/Examples/MultipleWindows/MultipleWindows.csproj +++ b/Examples/MultipleWindows/MultipleWindows.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs index e270b17..e847c53 100644 --- a/Examples/MultipleWindows/Program.cs +++ b/Examples/MultipleWindows/Program.cs @@ -5,6 +5,8 @@ using Silk.NET.OpenGL; using Silk.NET.Windowing; using System.Collections.Concurrent; +using SharpEngine.Core; +using SharpEngine.Core.Entities.Views.Settings; // An example provided a lovely person in this thread: // https://github.com/dotnet/Silk.NET/issues/2436#issuecomment-2752966073 @@ -71,19 +73,23 @@ private static void StartWindowQueue() } }); - private static IWindow CreateWindow() + private static SharpEngine.Core.Window CreateWindow() { - var options = WindowOptions.Default with + var options = new DefaultViewSettings() with { - Title = "Window" + _windows.Count, + WindowOptions = WindowOptions.Default with + { + Title = "Window" + _windows.Count, - // This is to make sure the windows don't overlap - Position = new Vector2D( - x: 500 + (50 * _windows.Count), - y: 400 + (50 * _windows.Count)) + // This is to make sure the windows don't overlap + Position = new Vector2D( + x: 500 + (50 * _windows.Count), + y: 400 + (50 * _windows.Count)) + } }; - var window = Window.Create(options); + //var window = Window.Create(options); + var window = new SharpEngine.Core.Window(new(), options); window.Initialize(); return window; @@ -92,11 +98,10 @@ private static IWindow CreateWindow() private static void CreateWindow(WindowOptions options) { var window = CreateWindow(); - var inputContext = window.CreateInput(); - foreach (var mouse in inputContext.Mice) + foreach (var mouse in window.Input.Mice) mouse.Click += Mouse_Click; - _inputContexts.Add(inputContext); + _inputContexts.Add(window.Input); _windows.Add(window); } From 7b2e0ab372630cee1c7ac6b543111751581f42e7 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Fri, 28 Mar 2025 01:52:51 +0200 Subject: [PATCH 05/28] UI improvements (WIP) A lot of stuff is unfinished. Context menu locations, launcher styling, xml documentation missing etc. Functionality should be working tho --- Core/Entities/GameObject.cs | 2 +- .../Entities/Properties/Meshes/MeshService.cs | 3 +- Core/Entities/UIElement.cs | 2 +- Core/GlobalSuppressions.cs | 8 --- Core/Math.cs | 2 +- Core/Renderers/Renderer.cs | 2 +- Core/Renderers/UIRenderer.cs | 1 + Core/Shaders/LampShader.cs | 1 + Core/Shaders/LightingShader.cs | 1 + Core/Shaders/Shader.cs | 7 +++ Core/Shaders/UIShader.cs | 1 + Core/Textures/Texture.cs | 3 +- Core/Textures/TextureService.cs | 1 + Core/{ => Windowing}/SilkWindow.cs | 2 +- Core/{ => Windowing}/Window.cs | 2 +- Editor/EditorWindow.cs | 26 +++++++-- Editor/Windows/AssetsWindow.cs | 3 ++ Editor/Windows/ImGuiWindowBase.cs | 2 +- Examples/MultipleWindows/Program.cs | 25 ++++----- Launcher/Services/EditorService.cs | 3 ++ Launcher/TestData/TestData.cs | 17 ++++++ Launcher/UI/Components/ContextMenu.razor | 46 ++++++++++++++++ Launcher/UI/Components/DiscardIcon.razor | 2 +- Launcher/UI/Components/Flyouts/Flyout.razor | 41 ++++++++++++++ .../UI/Components/Flyouts/Flyout.razor.scss | 0 Launcher/UI/Components/Flyouts/FlyoutBase.cs | 21 ++++++++ .../Components/Flyouts/SettingsFlyout.razor | 12 +++++ Launcher/UI/Components/Icon.razor | 51 ++++++++++++++++++ Launcher/UI/Components/Layout.razor | 24 +++++++-- .../Components/Navigation/SideBarItem.razor | 12 +++++ .../Navigation/SideBarItem.razor.scss | 3 ++ .../UI/Components/Navigation/Sidebar.razor | 10 ++++ Launcher/UI/Components/Searchbox.razor | 28 ++++++++++ Launcher/UI/Enums/SvgIcon.cs | 10 ++++ Launcher/UI/Home.razor | 23 ++++++-- Launcher/UI/Home.razor.cs | 53 +++++++++++++++++-- Launcher/UI/_Imports.razor | 3 +- Launcher/wwwroot/css/app.css | 4 +- 38 files changed, 410 insertions(+), 47 deletions(-) delete mode 100644 Core/GlobalSuppressions.cs rename Core/{ => Windowing}/SilkWindow.cs (99%) rename Core/{ => Windowing}/Window.cs (99%) create mode 100644 Launcher/TestData/TestData.cs create mode 100644 Launcher/UI/Components/ContextMenu.razor create mode 100644 Launcher/UI/Components/Flyouts/Flyout.razor create mode 100644 Launcher/UI/Components/Flyouts/Flyout.razor.scss create mode 100644 Launcher/UI/Components/Flyouts/FlyoutBase.cs create mode 100644 Launcher/UI/Components/Flyouts/SettingsFlyout.razor create mode 100644 Launcher/UI/Components/Icon.razor create mode 100644 Launcher/UI/Components/Navigation/SideBarItem.razor create mode 100644 Launcher/UI/Components/Navigation/SideBarItem.razor.scss create mode 100644 Launcher/UI/Components/Navigation/Sidebar.razor create mode 100644 Launcher/UI/Components/Searchbox.razor create mode 100644 Launcher/UI/Enums/SvgIcon.cs diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index b69e257..0dbb25f 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -5,7 +5,7 @@ using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Textures; - +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; using System.Collections.Generic; using System.IO; diff --git a/Core/Entities/Properties/Meshes/MeshService.cs b/Core/Entities/Properties/Meshes/MeshService.cs index 67e5300..7f7ed06 100644 --- a/Core/Entities/Properties/Meshes/MeshService.cs +++ b/Core/Entities/Properties/Meshes/MeshService.cs @@ -1,4 +1,5 @@ -using Silk.NET.OpenGL; +using SharpEngine.Core.Windowing; +using Silk.NET.OpenGL; using System.Collections.Generic; namespace SharpEngine.Core.Entities.Properties.Meshes; diff --git a/Core/Entities/UIElement.cs b/Core/Entities/UIElement.cs index 1664569..f367df0 100644 --- a/Core/Entities/UIElement.cs +++ b/Core/Entities/UIElement.cs @@ -2,7 +2,7 @@ using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; - +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; using System; using System.Threading.Tasks; diff --git a/Core/GlobalSuppressions.cs b/Core/GlobalSuppressions.cs deleted file mode 100644 index b944386..0000000 --- a/Core/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Minor Code Smell", "S2344:Enumeration type names should not have \"Flags\" or \"Enum\" suffixes", Justification = "", Scope = "type", Target = "~T:SharpEngine.Core.Renderers.RenderFlags")] diff --git a/Core/Math.cs b/Core/Math.cs index 5006ec2..102f871 100644 --- a/Core/Math.cs +++ b/Core/Math.cs @@ -27,7 +27,7 @@ public static ReadOnlySpan ToSpan(this Matrix4x4 matrix) /// /// Converts a value in to radians. /// - /// The degress to be converted + /// The degrees to be converted. /// The given in radians. public static double DegreesToRadians(double degrees) => System.Math.PI / 180 * degrees; diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index ae52bed..5fd0a5d 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -4,7 +4,7 @@ using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; - +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; using System; diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 47a6e4c..3cb617a 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -3,6 +3,7 @@ using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; using System; diff --git a/Core/Shaders/LampShader.cs b/Core/Shaders/LampShader.cs index aa63ddb..b28730f 100644 --- a/Core/Shaders/LampShader.cs +++ b/Core/Shaders/LampShader.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Extensions; +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; namespace SharpEngine.Core.Shaders; diff --git a/Core/Shaders/LightingShader.cs b/Core/Shaders/LightingShader.cs index 8ddd2af..17e7a9f 100644 --- a/Core/Shaders/LightingShader.cs +++ b/Core/Shaders/LightingShader.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Extensions; +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; using System; diff --git a/Core/Shaders/Shader.cs b/Core/Shaders/Shader.cs index 0ae4049..1ea1e8d 100644 --- a/Core/Shaders/Shader.cs +++ b/Core/Shaders/Shader.cs @@ -6,6 +6,7 @@ using System.Numerics; using Silk.NET.OpenGL; +using SharpEngine.Core.Windowing; namespace SharpEngine.Core.Shaders; @@ -42,6 +43,12 @@ public Shader(string vertPath, string fragPath, string name) // The fragment shader is responsible for then converting the vertices to "fragments", which represent all the data OpenGL needs to draw a pixel. // The fragment shader is what we'll be using the most here. + if (!vertPath.EndsWith(".vert")) + Console.WriteLine("Vertex shaders should have the file extension '.vert' for easier manageability."); + + if (!fragPath.EndsWith(".frag")) + Console.WriteLine("Fragment shaders should have the file extension '.frag' for easier manageability."); + // Load and compile shader if (!LoadShader(ShaderType.VertexShader, vertPath, out uint vertexShader)) { diff --git a/Core/Shaders/UIShader.cs b/Core/Shaders/UIShader.cs index 126b2fe..de44857 100644 --- a/Core/Shaders/UIShader.cs +++ b/Core/Shaders/UIShader.cs @@ -1,4 +1,5 @@ using SharpEngine.Core.Extensions; +using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; namespace SharpEngine.Core.Shaders; diff --git a/Core/Textures/Texture.cs b/Core/Textures/Texture.cs index 689b1a7..a947e3e 100644 --- a/Core/Textures/Texture.cs +++ b/Core/Textures/Texture.cs @@ -1,4 +1,5 @@ -using Silk.NET.OpenGL; +using SharpEngine.Core.Windowing; +using Silk.NET.OpenGL; namespace SharpEngine.Core.Textures; diff --git a/Core/Textures/TextureService.cs b/Core/Textures/TextureService.cs index 89b4bcf..05b1775 100644 --- a/Core/Textures/TextureService.cs +++ b/Core/Textures/TextureService.cs @@ -5,6 +5,7 @@ using System; using Silk.NET.OpenGL; +using SharpEngine.Core.Windowing; namespace SharpEngine.Core.Textures; diff --git a/Core/SilkWindow.cs b/Core/Windowing/SilkWindow.cs similarity index 99% rename from Core/SilkWindow.cs rename to Core/Windowing/SilkWindow.cs index a166a2f..4dc8267 100644 --- a/Core/SilkWindow.cs +++ b/Core/Windowing/SilkWindow.cs @@ -11,7 +11,7 @@ using System.IO; using System.Numerics; -namespace SharpEngine.Core; +namespace SharpEngine.Core.Windowing; /// /// Represents an abstraction for the interface. diff --git a/Core/Window.cs b/Core/Windowing/Window.cs similarity index 99% rename from Core/Window.cs rename to Core/Windowing/Window.cs index cb1d7e3..6be21ca 100644 --- a/Core/Window.cs +++ b/Core/Windowing/Window.cs @@ -22,7 +22,7 @@ using System.Numerics; using System.Threading.Tasks; -namespace SharpEngine.Core; +namespace SharpEngine.Core.Windowing; /// /// Represents the game window. diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index b943caa..beb1ef4 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -13,6 +13,7 @@ using System.Numerics; using System.Reflection; +using SharpEngine.Core.Windowing; namespace SharpEngine.Editor; @@ -159,6 +160,17 @@ private void RenderMenuBar() if (ImGui.BeginMenu("Edit")) { + if (ImGui.MenuItem("Settings")) + { + // Handle New action + + // TODO: Select preferred editor + // Required fields: + // radio button, each defined editor (store as json) + // textbox,Path to executable + // textbox, cli argument to open a file + } + ImGui.EndMenu(); } @@ -209,13 +221,15 @@ public override void OnMouseClick(IMouse mouse, MouseButton button, Vector2 vect if (button == Settings.PrimaryButton) { - if (ImGui.IsAnyItemHovered()) + // TODO: This probably isn't doable using ImGui. Figure out a new way or wait until UI is written using the Core 2D renderer. + if (ImGui.IsItemClicked((ImGuiMouseButton)button)) { - // TODO: Right-clicked on an ImGui component + // Clicked on an ImGui component + uint hoveredItemId = ImGui.GetID(""); + Debug.LogInformation($"Clicked on ImGui component with ID: {hoveredItemId}"); } else { - // TODO: Right-clicked outside of ImGui components Camera.IsInView(Scene, out GameObject? intersectingObject, out Vector3 hitPosition); _contextMenuWindow?.ShowContextMenu(intersectingObject); } @@ -223,9 +237,11 @@ public override void OnMouseClick(IMouse mouse, MouseButton button, Vector2 vect if (button == Settings.SecondaryButton) { - if (ImGui.IsAnyItemHovered()) + if (ImGui.IsItemClicked((ImGuiMouseButton)button)) { - // TODO: Left-clicked on an ImGui component + // Clicked on an ImGui component + uint hoveredItemId = ImGui.GetID(""); + Debug.LogInformation($"Clicked on ImGui component with ID: {hoveredItemId}"); } else { diff --git a/Editor/Windows/AssetsWindow.cs b/Editor/Windows/AssetsWindow.cs index b9b7051..ad5d876 100644 --- a/Editor/Windows/AssetsWindow.cs +++ b/Editor/Windows/AssetsWindow.cs @@ -33,6 +33,9 @@ private static void RenderDirectory(string path) foreach (var file in files) if (ImGui.Selectable(Path.GetFileName(file))) { + // TODO: Open the file in the IDE + // Process.Start(Settings.PreferredIDE.Name + Settings.PreferredIDE.CLIArguments + file); + // Scene.ActiveElement = file; // Set the file as the active element } } diff --git a/Editor/Windows/ImGuiWindowBase.cs b/Editor/Windows/ImGuiWindowBase.cs index 7b3e369..841e030 100644 --- a/Editor/Windows/ImGuiWindowBase.cs +++ b/Editor/Windows/ImGuiWindowBase.cs @@ -98,7 +98,7 @@ private void CreateSilkWindow() var thread = new Thread(() => { - var window = new Core.Window(new(), new DefaultViewSettings()); + var window = new Core.Windowing.Window(new(), new DefaultViewSettings()); window.Closing += () => _previousDockingStates[Name] = false; window.OnAfterRender += deltaTime => diff --git a/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs index e847c53..83caac5 100644 --- a/Examples/MultipleWindows/Program.cs +++ b/Examples/MultipleWindows/Program.cs @@ -1,11 +1,8 @@ -using Silk.NET.Core.Native; -using Silk.NET.GLFW; -using Silk.NET.Input; +using Silk.NET.Input; using Silk.NET.Maths; -using Silk.NET.OpenGL; using Silk.NET.Windowing; + using System.Collections.Concurrent; -using SharpEngine.Core; using SharpEngine.Core.Entities.Views.Settings; // An example provided a lovely person in this thread: @@ -17,9 +14,13 @@ public static partial class Program private static readonly ConcurrentQueue _windowQueue = []; private static readonly CancellationTokenSource _cancellationTokenSource = new(); + /// + /// The main entry point of the application. + /// + /// Arguments discarded. public static void Main(string[] _) { - StartWindowQueue(); + StartWindowQueueTask(); while (!_cancellationTokenSource.IsCancellationRequested) { @@ -59,10 +60,10 @@ private static void DequeueWindows() return; while (_windowQueue.TryDequeue(out var options)) - CreateWindow(options); + EnqueueWindow(options); } - private static void StartWindowQueue() + private static void StartWindowQueueTask() => Task.Run(async () => { _windowQueue.Enqueue(WindowOptions.Default); @@ -73,7 +74,7 @@ private static void StartWindowQueue() } }); - private static SharpEngine.Core.Window CreateWindow() + private static SharpEngine.Core.Windowing.Window CreateWindow() { var options = new DefaultViewSettings() with { @@ -89,16 +90,16 @@ private static SharpEngine.Core.Window CreateWindow() }; //var window = Window.Create(options); - var window = new SharpEngine.Core.Window(new(), options); + var window = new SharpEngine.Core.Windowing.Window(new(), options); window.Initialize(); return window; } - private static void CreateWindow(WindowOptions options) + private static void EnqueueWindow(WindowOptions options) { var window = CreateWindow(); - foreach (var mouse in window.Input.Mice) + foreach (var mouse in window!.Input!.Mice) mouse.Click += Mouse_Click; _inputContexts.Add(window.Input); diff --git a/Launcher/Services/EditorService.cs b/Launcher/Services/EditorService.cs index 22142f6..a503ff3 100644 --- a/Launcher/Services/EditorService.cs +++ b/Launcher/Services/EditorService.cs @@ -159,7 +159,10 @@ public List LoadProjects(string projectsFile) var projects = System.Text.Json.JsonSerializer.Deserialize>(json); if (projects is not null) + { + // TOOD: Resolve last modified return projects; + } _notificationService.Show($"Unable to load projects.", false, json, projects?.ToArray()); return []; diff --git a/Launcher/TestData/TestData.cs b/Launcher/TestData/TestData.cs new file mode 100644 index 0000000..44036eb --- /dev/null +++ b/Launcher/TestData/TestData.cs @@ -0,0 +1,17 @@ +using Launcher.UI; + +namespace Launcher.TestData; +public static class TestData +{ + public const bool LoadTestData = true; + + public const bool SimulateLoading = false; + + public static IEnumerable Projects = Enumerable.Range(0, 3).Select(i => + { + return new Project() + { + Name = $"Project {i}", + }; + }); +} diff --git a/Launcher/UI/Components/ContextMenu.razor b/Launcher/UI/Components/ContextMenu.razor new file mode 100644 index 0000000..3e59bc2 --- /dev/null +++ b/Launcher/UI/Components/ContextMenu.razor @@ -0,0 +1,46 @@ +@if (showMenu) +{ +
+ @ChildContent(Context) +
+} + + + +@code { + [Inject] + private IJSRuntime JS { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + public object Context { get; set; } + + private bool showMenu = false; + private int menuX, menuY; + + private string menuXpx => $"{menuX}px"; + private string menuYpx => $"{menuY}px"; + + public void Open(MouseEventArgs e, object context) + { + menuX = (int)e.ClientX; + menuY = (int)e.ClientY; + showMenu = true; + + Context = context; + + // Prevent the default browser context menu + JS.InvokeVoidAsync("preventContextMenu"); + } +} diff --git a/Launcher/UI/Components/DiscardIcon.razor b/Launcher/UI/Components/DiscardIcon.razor index 0b36549..62597d0 100644 --- a/Launcher/UI/Components/DiscardIcon.razor +++ b/Launcher/UI/Components/DiscardIcon.razor @@ -1,4 +1,4 @@ -✖ + @code { diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor b/Launcher/UI/Components/Flyouts/Flyout.razor new file mode 100644 index 0000000..c7abf26 --- /dev/null +++ b/Launcher/UI/Components/Flyouts/Flyout.razor @@ -0,0 +1,41 @@ +@inherits FlyoutBase + +
+
+
+ @if (Header is not null) + { + @Header + } + else + { +

@Title

+ } +
+
+ +
+
+
+ @Content +
+ +
+ +@code { + [Parameter] + public RenderFragment Header { get; set; } + + [Parameter] + public RenderFragment Content { get; set; } + + [Parameter] + public RenderFragment Footer { get; set; } + + [Parameter] + public string Title { get; set; } = default!; + + private string _isVisibleStyle => IsVisible ? "visible" : "hidden"; +} diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor.scss b/Launcher/UI/Components/Flyouts/Flyout.razor.scss new file mode 100644 index 0000000..e69de29 diff --git a/Launcher/UI/Components/Flyouts/FlyoutBase.cs b/Launcher/UI/Components/Flyouts/FlyoutBase.cs new file mode 100644 index 0000000..18e5a85 --- /dev/null +++ b/Launcher/UI/Components/Flyouts/FlyoutBase.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Components; + +namespace Launcher.UI.Components.Flyouts; + +public class FlyoutBase : ComponentBase +{ + [Parameter] + public bool IsVisible { get; set; } + + [Parameter] + public EventCallback IsVisibleChanged { get; set; } + + [Parameter] + public EventCallback OnClose { get; set; } + + public virtual async Task OnIsVisibleChangedAsync(bool visibility) + { + IsVisible = visibility; + await IsVisibleChanged.InvokeAsync(visibility); + } +} diff --git a/Launcher/UI/Components/Flyouts/SettingsFlyout.razor b/Launcher/UI/Components/Flyouts/SettingsFlyout.razor new file mode 100644 index 0000000..2326ad2 --- /dev/null +++ b/Launcher/UI/Components/Flyouts/SettingsFlyout.razor @@ -0,0 +1,12 @@ +@inherits FlyoutBase + + +
+ + +
+
+ +@code { + +} diff --git a/Launcher/UI/Components/Icon.razor b/Launcher/UI/Components/Icon.razor new file mode 100644 index 0000000..8d0dcce --- /dev/null +++ b/Launcher/UI/Components/Icon.razor @@ -0,0 +1,51 @@ +@switch (IconType) +{ + case SvgIcon.Home: + + + + + + + + break; + case SvgIcon.Cog: + + + + + + + + break; + case SvgIcon.Lens: + + + + + + + break; + case SvgIcon.Discard: + ✖ + break; + case SvgIcon.Close: + + + + + + + + break; + default: + break; +} + +@code { + [Parameter] + public SvgIcon IconType { get; set; } + + [Parameter] + public int SizePx { get; set; } = 16; +} diff --git a/Launcher/UI/Components/Layout.razor b/Launcher/UI/Components/Layout.razor index 21adf40..21bba39 100644 --- a/Launcher/UI/Components/Layout.razor +++ b/Launcher/UI/Components/Layout.razor @@ -1,5 +1,23 @@ -@inherits LayoutComponentBase +@using Launcher.UI.Components.Navigation +@using Launcher.UI.Components.Flyouts + +@inherits LayoutComponentBase
-@Body -
\ No newline at end of file + + + + +
+ @Body +
+ + + + +@code { + private bool _settingsVisible; + + private void OpenSettingsFlyout() + => _settingsVisible = true; +} \ No newline at end of file diff --git a/Launcher/UI/Components/Navigation/SideBarItem.razor b/Launcher/UI/Components/Navigation/SideBarItem.razor new file mode 100644 index 0000000..a34d252 --- /dev/null +++ b/Launcher/UI/Components/Navigation/SideBarItem.razor @@ -0,0 +1,12 @@ + + +@code { + [Parameter] + public SvgIcon Icon { get; set; } + + [Parameter] + public EventCallback OnClick { get; set; } + + private async Task OnItemClicked() + => await OnClick.InvokeAsync(); +} diff --git a/Launcher/UI/Components/Navigation/SideBarItem.razor.scss b/Launcher/UI/Components/Navigation/SideBarItem.razor.scss new file mode 100644 index 0000000..0d97a28 --- /dev/null +++ b/Launcher/UI/Components/Navigation/SideBarItem.razor.scss @@ -0,0 +1,3 @@ +.sidebar-item { + +} \ No newline at end of file diff --git a/Launcher/UI/Components/Navigation/Sidebar.razor b/Launcher/UI/Components/Navigation/Sidebar.razor new file mode 100644 index 0000000..19ae0d8 --- /dev/null +++ b/Launcher/UI/Components/Navigation/Sidebar.razor @@ -0,0 +1,10 @@ + + +@code { + [Parameter, EditorRequired] + public RenderFragment ChildContent { get; set; } +} diff --git a/Launcher/UI/Components/Searchbox.razor b/Launcher/UI/Components/Searchbox.razor new file mode 100644 index 0000000..0490ea2 --- /dev/null +++ b/Launcher/UI/Components/Searchbox.razor @@ -0,0 +1,28 @@ +
+ + +
+ +@code { + [Parameter] + public string Value { get; set; } = default!; + + [Parameter] + public EventCallback ValueChanged { get; set; } + + [Parameter] + public int DebounceDelayMilliseconds { get; set; } = 1000; + + private CancellationTokenSource _tokenSource { get; set; } = new(); + + private async Task OnChangeAsync(ChangeEventArgs e) + { + _tokenSource = new CancellationTokenSource(); + var token = _tokenSource.Token; + + await Task.Delay(DebounceDelayMilliseconds, token); + + Value = e.Value?.ToString() ?? string.Empty; + await ValueChanged.InvokeAsync(Value); + } +} diff --git a/Launcher/UI/Enums/SvgIcon.cs b/Launcher/UI/Enums/SvgIcon.cs new file mode 100644 index 0000000..1b432f7 --- /dev/null +++ b/Launcher/UI/Enums/SvgIcon.cs @@ -0,0 +1,10 @@ +namespace Launcher.UI.Enums; + +public enum SvgIcon +{ + Home, + Cog, + Discard, + Lens, + Close +} diff --git a/Launcher/UI/Home.razor b/Launcher/UI/Home.razor index 35fb873..f47f962 100644 --- a/Launcher/UI/Home.razor +++ b/Launcher/UI/Home.razor @@ -17,15 +17,32 @@ + + +
- + @context.Name @context.LastModified - + -
\ No newline at end of file + + + + @{ + var project = (Project)context; + + + + + } + + +@code { + private ContextMenu? _contextMenu; +} \ No newline at end of file diff --git a/Launcher/UI/Home.razor.cs b/Launcher/UI/Home.razor.cs index 71c52fb..99af3c2 100644 --- a/Launcher/UI/Home.razor.cs +++ b/Launcher/UI/Home.razor.cs @@ -1,7 +1,7 @@ using Launcher.Services; using Microsoft.AspNetCore.Components; +using System.Diagnostics; using System.Text.Json; -using System.Text.Json.Serialization; namespace Launcher.UI { @@ -17,12 +17,24 @@ public partial class Home : ComponentBase private bool _showConfirmDelete; private bool _showCreateProjectDialog; - private List Projects { get; set; } = []; + private List Projects + { + get => _allProjects; + set + { + _allProjects = value; + _filteredProjects = value; + } + } + + private List _allProjects = []; + private List _filteredProjects = []; + private Project _selectedProject = new(); private const string _projectsFile = "projects.json"; private static readonly string[] _sharpProjectFilePickerExtension = [".sharpproject"]; -#if DEBUG +#if !DEBUG private readonly string _projectsFilePath = Path.Join(Path.GetTempPath(), _projectsFile); #else private readonly string _projectsFilePath = Path.Join(AppContext.BaseDirectory, _projectsFile); @@ -35,14 +47,32 @@ protected override async Task OnInitializedAsync() // TODO: Get current launcher version // TODO: Check if GitHub has a new version released. +#if DEBUG + if (TestData.TestData.SimulateLoading) + { + _notificationService.Show("Simulating loading projects."); + await Task.Delay(10000); + } + + if (TestData.TestData.LoadTestData) + { + _notificationService.Show("Using test data."); + Projects = [.. TestData.TestData.Projects]; + return; + } +#endif + var projects = _editorService.LoadProjects(_projectsFilePath); - + if (projects is not null) Projects.AddRange(projects); else _notificationService.Show($"Unable to load project file.", false, projects); } + private void SearchProjects(string searchValue) + => _filteredProjects = [.. Projects.Where(p => p.Name!.Contains(searchValue, StringComparison.OrdinalIgnoreCase))]; + private void OnCreateProjectClicked() => _showCreateProjectDialog = true; @@ -113,5 +143,20 @@ private void SetProjects(Project project) string json = JsonSerializer.Serialize(Projects); File.WriteAllText(_projectsFilePath, json); } + + private static void OpenInExplorer(string projectPath) + => Process.Start("explorer.exe", projectPath); + + private void RemoveFromList(Project project) + { + var foundProject = Projects.Find(p => p.Id == project.Id); + if (foundProject is null) + { + _notificationService.Show($"Unable to remove project '{project.Name}', project not found.", false, project); + return; + } + + Projects.Remove(foundProject); + } } } diff --git a/Launcher/UI/_Imports.razor b/Launcher/UI/_Imports.razor index 464f868..5047218 100644 --- a/Launcher/UI/_Imports.razor +++ b/Launcher/UI/_Imports.razor @@ -9,4 +9,5 @@ @using Launcher @using Launcher.UI -@using Launcher.Services \ No newline at end of file +@using Launcher.Services +@using Launcher.UI.Enums diff --git a/Launcher/wwwroot/css/app.css b/Launcher/wwwroot/css/app.css index aad5a54..a257aed 100644 --- a/Launcher/wwwroot/css/app.css +++ b/Launcher/wwwroot/css/app.css @@ -100,5 +100,7 @@ p, h1, h4, a, td, tr, div, .prevent-select { .page { height: 100%; - display: flow-root; + display: grid; + grid-template-rows: 1fr; + grid-template-columns: 50px auto; } \ No newline at end of file From 5adf7b13d09fd17bc619927ed8667706daa2858b Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 29 Mar 2025 00:21:10 +0200 Subject: [PATCH 06/28] Launcher UI improvements --- Editor/EditorWindow.cs | 2 +- Editor/Program.cs | 4 +- Editor/Windows/ActionsMenuWindow.cs | 2 +- Editor/Windows/ImGuiWindowBase.cs | 2 +- Launcher/Enums/FilterMode.cs | 19 +++++ Launcher/Enums/SvgIcon.cs | 13 +++ Launcher/Services/EditorService.cs | 5 +- Launcher/TestData/TestData.cs | 5 +- .../Dialogs/CreateProjectDialog.razor.cs | 1 + .../UI/Components/Dialogs/Notification.razor | 21 +++-- Launcher/UI/Components/DiscardIcon.razor | 11 --- Launcher/UI/Components/DiscardIcon.razor.css | 6 -- .../UI/Components/DiscardIcon.razor.min.css | 1 - Launcher/UI/Components/DiscardIcon.razor.scss | 7 -- Launcher/UI/Components/Flyouts/Flyout.razor | 21 +++-- .../UI/Components/Flyouts/Flyout.razor.css | 38 +++++++++ .../Components/Flyouts/Flyout.razor.min.css | 1 + .../UI/Components/Flyouts/Flyout.razor.scss | 39 +++++++++ Launcher/UI/Components/Flyouts/FlyoutBase.cs | 6 ++ .../Components/Flyouts/SettingsFlyout.razor | 13 ++- Launcher/UI/Components/Grid.razor | 35 -------- Launcher/UI/Components/Grid/Grid.razor | 83 +++++++++++++++++++ Launcher/UI/Components/Grid/Grid.razor.css | 19 +++++ .../UI/Components/Grid/Grid.razor.min.css | 1 + Launcher/UI/Components/Grid/Grid.razor.scss | 19 +++++ Launcher/UI/Components/Grid/GridHeader.cs | 13 +++ Launcher/UI/Components/Grid/GridHeader.razor | 48 +++++++++++ .../UI/Components/Grid/GridHeader.razor.css | 15 ++++ .../Components/Grid/GridHeader.razor.min.css | 1 + .../UI/Components/Grid/GridHeader.razor.scss | 15 ++++ .../UI/Components/{ => Grid}/GridRow.razor | 0 Launcher/UI/Components/Icon.razor | 21 ++++- Launcher/UI/Components/Layout.razor | 9 +- .../Components/Navigation/SideBarItem.razor | 3 +- .../Navigation/SideBarItem.razor.css | 1 + .../Navigation/SideBarItem.razor.min.css | 1 + .../UI/Components/Navigation/Sidebar.razor | 4 +- Launcher/UI/Components/Searchbox.razor | 6 +- Launcher/UI/Enums/SvgIcon.cs | 10 --- Launcher/UI/Home.razor | 19 +++-- Launcher/UI/Home.razor.cs | 9 ++ Launcher/UI/Home.razor.css | 2 +- Launcher/UI/Home.razor.min.css | 2 +- Launcher/UI/Home.razor.scss | 2 +- Launcher/UI/NaturalStringComparer.cs | 67 +++++++++++++++ Launcher/UI/OrderedEnumerableExtensions.cs | 37 +++++++++ Launcher/UI/_Imports.razor | 2 +- Launcher/compilerconfig.json | 20 ++++- Launcher/wwwroot/css/app.css | 4 + .../Attributes/FilterAttribute.cs | 12 +++ SharpEngine.Shared/{ => Dto}/Project.cs | 8 +- .../{ => Extensions}/ProcessExtensions.cs | 4 +- .../Extensions/ReflectionExtensions.cs | 18 ++++ 53 files changed, 604 insertions(+), 123 deletions(-) create mode 100644 Launcher/Enums/FilterMode.cs create mode 100644 Launcher/Enums/SvgIcon.cs delete mode 100644 Launcher/UI/Components/DiscardIcon.razor delete mode 100644 Launcher/UI/Components/DiscardIcon.razor.css delete mode 100644 Launcher/UI/Components/DiscardIcon.razor.min.css delete mode 100644 Launcher/UI/Components/DiscardIcon.razor.scss create mode 100644 Launcher/UI/Components/Flyouts/Flyout.razor.css create mode 100644 Launcher/UI/Components/Flyouts/Flyout.razor.min.css delete mode 100644 Launcher/UI/Components/Grid.razor create mode 100644 Launcher/UI/Components/Grid/Grid.razor create mode 100644 Launcher/UI/Components/Grid/Grid.razor.css create mode 100644 Launcher/UI/Components/Grid/Grid.razor.min.css create mode 100644 Launcher/UI/Components/Grid/Grid.razor.scss create mode 100644 Launcher/UI/Components/Grid/GridHeader.cs create mode 100644 Launcher/UI/Components/Grid/GridHeader.razor create mode 100644 Launcher/UI/Components/Grid/GridHeader.razor.css create mode 100644 Launcher/UI/Components/Grid/GridHeader.razor.min.css create mode 100644 Launcher/UI/Components/Grid/GridHeader.razor.scss rename Launcher/UI/Components/{ => Grid}/GridRow.razor (100%) create mode 100644 Launcher/UI/Components/Navigation/SideBarItem.razor.css create mode 100644 Launcher/UI/Components/Navigation/SideBarItem.razor.min.css delete mode 100644 Launcher/UI/Enums/SvgIcon.cs create mode 100644 Launcher/UI/NaturalStringComparer.cs create mode 100644 Launcher/UI/OrderedEnumerableExtensions.cs create mode 100644 SharpEngine.Shared/Attributes/FilterAttribute.cs rename SharpEngine.Shared/{ => Dto}/Project.cs (85%) rename SharpEngine.Shared/{ => Extensions}/ProcessExtensions.cs (97%) create mode 100644 SharpEngine.Shared/Extensions/ReflectionExtensions.cs diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index beb1ef4..807a5b4 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -1,5 +1,4 @@ using ImGuiNET; -using Launcher.UI; using SharpEngine.Core; using SharpEngine.Core.Entities.Views.Settings; @@ -14,6 +13,7 @@ using System.Numerics; using System.Reflection; using SharpEngine.Core.Windowing; +using SharpEngine.Shared.Dto; namespace SharpEngine.Editor; diff --git a/Editor/Program.cs b/Editor/Program.cs index 1dd9670..e899d16 100644 --- a/Editor/Program.cs +++ b/Editor/Program.cs @@ -1,9 +1,9 @@ -using Launcher.UI; -using SharpEngine.Core; +using SharpEngine.Core; using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Scenes; using SharpEngine.Editor.Windows; using SharpEngine.Shared; +using SharpEngine.Shared.Dto; using System.Reflection; namespace SharpEngine.Editor; diff --git a/Editor/Windows/ActionsMenuWindow.cs b/Editor/Windows/ActionsMenuWindow.cs index af5e223..f6471ab 100644 --- a/Editor/Windows/ActionsMenuWindow.cs +++ b/Editor/Windows/ActionsMenuWindow.cs @@ -3,7 +3,7 @@ using SharpEngine.Core; using SharpEngine.Core.Audio; using SharpEngine.Core.Scenes; -using SharpEngine.Shared; +using SharpEngine.Shared.Extensions; namespace SharpEngine.Editor.Windows { diff --git a/Editor/Windows/ImGuiWindowBase.cs b/Editor/Windows/ImGuiWindowBase.cs index 841e030..8e62aa9 100644 --- a/Editor/Windows/ImGuiWindowBase.cs +++ b/Editor/Windows/ImGuiWindowBase.cs @@ -1,8 +1,8 @@ using ImGuiNET; -using Launcher.UI; using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Scenes; +using SharpEngine.Shared.Dto; namespace SharpEngine.Editor.Windows { diff --git a/Launcher/Enums/FilterMode.cs b/Launcher/Enums/FilterMode.cs new file mode 100644 index 0000000..06c4bb2 --- /dev/null +++ b/Launcher/Enums/FilterMode.cs @@ -0,0 +1,19 @@ +namespace Launcher.Enums; + +public enum FilterMode +{ + /// + /// No filter applied. + /// + None, + + /// + /// Filter in ascending order. + /// + Ascending, + + /// + /// Filter in descending order. + /// + Descending +} diff --git a/Launcher/Enums/SvgIcon.cs b/Launcher/Enums/SvgIcon.cs new file mode 100644 index 0000000..d2ce8f4 --- /dev/null +++ b/Launcher/Enums/SvgIcon.cs @@ -0,0 +1,13 @@ +namespace Launcher.Enums; + +public enum SvgIcon +{ + None, + Home, + Cog, + Discard, + Lens, + Close, + ChevronDown, + ChevronUp, +} diff --git a/Launcher/Services/EditorService.cs b/Launcher/Services/EditorService.cs index a503ff3..0f8f10a 100644 --- a/Launcher/Services/EditorService.cs +++ b/Launcher/Services/EditorService.cs @@ -1,5 +1,6 @@ -using Launcher.UI; -using SharpEngine.Shared; +using SharpEngine.Shared; +using SharpEngine.Shared.Dto; +using SharpEngine.Shared.Extensions; using System.Diagnostics; using System.Runtime.InteropServices; diff --git a/Launcher/TestData/TestData.cs b/Launcher/TestData/TestData.cs index 44036eb..18344e6 100644 --- a/Launcher/TestData/TestData.cs +++ b/Launcher/TestData/TestData.cs @@ -1,4 +1,4 @@ -using Launcher.UI; +using SharpEngine.Shared.Dto; namespace Launcher.TestData; public static class TestData @@ -7,11 +7,12 @@ public static class TestData public const bool SimulateLoading = false; - public static IEnumerable Projects = Enumerable.Range(0, 3).Select(i => + public static IEnumerable Projects = Enumerable.Range(0, 25).Select(i => { return new Project() { Name = $"Project {i}", + Path = @"C:\test", }; }); } diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs index ad2eaff..7d1b324 100644 --- a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs +++ b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Components; using Launcher.Services; using CommunityToolkit.Maui.Storage; +using SharpEngine.Shared.Dto; namespace Launcher.UI.Components.Dialogs { diff --git a/Launcher/UI/Components/Dialogs/Notification.razor b/Launcher/UI/Components/Dialogs/Notification.razor index 8a40e57..78b9225 100644 --- a/Launcher/UI/Components/Dialogs/Notification.razor +++ b/Launcher/UI/Components/Dialogs/Notification.razor @@ -1,17 +1,22 @@ -@using Launcher.Services +@using Launcher.Enums +@using Launcher.Services @inherits DialogBase @inject INotificationService NotificationService
- @foreach (var notification in NotificationService.Notifications) - { - - } + + @foreach (var notification in NotificationService.Notifications) + { + + } +
diff --git a/Launcher/UI/Components/DiscardIcon.razor b/Launcher/UI/Components/DiscardIcon.razor deleted file mode 100644 index 62597d0..0000000 --- a/Launcher/UI/Components/DiscardIcon.razor +++ /dev/null @@ -1,11 +0,0 @@ - - -@code { - - /// Gets or sets the event executed when the discard icon is clicked. - [Parameter] - public EventCallback DiscardClicked { get; set; } - - private async Task OnDiscardClicked() - => await DiscardClicked.InvokeAsync(); -} diff --git a/Launcher/UI/Components/DiscardIcon.razor.css b/Launcher/UI/Components/DiscardIcon.razor.css deleted file mode 100644 index 301f29c..0000000 --- a/Launcher/UI/Components/DiscardIcon.razor.css +++ /dev/null @@ -1,6 +0,0 @@ -span { - cursor: pointer; -} -span:hover { - filter: brightness(0.8); -} diff --git a/Launcher/UI/Components/DiscardIcon.razor.min.css b/Launcher/UI/Components/DiscardIcon.razor.min.css deleted file mode 100644 index 6048a35..0000000 --- a/Launcher/UI/Components/DiscardIcon.razor.min.css +++ /dev/null @@ -1 +0,0 @@ -span{cursor:pointer;}span:hover{filter:brightness(.8);} \ No newline at end of file diff --git a/Launcher/UI/Components/DiscardIcon.razor.scss b/Launcher/UI/Components/DiscardIcon.razor.scss deleted file mode 100644 index f5cb62a..0000000 --- a/Launcher/UI/Components/DiscardIcon.razor.scss +++ /dev/null @@ -1,7 +0,0 @@ -span { - cursor: pointer; - - &:hover { - filter: brightness(0.8); - } -} diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor b/Launcher/UI/Components/Flyouts/Flyout.razor index c7abf26..efb293e 100644 --- a/Launcher/UI/Components/Flyouts/Flyout.razor +++ b/Launcher/UI/Components/Flyouts/Flyout.razor @@ -1,5 +1,4 @@ -@inherits FlyoutBase - +@using Launcher.Enums
@@ -12,9 +11,7 @@

@Title

}
-
- -
+
@Content @@ -37,5 +34,17 @@ [Parameter] public string Title { get; set; } = default!; - private string _isVisibleStyle => IsVisible ? "visible" : "hidden"; + [Parameter] + public bool IsVisible { get; set; } + + [Parameter] + public EventCallback IsVisibleChanged { get; set; } + + private string _isVisibleStyle => IsVisible ? "grid" : "none"; + + private async Task Close() + { + IsVisible = false; + await IsVisibleChanged.InvokeAsync(IsVisible); + } } diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor.css b/Launcher/UI/Components/Flyouts/Flyout.razor.css new file mode 100644 index 0000000..82882fa --- /dev/null +++ b/Launcher/UI/Components/Flyouts/Flyout.razor.css @@ -0,0 +1,38 @@ +.flyout { + display: none; + position: absolute; + top: 0; + width: 50%; + height: 100vh; + z-index: 9999; + background: white; + border-right: 1px solid black; + left: 50px; + grid-template-rows: 70px auto 70px; +} + +.flyout-header { + display: flex; + justify-content: space-between; +} +.flyout-header button { + border: none; + background: none; +} + +.flyout-content { + overflow-y: scroll; + padding-left: 1em; +} + +.flyout-footer { + border-top: 1px solid black; + padding: 5px; + display: flex; + justify-content: end; + gap: 8px; +} + +.flyout-footer, .flyout-header, .flyout-content { + padding: 1em; +} diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor.min.css b/Launcher/UI/Components/Flyouts/Flyout.razor.min.css new file mode 100644 index 0000000..f1bb45f --- /dev/null +++ b/Launcher/UI/Components/Flyouts/Flyout.razor.min.css @@ -0,0 +1 @@ +.flyout{display:none;position:absolute;top:0;width:50%;height:100vh;z-index:9999;background:#fff;border-right:1px solid #000;left:50px;grid-template-rows:70px auto 70px;}.flyout-header{display:flex;justify-content:space-between;}.flyout-header button{border:0;background:none;}.flyout-content{overflow-y:scroll;padding-left:1em;}.flyout-footer{border-top:1px solid #000;padding:5px;display:flex;justify-content:end;gap:8px;}.flyout-footer,.flyout-header,.flyout-content{padding:1em;} \ No newline at end of file diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor.scss b/Launcher/UI/Components/Flyouts/Flyout.razor.scss index e69de29..decc8e0 100644 --- a/Launcher/UI/Components/Flyouts/Flyout.razor.scss +++ b/Launcher/UI/Components/Flyouts/Flyout.razor.scss @@ -0,0 +1,39 @@ +.flyout { + display: none; + position: absolute; + top: 0; + width: 50%; + height: 100vh; + z-index: 9999; + background: white; + border-right: 1px solid black; + left: 50px; + grid-template-rows: 70px auto 70px; +} + +.flyout-header { + display: flex; + justify-content: space-between; + + button { + border: none; + background: none; + } +} + +.flyout-content { + overflow-y: scroll; + padding-left: 1em; +} + +.flyout-footer { + border-top: 1px solid black; + padding: 5px; + display: flex; + justify-content: end; + gap: 8px; +} + +.flyout-footer, .flyout-header, .flyout-content { + padding: 1em; +} \ No newline at end of file diff --git a/Launcher/UI/Components/Flyouts/FlyoutBase.cs b/Launcher/UI/Components/Flyouts/FlyoutBase.cs index 18e5a85..ed720a2 100644 --- a/Launcher/UI/Components/Flyouts/FlyoutBase.cs +++ b/Launcher/UI/Components/Flyouts/FlyoutBase.cs @@ -18,4 +18,10 @@ public virtual async Task OnIsVisibleChangedAsync(bool visibility) IsVisible = visibility; await IsVisibleChanged.InvokeAsync(visibility); } + + public virtual async Task Close() + { + await OnClose.InvokeAsync(); + await OnIsVisibleChangedAsync(false); + } } diff --git a/Launcher/UI/Components/Flyouts/SettingsFlyout.razor b/Launcher/UI/Components/Flyouts/SettingsFlyout.razor index 2326ad2..e8c7930 100644 --- a/Launcher/UI/Components/Flyouts/SettingsFlyout.razor +++ b/Launcher/UI/Components/Flyouts/SettingsFlyout.razor @@ -1,12 +1,19 @@ @inherits FlyoutBase - +
- - + +
@code { + [Inject] + private INotificationService _notificationService { get; init; } = default!; + private async Task SaveSettings() + { + _notificationService.Show("Settings saved."); + await Close(); + } } diff --git a/Launcher/UI/Components/Grid.razor b/Launcher/UI/Components/Grid.razor deleted file mode 100644 index a65a5ef..0000000 --- a/Launcher/UI/Components/Grid.razor +++ /dev/null @@ -1,35 +0,0 @@ -@typeparam TItem - - - - - - - - - - - @foreach (var item in Items) - { - - - @Row(item) - - - } - -
NameLast ModifiedActions
- -@code { - /// - /// Gets or sets the items to be rendered onto the grid. - /// - [Parameter] - public List Items { get; set; } - - /// - /// Gets or sets a template by which each row should be rendered. - /// - [Parameter] - public RenderFragment Row { get; set; } -} \ No newline at end of file diff --git a/Launcher/UI/Components/Grid/Grid.razor b/Launcher/UI/Components/Grid/Grid.razor new file mode 100644 index 0000000..5050b38 --- /dev/null +++ b/Launcher/UI/Components/Grid/Grid.razor @@ -0,0 +1,83 @@ +@using System.ComponentModel +@using SharpEngine.Shared.Attributes + +@typeparam TItem + +
+ + + + @foreach (var header in _headers) + { + + } + + +
+
+ + +
+ + + @foreach (var item in Items) + { + + + @GridRowTemplate(item) + + + } + +
+
+ +@code { + /// + /// Gets or sets the items to be rendered onto the grid. + /// + [Parameter] + public List Items { get; set; } = []; + + /// + /// Gets or sets a template by which each row should be rendered. + /// + [Parameter] + public RenderFragment GridRowTemplate { get; set; } + + [Parameter] + public EventCallback> FilterChanged { get; set; } + + private List _headers { get; set; } = []; + private Dictionary _filterModes { get; set; } = new(); + + protected override Task OnInitializedAsync() + { + GetHeaders(); + return base.OnInitializedAsync(); + } + + private void GetHeaders() + { + var props = typeof(TItem).GetProperties(); + var headers = props.Select(prop => + { + var displayNameAttribute = (DisplayNameAttribute?)prop.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault(); + var filterAttribute = (FilterAttribute?)prop.GetCustomAttributes(typeof(FilterAttribute), false).FirstOrDefault(); + + return new GridHeaderInfo(displayNameAttribute?.DisplayName ?? prop.Name, filterAttribute?.IsFilterable ?? true); + }); + + _headers = headers.ToList(); + } + + private async Task OnFilterChanged(FilterMode filterMode, string header) + { + if (_filterModes.TryGetValue(header, out var mode)) + _filterModes[header] = filterMode; + else + _filterModes.Add(header, filterMode); + + await FilterChanged.InvokeAsync(_filterModes); + } +} \ No newline at end of file diff --git a/Launcher/UI/Components/Grid/Grid.razor.css b/Launcher/UI/Components/Grid/Grid.razor.css new file mode 100644 index 0000000..96e0633 --- /dev/null +++ b/Launcher/UI/Components/Grid/Grid.razor.css @@ -0,0 +1,19 @@ +.auto-width-table { + table-layout: auto; + width: 100%; +} + +.auto-width-table th, .auto-width-table td { + white-space: nowrap; + padding: 8px; + text-align: left; +} + +.table-header { + border-bottom: 1px solid black; +} + +.table-content { + overflow-y: scroll; + height: 250px; +} diff --git a/Launcher/UI/Components/Grid/Grid.razor.min.css b/Launcher/UI/Components/Grid/Grid.razor.min.css new file mode 100644 index 0000000..74f740a --- /dev/null +++ b/Launcher/UI/Components/Grid/Grid.razor.min.css @@ -0,0 +1 @@ +.auto-width-table{table-layout:auto;width:100%;}.auto-width-table th,.auto-width-table td{white-space:nowrap;padding:8px;text-align:left;}.table-header{border-bottom:1px solid #000;}.table-content{overflow-y:scroll;height:250px;} \ No newline at end of file diff --git a/Launcher/UI/Components/Grid/Grid.razor.scss b/Launcher/UI/Components/Grid/Grid.razor.scss new file mode 100644 index 0000000..daf24c1 --- /dev/null +++ b/Launcher/UI/Components/Grid/Grid.razor.scss @@ -0,0 +1,19 @@ +.auto-width-table { + table-layout: auto; + width: 100%; +} + +.auto-width-table th, .auto-width-table td { + white-space: nowrap; + padding: 8px; + text-align: left; +} + +.table-header { + border-bottom: 1px solid black; +} + +.table-content { + overflow-y: scroll; + height: 250px; +} \ No newline at end of file diff --git a/Launcher/UI/Components/Grid/GridHeader.cs b/Launcher/UI/Components/Grid/GridHeader.cs new file mode 100644 index 0000000..4c8a9de --- /dev/null +++ b/Launcher/UI/Components/Grid/GridHeader.cs @@ -0,0 +1,13 @@ +namespace Launcher.UI.Components.Grid; + +public class GridHeaderInfo +{ + public string Title { get; set; } + public bool AllowFiltering { get; set; } + + public GridHeaderInfo(string title, bool allowFiltering) + { + Title = title; + AllowFiltering = allowFiltering; + } +} diff --git a/Launcher/UI/Components/Grid/GridHeader.razor b/Launcher/UI/Components/Grid/GridHeader.razor new file mode 100644 index 0000000..00c7563 --- /dev/null +++ b/Launcher/UI/Components/Grid/GridHeader.razor @@ -0,0 +1,48 @@ +@using Launcher.Enums + + + + + +@code { + /// + /// Gets or sets the contents of the grid row header. + /// + [Parameter] + public string Title { get; set; } = default!; + + [Parameter] + public bool AllowFiltering { get; set; } + + [Parameter] + public EventCallback FilterChanged { get; set; } + + [Parameter] + public FilterMode FilterMode { get; set; } = FilterMode.None; + + private string _filterClass => AllowFiltering ? "filter-btn" : "filter-disabled"; + + private SvgIcon _icon => this.FilterMode switch + { + FilterMode.Ascending => SvgIcon.ChevronUp, + FilterMode.Descending => SvgIcon.ChevronDown, + _ => SvgIcon.None + }; + + private async Task OnFilterClicked() + { + FilterMode = FilterMode switch + { + FilterMode.None => FilterMode.Ascending, + FilterMode.Ascending => FilterMode.Descending, + FilterMode.Descending => FilterMode.None, + _ => FilterMode.None + }; + + if (AllowFiltering) + await FilterChanged.InvokeAsync(FilterMode); + } +} diff --git a/Launcher/UI/Components/Grid/GridHeader.razor.css b/Launcher/UI/Components/Grid/GridHeader.razor.css new file mode 100644 index 0000000..642257e --- /dev/null +++ b/Launcher/UI/Components/Grid/GridHeader.razor.css @@ -0,0 +1,15 @@ +.btn { + background: transparent; + border: none; + padding: 0; +} + +.filter-btn { + display: flex; + align-items: center; +} + +.filter-disabled { + cursor: none; + pointer-events: none; +} diff --git a/Launcher/UI/Components/Grid/GridHeader.razor.min.css b/Launcher/UI/Components/Grid/GridHeader.razor.min.css new file mode 100644 index 0000000..b5f165e --- /dev/null +++ b/Launcher/UI/Components/Grid/GridHeader.razor.min.css @@ -0,0 +1 @@ +.btn{background:transparent;border:0;padding:0;}.filter-btn{display:flex;align-items:center;}.filter-disabled{cursor:none;pointer-events:none;} \ No newline at end of file diff --git a/Launcher/UI/Components/Grid/GridHeader.razor.scss b/Launcher/UI/Components/Grid/GridHeader.razor.scss new file mode 100644 index 0000000..e0f25a0 --- /dev/null +++ b/Launcher/UI/Components/Grid/GridHeader.razor.scss @@ -0,0 +1,15 @@ +.btn { + background: transparent; + border: none; + padding: 0; +} + +.filter-btn { + display: flex; + align-items: center; +} + +.filter-disabled { + cursor: none; + pointer-events: none; +} \ No newline at end of file diff --git a/Launcher/UI/Components/GridRow.razor b/Launcher/UI/Components/Grid/GridRow.razor similarity index 100% rename from Launcher/UI/Components/GridRow.razor rename to Launcher/UI/Components/Grid/GridRow.razor diff --git a/Launcher/UI/Components/Icon.razor b/Launcher/UI/Components/Icon.razor index 8d0dcce..ee88315 100644 --- a/Launcher/UI/Components/Icon.razor +++ b/Launcher/UI/Components/Icon.razor @@ -1,4 +1,6 @@ -@switch (IconType) +@using Launcher.Enums + +@switch (IconType) { case SvgIcon.Home: @@ -38,6 +40,23 @@ break; + case SvgIcon.ChevronDown: + + + + + + + break; + case SvgIcon.ChevronUp: + + + + + + + + break; default: break; } diff --git a/Launcher/UI/Components/Layout.razor b/Launcher/UI/Components/Layout.razor index 21bba39..5979b20 100644 --- a/Launcher/UI/Components/Layout.razor +++ b/Launcher/UI/Components/Layout.razor @@ -1,14 +1,19 @@ -@using Launcher.UI.Components.Navigation +@using Launcher.Enums +@using Launcher.UI.Components.Dialogs @using Launcher.UI.Components.Flyouts +@using Launcher.UI.Components.Navigation @inherits LayoutComponentBase
+ + -
+ +
@Body
diff --git a/Launcher/UI/Components/Navigation/SideBarItem.razor b/Launcher/UI/Components/Navigation/SideBarItem.razor index a34d252..7d324f4 100644 --- a/Launcher/UI/Components/Navigation/SideBarItem.razor +++ b/Launcher/UI/Components/Navigation/SideBarItem.razor @@ -1,4 +1,5 @@ - +@using Launcher.Enums + @code { [Parameter] diff --git a/Launcher/UI/Components/Navigation/SideBarItem.razor.css b/Launcher/UI/Components/Navigation/SideBarItem.razor.css new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/Launcher/UI/Components/Navigation/SideBarItem.razor.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css b/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Launcher/UI/Components/Navigation/Sidebar.razor b/Launcher/UI/Components/Navigation/Sidebar.razor index 19ae0d8..58495a2 100644 --- a/Launcher/UI/Components/Navigation/Sidebar.razor +++ b/Launcher/UI/Components/Navigation/Sidebar.razor @@ -1,5 +1,5 @@ - - -
- - - @context.Name + + + + +

@context.Name

+

@context.Path

+ @context.LastModified -
+
diff --git a/Launcher/UI/Home.razor.cs b/Launcher/UI/Home.razor.cs index 99af3c2..5e33d0b 100644 --- a/Launcher/UI/Home.razor.cs +++ b/Launcher/UI/Home.razor.cs @@ -1,8 +1,11 @@ using Launcher.Services; using Microsoft.AspNetCore.Components; +using SharpEngine.Shared.Dto; using System.Diagnostics; using System.Text.Json; +using FilterMode = Launcher.Enums.FilterMode; + namespace Launcher.UI { /// Represents the home page of the launcher. @@ -158,5 +161,11 @@ private void RemoveFromList(Project project) Projects.Remove(foundProject); } + + private void OnFiltersChanged(Dictionary filters) + { + _filteredProjects = [.. Projects.FilterBy(filters)]; + StateHasChanged(); + } } } diff --git a/Launcher/UI/Home.razor.css b/Launcher/UI/Home.razor.css index ffb5a06..dfc3ab4 100644 --- a/Launcher/UI/Home.razor.css +++ b/Launcher/UI/Home.razor.css @@ -34,7 +34,7 @@ display: flex; justify-content: space-between; align-items: center; - margin: 25px; + margin: 25px 0; } .alert-header { diff --git a/Launcher/UI/Home.razor.min.css b/Launcher/UI/Home.razor.min.css index 384ee6f..cb60f75 100644 --- a/Launcher/UI/Home.razor.min.css +++ b/Launcher/UI/Home.razor.min.css @@ -1 +1 @@ -.toast{right:0;transition:transform .5s ease-in-out;}.dialog{left:0;right:0;margin-left:auto;margin-right:auto;width:auto;max-width:90%;}.toast,.dialog{position:absolute;top:50px;}.notification-visible{transform:translateX(0);}.notification-hidden{transform:translateX(100%);}.create-project-dialog-hidden,.confirm-delete-hidden{display:none;}.header{display:flex;justify-content:space-between;align-items:center;margin:25px;}.alert-header{display:flex;justify-content:space-between;}.alert-header h4{padding:0;}.flex-end{justify-content:end;display:flex;gap:8px;}.custom-file-upload{display:inline-block;padding:8px 12px;background-color:#007bff;color:#fff;border-radius:4px;cursor:pointer;}.hidden-input{position:absolute;width:0;height:0;opacity:0;} \ No newline at end of file +.toast{right:0;transition:transform .5s ease-in-out;}.dialog{left:0;right:0;margin-left:auto;margin-right:auto;width:auto;max-width:90%;}.toast,.dialog{position:absolute;top:50px;}.notification-visible{transform:translateX(0);}.notification-hidden{transform:translateX(100%);}.create-project-dialog-hidden,.confirm-delete-hidden{display:none;}.header{display:flex;justify-content:space-between;align-items:center;margin:25px 0;}.alert-header{display:flex;justify-content:space-between;}.alert-header h4{padding:0;}.flex-end{justify-content:end;display:flex;gap:8px;}.custom-file-upload{display:inline-block;padding:8px 12px;background-color:#007bff;color:#fff;border-radius:4px;cursor:pointer;}.hidden-input{position:absolute;width:0;height:0;opacity:0;} \ No newline at end of file diff --git a/Launcher/UI/Home.razor.scss b/Launcher/UI/Home.razor.scss index be215ee..bbdf065 100644 --- a/Launcher/UI/Home.razor.scss +++ b/Launcher/UI/Home.razor.scss @@ -34,7 +34,7 @@ display: flex; justify-content: space-between; align-items: center; - margin: 25px; + margin: 25px 0 ; } .alert-header { diff --git a/Launcher/UI/NaturalStringComparer.cs b/Launcher/UI/NaturalStringComparer.cs new file mode 100644 index 0000000..248c3f5 --- /dev/null +++ b/Launcher/UI/NaturalStringComparer.cs @@ -0,0 +1,67 @@ +using System.Globalization; + +namespace Launcher.UI; + +public class NaturalStringComparer : IComparer +{ + /// + public int Compare(string? s1, string? s2) + { + if (s1 == s2) + return 0; + if (s1 == null) + return -1; + if (s2 == null) + return 1; + + int i = 0, j = 0; + int len1 = s1.Length, len2 = s2.Length; + + while (i < len1 && j < len2) + { + int result = CompareCharacters(s1, s2, ref i, ref j); + if (result != 0) + return result; + } + + // If one string has remaining characters, it is greater + return (len1 - i).CompareTo(len2 - j); + } + + private int CompareCharacters(string s1, string s2, ref int i, ref int j) + { + char c1 = s1[i]; + char c2 = s2[j]; + + // If both characters are digits, compare as numbers + if (char.IsDigit(c1) && char.IsDigit(c2)) + { + int start1 = i; + while (i < s1.Length && char.IsDigit(s1[i])) i++; + int start2 = j; + while (j < s2.Length && char.IsDigit(s2[j])) j++; + + string numStr1 = s1.Substring(start1, i - start1); + string numStr2 = s2.Substring(start2, j - start2); + + // Parse numbers ignoring leading zeros + int num1 = int.Parse(numStr1, CultureInfo.InvariantCulture); + int num2 = int.Parse(numStr2, CultureInfo.InvariantCulture); + + if (num1 != num2) + return num1.CompareTo(num2); + + return 0; + } + + // Compare characters case-insensitively + int result = char.ToUpperInvariant(c1).CompareTo(char.ToUpperInvariant(c2)); + if (result != 0) + return result; + + i++; + j++; + + return 0; + } +} diff --git a/Launcher/UI/OrderedEnumerableExtensions.cs b/Launcher/UI/OrderedEnumerableExtensions.cs new file mode 100644 index 0000000..ba8ac20 --- /dev/null +++ b/Launcher/UI/OrderedEnumerableExtensions.cs @@ -0,0 +1,37 @@ +using Launcher.UI; +using SharpEngine.Shared.Dto; +using SharpEngine.Shared.Extensions; + +using FilterMode = Launcher.Enums.FilterMode; + +public static class OrderedEnumerableExtensions +{ + /// + /// Filters and orders a collection of projects based on specified criteria. + /// + /// The collection of projects to be filtered and ordered. + /// The names of properties and their corresponding filter modes. + /// A filtered collection of projects based on the applied filters. + public static IEnumerable FilterBy(this IEnumerable toFilter, Dictionary filters) + { + var stringComparer = new NaturalStringComparer(); + IOrderedEnumerable? ordered = null; + + foreach (var filter in filters) + { + string keySelector(Project p) => p.GetPropertyValue(filter.Key)?.ToString() ?? string.Empty; + + if (ordered == null) + ordered = filter.Value == FilterMode.Ascending + ? toFilter.OrderBy(keySelector, stringComparer) + : toFilter.OrderByDescending(keySelector, stringComparer); + + else + ordered = filter.Value == FilterMode.Ascending + ? ordered.ThenBy(keySelector, stringComparer) + : ordered.ThenByDescending(keySelector, stringComparer); + } + + return ordered ?? toFilter; + } +} diff --git a/Launcher/UI/_Imports.razor b/Launcher/UI/_Imports.razor index 5047218..a51eaed 100644 --- a/Launcher/UI/_Imports.razor +++ b/Launcher/UI/_Imports.razor @@ -10,4 +10,4 @@ @using Launcher @using Launcher.UI @using Launcher.Services -@using Launcher.UI.Enums +@using Launcher.Enums diff --git a/Launcher/compilerconfig.json b/Launcher/compilerconfig.json index 9fe060d..99824cb 100644 --- a/Launcher/compilerconfig.json +++ b/Launcher/compilerconfig.json @@ -7,10 +7,6 @@ "outputFile": "UI/Home.razor.css", "inputFile": "UI/Home.razor.scss" }, - { - "outputFile": "UI/Components/DiscardIcon.razor.css", - "inputFile": "UI/Components/DiscardIcon.razor.scss" - }, { "outputFile": "wwwroot/css/additional.razor.css", "inputFile": "wwwroot/css/additional.razor.scss" @@ -22,5 +18,21 @@ { "outputFile": "UI/Components/Dialogs/ConfirmDeleteDialog.razor.css", "inputFile": "UI/Components/Dialogs/ConfirmDeleteDialog.razor.scss" + }, + { + "outputFile": "UI/Components/Navigation/SideBarItem.razor.css", + "inputFile": "UI/Components/Navigation/SideBarItem.razor.scss" + }, + { + "outputFile": "UI/Components/Flyouts/Flyout.razor.css", + "inputFile": "UI/Components/Flyouts/Flyout.razor.scss" + }, + { + "outputFile": "UI/Components/Grid.razor.css", + "inputFile": "UI/Components/Grid.razor.scss" + }, + { + "outputFile": "UI/Components/Grid/GridHeader.razor.css", + "inputFile": "UI/Components/Grid/GridHeader.razor.scss" } ] \ No newline at end of file diff --git a/Launcher/wwwroot/css/app.css b/Launcher/wwwroot/css/app.css index a257aed..2196041 100644 --- a/Launcher/wwwroot/css/app.css +++ b/Launcher/wwwroot/css/app.css @@ -103,4 +103,8 @@ p, h1, h4, a, td, tr, div, .prevent-select { display: grid; grid-template-rows: 1fr; grid-template-columns: 50px auto; +} + +p { + margin: 0; } \ No newline at end of file diff --git a/SharpEngine.Shared/Attributes/FilterAttribute.cs b/SharpEngine.Shared/Attributes/FilterAttribute.cs new file mode 100644 index 0000000..4cfd400 --- /dev/null +++ b/SharpEngine.Shared/Attributes/FilterAttribute.cs @@ -0,0 +1,12 @@ +namespace SharpEngine.Shared.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +public class FilterAttribute : Attribute +{ + public bool IsFilterable { get; set; } + + public FilterAttribute(bool isFilterable = true) + { + IsFilterable = isFilterable; + } +} diff --git a/SharpEngine.Shared/Project.cs b/SharpEngine.Shared/Dto/Project.cs similarity index 85% rename from SharpEngine.Shared/Project.cs rename to SharpEngine.Shared/Dto/Project.cs index 5cac973..cf6b427 100644 --- a/SharpEngine.Shared/Project.cs +++ b/SharpEngine.Shared/Dto/Project.cs @@ -1,8 +1,10 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Text.Json; using System.Text.Json.Serialization; +using SharpEngine.Shared.Attributes; -namespace Launcher.UI; +namespace SharpEngine.Shared.Dto; /// /// Represents a SharpEngine project. @@ -30,6 +32,8 @@ public class Project /// Gets or sets when the project was last modified. /// [JsonIgnore] + [DisplayName("Last Modified")] + [Filter(false)] public DateTime LastModified { get; set; } = DateTime.Now; /// diff --git a/SharpEngine.Shared/ProcessExtensions.cs b/SharpEngine.Shared/Extensions/ProcessExtensions.cs similarity index 97% rename from SharpEngine.Shared/ProcessExtensions.cs rename to SharpEngine.Shared/Extensions/ProcessExtensions.cs index 00cb0bc..f8d2a36 100644 --- a/SharpEngine.Shared/ProcessExtensions.cs +++ b/SharpEngine.Shared/Extensions/ProcessExtensions.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; -namespace SharpEngine.Shared; +namespace SharpEngine.Shared.Extensions; /// /// Contains extensions for handling processes. @@ -68,7 +68,7 @@ void SendMessage(string? msg) } }); - Thread thread = new Thread(start); + var thread = new Thread(start); thread.Start(); SendMessage("Editor launched."); diff --git a/SharpEngine.Shared/Extensions/ReflectionExtensions.cs b/SharpEngine.Shared/Extensions/ReflectionExtensions.cs new file mode 100644 index 0000000..081cdc9 --- /dev/null +++ b/SharpEngine.Shared/Extensions/ReflectionExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Shared.Extensions; +public static class ReflectionExtensions +{ + public static object? GetPropertyValue(this T obj, string propertyName) + { + var property = typeof(T).GetProperty(propertyName); + if (property == null) + return null; + + return property.GetValue(obj); + } +} From c66594d962dde0fae20f979706c002b42c6b6465 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 29 Mar 2025 01:49:32 +0200 Subject: [PATCH 07/28] Cleanup --- Core/Entities/GameObject.cs | 8 ++--- Core/Entities/Lights/DirectionalLight.cs | 1 + Core/Entities/Lights/PointLight.cs | 1 + Core/Entities/Lights/SpotLight.cs | 1 + Core/Entities/Properties/Meshes/Mesh.cs | 2 +- Core/Entities/UIElement.cs | 5 ++- Core/Entities/Views/View.cs | 7 ++-- Core/Extensions/CollectionExtensions.cs | 2 +- Core/Math.cs | 2 +- Core/Primitives/PrimitiveFactory.cs | 1 + Core/Scenes/SceneNode.cs | 2 +- Core/Shaders/LampShader.cs | 3 ++ Core/Shaders/LightingShader.cs | 4 ++- Core/Shaders/ShaderService.cs | 4 +-- Core/Shaders/UIShader.cs | 3 ++ Core/Windowing/SilkWindow.cs | 4 +-- Core/Windowing/Window.cs | 11 ++++--- Editor/Windows/ActionsMenuWindow.cs | 18 ++++++++--- Editor/Windows/AssetsWindow.cs | 1 - Editor/Windows/ContextMenuWindow.cs | 1 - Examples/MinecraftClone/Program.cs | 2 +- Examples/Minimal/Program.cs | 2 +- Launcher/Attributes/FileAttribute.cs | 2 +- Launcher/Enums/FilterMode.cs | 19 ----------- Launcher/Enums/SvgIcon.cs | 18 +++++++++++ Launcher/TestData/TestData.cs | 16 ++++++---- Launcher/UI/Components/ContextMenu.razor | 21 +++--------- Launcher/UI/Components/ContextMenu.razor.css | 9 ++++++ .../UI/Components/ContextMenu.razor.min.css | 1 + Launcher/UI/Components/ContextMenu.razor.scss | 9 ++++++ .../Dialogs/ConfirmDeleteDialog.razor | 2 +- .../Dialogs/CreateProjectDialog.razor | 4 +-- .../Dialogs/CreateProjectDialog.razor.css | 9 ++++++ .../Dialogs/CreateProjectDialog.razor.min.css | 1 + .../Dialogs/CreateProjectDialog.razor.scss | 9 ++++++ Launcher/UI/Components/Flyouts/Flyout.razor | 6 ++++ Launcher/UI/Components/Flyouts/FlyoutBase.cs | 19 +++++++++++ Launcher/UI/Components/Grid/Grid.razor | 1 + Launcher/UI/Components/Grid/GridHeader.cs | 11 +++++++ Launcher/UI/Components/Grid/GridHeader.razor | 11 +++---- .../UI/Components/Icons/DiscardIcon.razor | 10 ++++++ Launcher/UI/Components/{ => Icons}/Icon.razor | 3 ++ Launcher/UI/Components/Layout.razor | 9 ++---- Launcher/UI/Components/Layout.razor.css | 6 ++++ Launcher/UI/Components/Layout.razor.min.css | 1 + Launcher/UI/Components/Layout.razor.scss | 6 ++++ .../Components/Navigation/SideBarItem.razor | 5 +-- .../Navigation/SideBarItem.razor.css | 6 +++- .../Navigation/SideBarItem.razor.min.css | 2 +- .../Navigation/SideBarItem.razor.scss | 6 ++-- .../UI/Components/Navigation/Sidebar.razor | 5 +-- .../Components/Navigation/Sidebar.razor.css | 6 ++++ .../Navigation/Sidebar.razor.min.css | 1 + .../Components/Navigation/Sidebar.razor.scss | 7 ++++ .../Notification.razor | 14 +++----- .../Notification/Notification.razor.css | 18 +++++++++++ .../Notification/Notification.razor.min.css | 1 + .../Notification/Notification.razor.scss | 18 +++++++++++ .../Notification/NotificationType.cs | 32 +++++++++++++++++++ .../NotificationTypeExtensions.cs} | 32 +------------------ Launcher/UI/Components/Searchbox.razor | 11 ++++--- Launcher/UI/Components/Searchbox.razor.css | 14 ++++++++ .../UI/Components/Searchbox.razor.min.css | 1 + Launcher/UI/Components/Searchbox.razor.scss | 14 ++++++++ Launcher/UI/Home.razor | 14 ++------ Launcher/UI/Home.razor.cs | 5 ++- Launcher/UI/_Imports.razor | 14 +++++++- Launcher/compilerconfig.json | 24 ++++++++++++++ .../Attributes/FilterAttribute.cs | 8 +++++ SharpEngine.Shared/Enums/FilterMode.cs | 16 ++++++++++ .../OrderedEnumerableExtensions.cs | 6 ++-- .../Extensions/ReflectionExtensions.cs | 17 ++++++---- .../NaturalStringComparer.cs | 12 +++++-- 73 files changed, 430 insertions(+), 167 deletions(-) delete mode 100644 Launcher/Enums/FilterMode.cs create mode 100644 Launcher/UI/Components/ContextMenu.razor.css create mode 100644 Launcher/UI/Components/ContextMenu.razor.min.css create mode 100644 Launcher/UI/Components/ContextMenu.razor.scss create mode 100644 Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css create mode 100644 Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css create mode 100644 Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss create mode 100644 Launcher/UI/Components/Icons/DiscardIcon.razor rename Launcher/UI/Components/{ => Icons}/Icon.razor (97%) create mode 100644 Launcher/UI/Components/Layout.razor.css create mode 100644 Launcher/UI/Components/Layout.razor.min.css create mode 100644 Launcher/UI/Components/Layout.razor.scss create mode 100644 Launcher/UI/Components/Navigation/Sidebar.razor.css create mode 100644 Launcher/UI/Components/Navigation/Sidebar.razor.min.css create mode 100644 Launcher/UI/Components/Navigation/Sidebar.razor.scss rename Launcher/UI/Components/{Dialogs => Notification}/Notification.razor (55%) create mode 100644 Launcher/UI/Components/Notification/Notification.razor.css create mode 100644 Launcher/UI/Components/Notification/Notification.razor.min.css create mode 100644 Launcher/UI/Components/Notification/Notification.razor.scss create mode 100644 Launcher/UI/Components/Notification/NotificationType.cs rename Launcher/UI/Components/{Dialogs/NotificationType.cs => Notification/NotificationTypeExtensions.cs} (59%) create mode 100644 Launcher/UI/Components/Searchbox.razor.css create mode 100644 Launcher/UI/Components/Searchbox.razor.min.css create mode 100644 Launcher/UI/Components/Searchbox.razor.scss create mode 100644 SharpEngine.Shared/Enums/FilterMode.cs rename {Launcher/UI => SharpEngine.Shared/Extensions}/OrderedEnumerableExtensions.cs (93%) rename {Launcher/UI => SharpEngine.Shared}/NaturalStringComparer.cs (83%) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 0dbb25f..ba5bdae 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -6,16 +6,16 @@ using SharpEngine.Core.Shaders; using SharpEngine.Core.Textures; using SharpEngine.Core.Windowing; + using Silk.NET.OpenGL; + using System.Collections.Generic; -using System.IO; -using System.Reflection; using System.Threading.Tasks; namespace SharpEngine.Core.Entities; /// -/// Represents a game object in the scene. +/// Represents a game object in the scene. /// public class GameObject : SceneNode { @@ -32,7 +32,7 @@ public GameObject() } /// - /// Initializes a new instance of the with specified textures and shaders. + /// Initializes a new instance of the with specified textures and shaders. /// /// The file path of the diffuse map texture. /// The file path of the specular map texture. diff --git a/Core/Entities/Lights/DirectionalLight.cs b/Core/Entities/Lights/DirectionalLight.cs index 4fcb63b..d320965 100644 --- a/Core/Entities/Lights/DirectionalLight.cs +++ b/Core/Entities/Lights/DirectionalLight.cs @@ -1,6 +1,7 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; + using System.Numerics; using System.Threading.Tasks; diff --git a/Core/Entities/Lights/PointLight.cs b/Core/Entities/Lights/PointLight.cs index 858dbf8..0dcdeff 100644 --- a/Core/Entities/Lights/PointLight.cs +++ b/Core/Entities/Lights/PointLight.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; + using System.Numerics; using System.Threading.Tasks; diff --git a/Core/Entities/Lights/SpotLight.cs b/Core/Entities/Lights/SpotLight.cs index 502952d..4a839f0 100644 --- a/Core/Entities/Lights/SpotLight.cs +++ b/Core/Entities/Lights/SpotLight.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; + using System.Numerics; using System.Threading.Tasks; diff --git a/Core/Entities/Properties/Meshes/Mesh.cs b/Core/Entities/Properties/Meshes/Mesh.cs index 5685204..aea061c 100644 --- a/Core/Entities/Properties/Meshes/Mesh.cs +++ b/Core/Entities/Properties/Meshes/Mesh.cs @@ -7,7 +7,7 @@ namespace SharpEngine.Core.Entities.Properties.Meshes; /// public class Mesh { - /// Gets or sets a indetifying name for the mesh. + /// Gets or sets a identifying name for the mesh. public string Name { get; set; } = "New Mesh"; /// diff --git a/Core/Entities/UIElement.cs b/Core/Entities/UIElement.cs index f367df0..2e777b0 100644 --- a/Core/Entities/UIElement.cs +++ b/Core/Entities/UIElement.cs @@ -3,8 +3,8 @@ using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; + using Silk.NET.OpenGL; -using System; using System.Threading.Tasks; namespace SharpEngine.Core.Entities; @@ -62,6 +62,9 @@ public void Initialize() InitializeBuffers(); } + /// + /// Binds the VAO to the current context. + /// public void Bind() { Window.GL.BindVertexArray(_vertexArrayObject); diff --git a/Core/Entities/Views/View.cs b/Core/Entities/Views/View.cs index 8f78b02..88992a0 100644 --- a/Core/Entities/Views/View.cs +++ b/Core/Entities/Views/View.cs @@ -21,7 +21,6 @@ public View(IViewSettings settings) Settings = settings; } - // Those vectors are directions pointing outwards from the camera to define how it rotated. protected Vector3 _front = -Vector3.UnitZ; protected Vector3 _up = Vector3.UnitY; protected Vector3 _right = Vector3.UnitX; @@ -35,15 +34,15 @@ public View(IViewSettings settings) /// Gets the right vector of the camera. public Vector3 Right => _right; + /// Gets or sets the position of the camera. + public Vector3 Position { get; set; } + /// Gets or sets the settings for the view. public IViewSettings Settings { get; set; } /// Gets or sets the aspect ratio of the viewport, used for the projection matrix. public float AspectRatio { get; set; } - /// Gets or sets the position of the camera. - public Vector3 Position { get; set; } - private protected bool firstMove; private protected Vector2 lastPos; diff --git a/Core/Extensions/CollectionExtensions.cs b/Core/Extensions/CollectionExtensions.cs index aab5eb9..fa8686d 100644 --- a/Core/Extensions/CollectionExtensions.cs +++ b/Core/Extensions/CollectionExtensions.cs @@ -13,7 +13,7 @@ public static class CollectionExtensions /// /// The type of the item. /// The item which is being checked if it's in the collection. - /// The collection where the item's existance is being checked. + /// The collection where the item's existence is being checked. /// if the item is in the collection; otherwise, . public static bool IsAnyOf(this T item, params T[] items) => items.Contains(item); diff --git a/Core/Math.cs b/Core/Math.cs index 102f871..7b74cad 100644 --- a/Core/Math.cs +++ b/Core/Math.cs @@ -40,7 +40,7 @@ public static float DegreesToRadians(float degrees) /// Converts a value in to degrees. /// /// The radians to convert. - /// The given as degress. + /// The given as degrees. public static float RadiansToDegrees(float radians) => radians * (180 / MathF.PI); } diff --git a/Core/Primitives/PrimitiveFactory.cs b/Core/Primitives/PrimitiveFactory.cs index 38ec50e..2c9e437 100644 --- a/Core/Primitives/PrimitiveFactory.cs +++ b/Core/Primitives/PrimitiveFactory.cs @@ -1,6 +1,7 @@ using SharpEngine.Core.Entities; using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Extensions; + using System; using System.Numerics; diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 5644ac3..1fdcee6 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -16,7 +16,7 @@ public class SceneNode /// /// Gets or sets the children of the node. /// - public List Children { get; set; } = new(); + public List Children { get; set; } = []; /// /// Initializes a new empty . diff --git a/Core/Shaders/LampShader.cs b/Core/Shaders/LampShader.cs index b28730f..844141e 100644 --- a/Core/Shaders/LampShader.cs +++ b/Core/Shaders/LampShader.cs @@ -7,6 +7,9 @@ namespace SharpEngine.Core.Shaders; internal class LampShader : ShaderBase { + /// + /// Initializes a new instance of . + /// public LampShader() { Shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/shader.frag"), "lamp"); diff --git a/Core/Shaders/LightingShader.cs b/Core/Shaders/LightingShader.cs index 17e7a9f..4701d04 100644 --- a/Core/Shaders/LightingShader.cs +++ b/Core/Shaders/LightingShader.cs @@ -2,12 +2,14 @@ using SharpEngine.Core.Extensions; using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; -using System; namespace SharpEngine.Core.Shaders; internal class LightingShader : ShaderBase { + /// + /// Initializes a new instance of . + /// public LightingShader() { Shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/lighting.frag"), "lighting"); diff --git a/Core/Shaders/ShaderService.cs b/Core/Shaders/ShaderService.cs index db685b3..6a27cb8 100644 --- a/Core/Shaders/ShaderService.cs +++ b/Core/Shaders/ShaderService.cs @@ -15,7 +15,7 @@ public class ShaderService public static ShaderService Instance { get; } = new ShaderService(); // TODO: Cache data is never set - private readonly Dictionary _shaderCache = new(); + private readonly Dictionary _shaderCache = []; /// /// Gets or sets whether there are shaders to load. @@ -34,7 +34,7 @@ private ShaderService() { } public List GetAll() { HasShadersToLoad = false; - return new(_shaderCache.Values); + return [.. _shaderCache.Values]; } /// diff --git a/Core/Shaders/UIShader.cs b/Core/Shaders/UIShader.cs index de44857..a72a726 100644 --- a/Core/Shaders/UIShader.cs +++ b/Core/Shaders/UIShader.cs @@ -6,6 +6,9 @@ namespace SharpEngine.Core.Shaders; internal class UIShader : ShaderBase { + /// + /// Initializes a new instance of . + /// public UIShader() { Shader = new Shader(PathExtensions.GetAssemblyPath("Shaders/uiShader.vert"), PathExtensions.GetAssemblyPath("Shaders/uiShader.frag"), "ui"); diff --git a/Core/Windowing/SilkWindow.cs b/Core/Windowing/SilkWindow.cs index 4dc8267..64ae012 100644 --- a/Core/Windowing/SilkWindow.cs +++ b/Core/Windowing/SilkWindow.cs @@ -217,9 +217,9 @@ public IWindow CreateWindow(WindowOptions opts) // public static T Create(WindowOptions opts) where T : SilkWindow, new() // { // var instance = new T(); - // var winndow = instance.CreateWindow(opts); + // var window = instance.CreateWindow(opts); // - // instance.CurrentWindow = winndow; + // instance.CurrentWindow = window; // return instance; // } diff --git a/Core/Windowing/Window.cs b/Core/Windowing/Window.cs index 6be21ca..f4ebdee 100644 --- a/Core/Windowing/Window.cs +++ b/Core/Windowing/Window.cs @@ -3,7 +3,6 @@ using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Enums; using SharpEngine.Core.Extensions; -using SharpEngine.Core.Interfaces; using SharpEngine.Core.Renderers; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; @@ -33,8 +32,14 @@ public class Window : SilkWindow private IEnumerable _renderers = []; private ImGuiController? _imGuiController; + /// + /// Gets or sets the view for the window. + /// public readonly CameraView Camera; + /// + /// Gets the settings for the current window. + /// public IViewSettings Settings; /// The event executed when mouse events are executed. @@ -67,8 +72,7 @@ public class Window : SilkWindow /// The OpenGL context for this window. public static GL GetGL() => GL; private static void SetGL(GL gl) => GL = gl; - public Window() { } - + /// /// Initializes a new instance of . /// @@ -99,7 +103,6 @@ public Window(Scene scene, IViewSettings settings) private bool _windowInitialized; - // public void Initialize() where T : SilkWindow, new() private void Initialize() { CurrentWindow = CreateWindow(Settings.WindowOptions); diff --git a/Editor/Windows/ActionsMenuWindow.cs b/Editor/Windows/ActionsMenuWindow.cs index f6471ab..a0e356e 100644 --- a/Editor/Windows/ActionsMenuWindow.cs +++ b/Editor/Windows/ActionsMenuWindow.cs @@ -1,9 +1,9 @@ using ImGuiNET; -using Launcher.UI; using SharpEngine.Core; using SharpEngine.Core.Audio; using SharpEngine.Core.Scenes; using SharpEngine.Shared.Extensions; +using System.Text; namespace SharpEngine.Editor.Windows { @@ -32,16 +32,24 @@ public override void Render() if (ImGui.Button("Play")) StartGame(); + const uint size = 256; + var buffer = new byte[size]; + + ImGui.InputText("Audio path", buffer, size); if (ImGui.Button("Test audio")) - PlayTestAudio(); + PlayTestAudio(Encoding.Default.GetString(buffer)); } - private void PlayTestAudio() + private void PlayTestAudio(string filePath) { - try + if (!File.Exists(filePath)) { - string filePath = ""; // TODO: Replace with absolute path to .wav file. + Debug.LogInformation($"Audio file '{filePath}' does not exist."); + return; + } + try + { var audioPlayer = new WavPlayer(); audioPlayer.AudioProperties.IsLooping = false; diff --git a/Editor/Windows/AssetsWindow.cs b/Editor/Windows/AssetsWindow.cs index ad5d876..9f6f531 100644 --- a/Editor/Windows/AssetsWindow.cs +++ b/Editor/Windows/AssetsWindow.cs @@ -1,5 +1,4 @@ using ImGuiNET; -using Launcher.UI; namespace SharpEngine.Editor.Windows { diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs index 01005fe..ed19afa 100644 --- a/Editor/Windows/ContextMenuWindow.cs +++ b/Editor/Windows/ContextMenuWindow.cs @@ -1,5 +1,4 @@ using ImGuiNET; -using Launcher.UI; using SharpEngine.Core.Entities; using SharpEngine.Core.Primitives; using SharpEngine.Core.Scenes; diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs index 376ee7a..481bbbb 100644 --- a/Examples/MinecraftClone/Program.cs +++ b/Examples/MinecraftClone/Program.cs @@ -18,7 +18,7 @@ private static void Main() Scene scene = new Scene(); Minecraft game = new Minecraft(scene, gameSettings); - using var window = new SharpEngine.Core.Window(game.Camera, scene, game.Camera.Settings); + using var window = new SharpEngine.Core.Windowing.Window(game.Camera, scene, game.Camera.Settings); window.OnLoaded += () => game.Initialize(); window.OnHandleMouse += game.HandleMouse; window.OnUpdate += game.Update; diff --git a/Examples/Minimal/Program.cs b/Examples/Minimal/Program.cs index b3c78f4..dd6f183 100644 --- a/Examples/Minimal/Program.cs +++ b/Examples/Minimal/Program.cs @@ -19,7 +19,7 @@ public static void Main(string[] _) var game = new Minimal(new DefaultSettings()); var scene = new Scene(); - using var window = new SharpEngine.Core.Window(game.Camera, scene, game.Camera.Settings); + using var window = new SharpEngine.Core.Windowing.Window(game.Camera, scene, game.Camera.Settings); window.Run(); } } diff --git a/Launcher/Attributes/FileAttribute.cs b/Launcher/Attributes/FileAttribute.cs index 0c748ea..fe43381 100644 --- a/Launcher/Attributes/FileAttribute.cs +++ b/Launcher/Attributes/FileAttribute.cs @@ -12,7 +12,7 @@ public class FileAttribute : ValidationAttribute public override bool IsValid(object? value) { if (value is string path) - return System.IO.File.Exists(path); + return File.Exists(path); return false; } diff --git a/Launcher/Enums/FilterMode.cs b/Launcher/Enums/FilterMode.cs deleted file mode 100644 index 06c4bb2..0000000 --- a/Launcher/Enums/FilterMode.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Launcher.Enums; - -public enum FilterMode -{ - /// - /// No filter applied. - /// - None, - - /// - /// Filter in ascending order. - /// - Ascending, - - /// - /// Filter in descending order. - /// - Descending -} diff --git a/Launcher/Enums/SvgIcon.cs b/Launcher/Enums/SvgIcon.cs index d2ce8f4..650ac01 100644 --- a/Launcher/Enums/SvgIcon.cs +++ b/Launcher/Enums/SvgIcon.cs @@ -1,13 +1,31 @@ namespace Launcher.Enums; +/// +/// Contains the supported SVG icons. +/// public enum SvgIcon { + /// Indicates no icon. None, + + /// Indicates the home icon. Home, + + /// Indicates the settings icon. Cog, + + /// Indicates the plus icon. Discard, + + /// Indicates the search lens icon. Lens, + + /// Indicates the close 'x' icon. Close, + + /// Indicates the chevron '<' down icon. ChevronDown, + + /// Indicates the chevron '<' up icon. ChevronUp, } diff --git a/Launcher/TestData/TestData.cs b/Launcher/TestData/TestData.cs index 18344e6..0643b85 100644 --- a/Launcher/TestData/TestData.cs +++ b/Launcher/TestData/TestData.cs @@ -1,18 +1,22 @@ using SharpEngine.Shared.Dto; namespace Launcher.TestData; + +/// +/// Contains test data for the launcher. +/// public static class TestData { + /// Indicates whether test data should be loaded. public const bool LoadTestData = true; + /// Indicates whether to simulate loading. public const bool SimulateLoading = false; - public static IEnumerable Projects = Enumerable.Range(0, 25).Select(i => + /// A collection of test data projects. + public static IEnumerable Projects = Enumerable.Range(0, 25).Select(i => new Project() { - return new Project() - { - Name = $"Project {i}", - Path = @"C:\test", - }; + Name = $"Project {i}", + Path = @"C:\test", }); } diff --git a/Launcher/UI/Components/ContextMenu.razor b/Launcher/UI/Components/ContextMenu.razor index 3e59bc2..6f3d22f 100644 --- a/Launcher/UI/Components/ContextMenu.razor +++ b/Launcher/UI/Components/ContextMenu.razor @@ -1,31 +1,18 @@ @if (showMenu) {
- @ChildContent(Context) + @ChildContent(_context!)
} - - @code { [Inject] - private IJSRuntime JS { get; set; } + private IJSRuntime JS { get; init; } = default!; [Parameter] public RenderFragment ChildContent { get; set; } - public object Context { get; set; } - + private object? _context; private bool showMenu = false; private int menuX, menuY; @@ -38,7 +25,7 @@ menuY = (int)e.ClientY; showMenu = true; - Context = context; + _context = context; // Prevent the default browser context menu JS.InvokeVoidAsync("preventContextMenu"); diff --git a/Launcher/UI/Components/ContextMenu.razor.css b/Launcher/UI/Components/ContextMenu.razor.css new file mode 100644 index 0000000..d0f597f --- /dev/null +++ b/Launcher/UI/Components/ContextMenu.razor.css @@ -0,0 +1,9 @@ +.context-menu { + position: absolute; + z-index: 1000; + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 8px; +} diff --git a/Launcher/UI/Components/ContextMenu.razor.min.css b/Launcher/UI/Components/ContextMenu.razor.min.css new file mode 100644 index 0000000..c5c1a48 --- /dev/null +++ b/Launcher/UI/Components/ContextMenu.razor.min.css @@ -0,0 +1 @@ +.context-menu{position:absolute;z-index:1000;background-color:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 2px 4px rgba(0,0,0,.1);padding:8px;} \ No newline at end of file diff --git a/Launcher/UI/Components/ContextMenu.razor.scss b/Launcher/UI/Components/ContextMenu.razor.scss new file mode 100644 index 0000000..580b5a7 --- /dev/null +++ b/Launcher/UI/Components/ContextMenu.razor.scss @@ -0,0 +1,9 @@ +.context-menu { + position: absolute; + z-index: 1000; + background-color: white; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 8px; +} diff --git a/Launcher/UI/Components/Dialogs/ConfirmDeleteDialog.razor b/Launcher/UI/Components/Dialogs/ConfirmDeleteDialog.razor index 52ed7ad..53bdec2 100644 --- a/Launcher/UI/Components/Dialogs/ConfirmDeleteDialog.razor +++ b/Launcher/UI/Components/Dialogs/ConfirmDeleteDialog.razor @@ -22,7 +22,7 @@ @code { private string _confirmDeleteVisibleClass => Visible ? "confirm-delete-visible" : "confirm-delete-hidden"; - /// Gets or sets the event exectued when the project deletion is confirmed. + /// Gets or sets the event executed when the project deletion is confirmed. [Parameter] public EventCallback OnDeleteProjectConfirmed { get; set; } diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor index 27d5313..5f3275d 100644 --- a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor +++ b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor @@ -10,7 +10,7 @@ -
+
@@ -18,7 +18,7 @@
-
+
diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css new file mode 100644 index 0000000..a76d394 --- /dev/null +++ b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css @@ -0,0 +1,9 @@ +.field-path-container { + display: flex; + align-items: center; + gap: 8px; +} + +.field-name-container { + margin-bottom: 8px; +} diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css new file mode 100644 index 0000000..0ee1ee3 --- /dev/null +++ b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css @@ -0,0 +1 @@ +.field-path-container{display:flex;align-items:center;gap:8px;}.field-name-container{margin-bottom:8px;} \ No newline at end of file diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss new file mode 100644 index 0000000..6457dd0 --- /dev/null +++ b/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss @@ -0,0 +1,9 @@ +.field-path-container { + display: flex; + align-items: center; + gap: 8px; +} + +.field-name-container { + margin-bottom: 8px; +} \ No newline at end of file diff --git a/Launcher/UI/Components/Flyouts/Flyout.razor b/Launcher/UI/Components/Flyouts/Flyout.razor index efb293e..5fca179 100644 --- a/Launcher/UI/Components/Flyouts/Flyout.razor +++ b/Launcher/UI/Components/Flyouts/Flyout.razor @@ -22,21 +22,27 @@
@code { + /// Gets or sets the header content. [Parameter] public RenderFragment Header { get; set; } + /// Gets or sets the main content. [Parameter] public RenderFragment Content { get; set; } + /// Gets or sets the footer content. [Parameter] public RenderFragment Footer { get; set; } + /// Gets or sets the title. [Parameter] public string Title { get; set; } = default!; + /// Gets or sets whether the flyout is visible. [Parameter] public bool IsVisible { get; set; } + /// Gets or sets the event executed when the visibility changes. [Parameter] public EventCallback IsVisibleChanged { get; set; } diff --git a/Launcher/UI/Components/Flyouts/FlyoutBase.cs b/Launcher/UI/Components/Flyouts/FlyoutBase.cs index ed720a2..0d0d302 100644 --- a/Launcher/UI/Components/Flyouts/FlyoutBase.cs +++ b/Launcher/UI/Components/Flyouts/FlyoutBase.cs @@ -2,23 +2,42 @@ namespace Launcher.UI.Components.Flyouts; +/// +/// Represents a base class for flyout components. +/// public class FlyoutBase : ComponentBase { + /// Gets or sets whether the flyout is visible. [Parameter] public bool IsVisible { get; set; } + /// Gets or sets the event executed when the visibility changes. [Parameter] public EventCallback IsVisibleChanged { get; set; } + /// Gets or sets the event executed when the flyout is closed. [Parameter] public EventCallback OnClose { get; set; } + /// + /// Handles changes in visibility asynchronously and updates the visibility state. + /// + /// Indicates whether the element is currently visible or not. + /// + /// A representing an asynchronous operation. + /// public virtual async Task OnIsVisibleChangedAsync(bool visibility) { IsVisible = visibility; await IsVisibleChanged.InvokeAsync(visibility); } + /// + /// Closes the flyout. + /// + /// + /// A representing an asynchronous operation. + /// public virtual async Task Close() { await OnClose.InvokeAsync(); diff --git a/Launcher/UI/Components/Grid/Grid.razor b/Launcher/UI/Components/Grid/Grid.razor index 5050b38..2b23f84 100644 --- a/Launcher/UI/Components/Grid/Grid.razor +++ b/Launcher/UI/Components/Grid/Grid.razor @@ -51,6 +51,7 @@ private List _headers { get; set; } = []; private Dictionary _filterModes { get; set; } = new(); + /// protected override Task OnInitializedAsync() { GetHeaders(); diff --git a/Launcher/UI/Components/Grid/GridHeader.cs b/Launcher/UI/Components/Grid/GridHeader.cs index 4c8a9de..91e07ec 100644 --- a/Launcher/UI/Components/Grid/GridHeader.cs +++ b/Launcher/UI/Components/Grid/GridHeader.cs @@ -1,10 +1,21 @@ namespace Launcher.UI.Components.Grid; +/// +/// Contains information about a grid header. +/// public class GridHeaderInfo { + /// Gets or sets the title of the column. public string Title { get; set; } + + /// Gets or sets whether the column allows filtering. public bool AllowFiltering { get; set; } + /// + /// Initializes a new instance of the . + /// + /// Specifies the header title for the grid. + /// Indicates whether filtering is permitted on the grid header. public GridHeaderInfo(string title, bool allowFiltering) { Title = title; diff --git a/Launcher/UI/Components/Grid/GridHeader.razor b/Launcher/UI/Components/Grid/GridHeader.razor index 00c7563..e5a8481 100644 --- a/Launcher/UI/Components/Grid/GridHeader.razor +++ b/Launcher/UI/Components/Grid/GridHeader.razor @@ -1,6 +1,4 @@ -@using Launcher.Enums - - + - + @code { + /// Gets or sets the value of the search box. [Parameter] public string Value { get; set; } = default!; + /// Gets or sets the event executed when the value of the search box changes. [Parameter] public EventCallback ValueChanged { get; set; } + /// Gets or sets the delay in milliseconds before the value is updated after the user stops typing. [Parameter] public int DebounceDelayMilliseconds { get; set; } = 1000; + /// Gets or sets any extra CSS classes to apply to the search box. [Parameter] public string? CssClass { get; set; } diff --git a/Launcher/UI/Components/Searchbox.razor.css b/Launcher/UI/Components/Searchbox.razor.css new file mode 100644 index 0000000..9f2bc34 --- /dev/null +++ b/Launcher/UI/Components/Searchbox.razor.css @@ -0,0 +1,14 @@ +.searchbox { + border: 1px solid black; + width: 40%; + justify-self: end; +} + +.searchbox-btn { + border: none; + background: none; +} + +.searchbox-input { + border: none; +} diff --git a/Launcher/UI/Components/Searchbox.razor.min.css b/Launcher/UI/Components/Searchbox.razor.min.css new file mode 100644 index 0000000..2641fc8 --- /dev/null +++ b/Launcher/UI/Components/Searchbox.razor.min.css @@ -0,0 +1 @@ +.searchbox{border:1px solid #000;width:40%;justify-self:end;}.searchbox-btn{border:0;background:none;}.searchbox-input{border:0;} \ No newline at end of file diff --git a/Launcher/UI/Components/Searchbox.razor.scss b/Launcher/UI/Components/Searchbox.razor.scss new file mode 100644 index 0000000..4dd8607 --- /dev/null +++ b/Launcher/UI/Components/Searchbox.razor.scss @@ -0,0 +1,14 @@ +.searchbox { + border: 1px solid black; + width: 40%; + justify-self: end; +} + +.searchbox-btn { + border: none; + background: none; +} + +.searchbox-input { + border: none; +} \ No newline at end of file diff --git a/Launcher/UI/Home.razor b/Launcher/UI/Home.razor index 1a8c2bb..0eae1e4 100644 --- a/Launcher/UI/Home.razor +++ b/Launcher/UI/Home.razor @@ -1,11 +1,5 @@ @page "/" -@using System.Diagnostics -@using Launcher.UI.Components -@using Launcher.UI.Components.Dialogs -@using Launcher.UI.Components.Grid -@using SharpEngine.Shared.Dto - @@ -30,7 +24,7 @@ @context.LastModified - + @@ -44,8 +38,4 @@ } - - -@code { - private ContextMenu? _contextMenu; -} \ No newline at end of file + \ No newline at end of file diff --git a/Launcher/UI/Home.razor.cs b/Launcher/UI/Home.razor.cs index 5e33d0b..73ff595 100644 --- a/Launcher/UI/Home.razor.cs +++ b/Launcher/UI/Home.razor.cs @@ -1,10 +1,11 @@ using Launcher.Services; +using Launcher.UI.Components; using Microsoft.AspNetCore.Components; using SharpEngine.Shared.Dto; using System.Diagnostics; using System.Text.Json; -using FilterMode = Launcher.Enums.FilterMode; +using FilterMode = SharpEngine.Shared.Enums.FilterMode; namespace Launcher.UI { @@ -20,6 +21,8 @@ public partial class Home : ComponentBase private bool _showConfirmDelete; private bool _showCreateProjectDialog; + private ContextMenu? _contextMenu; + private List Projects { get => _allProjects; diff --git a/Launcher/UI/_Imports.razor b/Launcher/UI/_Imports.razor index a51eaed..ea37d20 100644 --- a/Launcher/UI/_Imports.razor +++ b/Launcher/UI/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using System.Net.Http.Json +@using System.Diagnostics @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @@ -8,6 +9,17 @@ @using Microsoft.JSInterop @using Launcher -@using Launcher.UI @using Launcher.Services @using Launcher.Enums + +@using Launcher.UI +@using Launcher.UI.Components +@using Launcher.UI.Components.Grid +@using Launcher.UI.Components.Notification +@using Launcher.UI.Components.Icons +@using Launcher.UI.Components.Dialogs +@using Launcher.UI.Components.Flyouts +@using Launcher.UI.Components.Navigation + +@using SharpEngine.Shared.Dto +@using SharpEngine.Shared.Enums \ No newline at end of file diff --git a/Launcher/compilerconfig.json b/Launcher/compilerconfig.json index 99824cb..fa73a0f 100644 --- a/Launcher/compilerconfig.json +++ b/Launcher/compilerconfig.json @@ -34,5 +34,29 @@ { "outputFile": "UI/Components/Grid/GridHeader.razor.css", "inputFile": "UI/Components/Grid/GridHeader.razor.scss" + }, + { + "outputFile": "UI/Components/Dialogs/CreateProjectDialog.razor.css", + "inputFile": "UI/Components/Dialogs/CreateProjectDialog.razor.scss" + }, + { + "outputFile": "UI/Components/Navigation/Sidebar.razor.css", + "inputFile": "UI/Components/Navigation/Sidebar.razor.scss" + }, + { + "outputFile": "UI/Components/Notification/Notification.razor.css", + "inputFile": "UI/Components/Notification/Notification.razor.scss" + }, + { + "outputFile": "UI/Components/ContextMenu.razor.css", + "inputFile": "UI/Components/ContextMenu.razor.scss" + }, + { + "outputFile": "UI/Components/Layout.razor.css", + "inputFile": "UI/Components/Layout.razor.scss" + }, + { + "outputFile": "UI/Components/Searchbox.razor.css", + "inputFile": "UI/Components/Searchbox.razor.scss" } ] \ No newline at end of file diff --git a/SharpEngine.Shared/Attributes/FilterAttribute.cs b/SharpEngine.Shared/Attributes/FilterAttribute.cs index 4cfd400..f37af87 100644 --- a/SharpEngine.Shared/Attributes/FilterAttribute.cs +++ b/SharpEngine.Shared/Attributes/FilterAttribute.cs @@ -1,10 +1,18 @@ namespace SharpEngine.Shared.Attributes; +/// +/// Represents a attribute that indicates if a property is filterable. +/// [AttributeUsage(AttributeTargets.Property)] public class FilterAttribute : Attribute { + /// Gets or sets whether the property is filterable in collections. public bool IsFilterable { get; set; } + /// + /// Initializes a new instance of . + /// + /// Determines whether the target property is filterable. public FilterAttribute(bool isFilterable = true) { IsFilterable = isFilterable; diff --git a/SharpEngine.Shared/Enums/FilterMode.cs b/SharpEngine.Shared/Enums/FilterMode.cs new file mode 100644 index 0000000..71d1da1 --- /dev/null +++ b/SharpEngine.Shared/Enums/FilterMode.cs @@ -0,0 +1,16 @@ +namespace SharpEngine.Shared.Enums; + +/// +/// Contains the possible ways to filter a collection. +/// +public enum FilterMode +{ + /// No filter applied. + None, + + /// Filter in ascending order. + Ascending, + + /// Filter in descending order. + Descending +} diff --git a/Launcher/UI/OrderedEnumerableExtensions.cs b/SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs similarity index 93% rename from Launcher/UI/OrderedEnumerableExtensions.cs rename to SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs index ba8ac20..d6f93d6 100644 --- a/Launcher/UI/OrderedEnumerableExtensions.cs +++ b/SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs @@ -1,9 +1,11 @@ using Launcher.UI; using SharpEngine.Shared.Dto; +using SharpEngine.Shared.Enums; using SharpEngine.Shared.Extensions; -using FilterMode = Launcher.Enums.FilterMode; - +/// +/// Handles ordering collections. +/// public static class OrderedEnumerableExtensions { /// diff --git a/SharpEngine.Shared/Extensions/ReflectionExtensions.cs b/SharpEngine.Shared/Extensions/ReflectionExtensions.cs index 081cdc9..a663bf7 100644 --- a/SharpEngine.Shared/Extensions/ReflectionExtensions.cs +++ b/SharpEngine.Shared/Extensions/ReflectionExtensions.cs @@ -1,12 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace SharpEngine.Shared.Extensions; -namespace SharpEngine.Shared.Extensions; +/// +/// Contains extensions for reflection. +/// public static class ReflectionExtensions { + /// + /// Gets the value of a property from the given . + /// + /// The type of the object. + /// The object whose property value is being retrieved. + /// The name of the property whose values is being retrieved. + /// The value of the property, if not found. public static object? GetPropertyValue(this T obj, string propertyName) { var property = typeof(T).GetProperty(propertyName); diff --git a/Launcher/UI/NaturalStringComparer.cs b/SharpEngine.Shared/NaturalStringComparer.cs similarity index 83% rename from Launcher/UI/NaturalStringComparer.cs rename to SharpEngine.Shared/NaturalStringComparer.cs index 248c3f5..50b10b7 100644 --- a/Launcher/UI/NaturalStringComparer.cs +++ b/SharpEngine.Shared/NaturalStringComparer.cs @@ -2,6 +2,9 @@ namespace Launcher.UI; +/// +/// Checks if two strings are equal in a natural way. +/// public class NaturalStringComparer : IComparer { /// @@ -28,7 +31,7 @@ public int Compare(string? s1, string? s2) return (len1 - i).CompareTo(len2 - j); } - private int CompareCharacters(string s1, string s2, ref int i, ref int j) + private static int CompareCharacters(string s1, string s2, ref int i, ref int j) { char c1 = s1[i]; char c2 = s2[j]; @@ -37,9 +40,12 @@ private int CompareCharacters(string s1, string s2, ref int i, ref int j) if (char.IsDigit(c1) && char.IsDigit(c2)) { int start1 = i; - while (i < s1.Length && char.IsDigit(s1[i])) i++; + while (i < s1.Length && char.IsDigit(s1[i])) + i++; + int start2 = j; - while (j < s2.Length && char.IsDigit(s2[j])) j++; + while (j < s2.Length && char.IsDigit(s2[j])) + j++; string numStr1 = s1.Substring(start1, i - start1); string numStr2 = s2.Substring(start2, j - start2); From ea21cc98ee15c137c39e670205761dcc2b8a781a Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 29 Mar 2025 18:54:04 +0200 Subject: [PATCH 08/28] Cleanup UIElement mesh data --- Core/Entities/Properties/Meshes/Mesh.cs | 3 ++ .../Entities/Properties/Meshes/MeshService.cs | 6 ++++ .../Properties/{ => Meshes}/VertexData.cs | 4 +-- Core/Entities/UIElement.cs | 29 +++++-------------- Core/Primitives/Cube.cs | 26 +++++++++++++++++ Core/Primitives/Plane.cs | 27 ++++++++++------- Core/Renderers/UIRenderer.cs | 2 -- Core/Shaders/LampShader.cs | 2 +- Core/Shaders/LightingShader.cs | 2 +- Core/Shaders/UIShader.cs | 27 +++++++++++++++-- Core/Shaders/uiShader.frag | 5 ++++ Core/Shaders/uiShader.vert | 12 ++++++-- Editor/Windows/ContextMenuWindow.cs | 6 ++++ Examples/MinecraftClone/Minecraft.cs | 2 +- 14 files changed, 109 insertions(+), 44 deletions(-) rename Core/Entities/Properties/{ => Meshes}/VertexData.cs (90%) diff --git a/Core/Entities/Properties/Meshes/Mesh.cs b/Core/Entities/Properties/Meshes/Mesh.cs index aea061c..88ca01c 100644 --- a/Core/Entities/Properties/Meshes/Mesh.cs +++ b/Core/Entities/Properties/Meshes/Mesh.cs @@ -27,4 +27,7 @@ public class Mesh /// [Inspector(DisplayInInspector = false)] public float[] TextureCoordinates { get; set; } = []; + + [Inspector(DisplayInInspector = false)] + public uint[] Indices { get; set; } = []; } \ No newline at end of file diff --git a/Core/Entities/Properties/Meshes/MeshService.cs b/Core/Entities/Properties/Meshes/MeshService.cs index 7f7ed06..d50b576 100644 --- a/Core/Entities/Properties/Meshes/MeshService.cs +++ b/Core/Entities/Properties/Meshes/MeshService.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; +using System; using System.Collections.Generic; namespace SharpEngine.Core.Entities.Properties.Meshes; @@ -37,5 +38,10 @@ public Mesh LoadMesh(string identifier, Mesh mesh) return mesh; } + public Mesh LoadMesh(string identifier, string meshFilePath) + { + throw new NotImplementedException("Loading meshes from files is not currently supported."); + } + // TODO: #3 & #2 Load mesh from file } \ No newline at end of file diff --git a/Core/Entities/Properties/VertexData.cs b/Core/Entities/Properties/Meshes/VertexData.cs similarity index 90% rename from Core/Entities/Properties/VertexData.cs rename to Core/Entities/Properties/Meshes/VertexData.cs index c25e5d4..f9172c2 100644 --- a/Core/Entities/Properties/VertexData.cs +++ b/Core/Entities/Properties/Meshes/VertexData.cs @@ -1,11 +1,11 @@ -namespace SharpEngine.Core.Entities.Properties; +namespace SharpEngine.Core.Entities.Properties.Meshes; /// /// Represents the data of a vertex. /// public struct VertexData { - /// The stride or lenght of the vertex data object. + /// The stride or length of the vertex data object. public const int Stride = 8 * sizeof(float); /// The size of the vertices in the vertex data object. diff --git a/Core/Entities/UIElement.cs b/Core/Entities/UIElement.cs index 2e777b0..592180e 100644 --- a/Core/Entities/UIElement.cs +++ b/Core/Entities/UIElement.cs @@ -21,7 +21,7 @@ public class UIElement : SceneNode public UIElement(string name) { Name = name; - // Mesh = MeshService.Instance.LoadMesh("plane", Primitives.Plane.Mesh); + Mesh = MeshService.Instance.LoadMesh(nameof(Primitives.Plane), Primitives.Plane.Mesh); } private readonly UIShader _uIShader = new(); @@ -29,27 +29,12 @@ public UIElement(string name) /// Gets or sets the 2D space transformation of the UI element. public Transform2D Transform { get; set; } = new() { - Rotation = 180 + //Rotation = 180 + Scale = new System.Numerics.Vector2(0.5f, 0.5f) }; - // TODO: Use actual mesh /// Gets or sets the mesh of the UI element. - public Mesh Mesh { get; set; } // = new(); - - private readonly float[] _vertices = - [ - // Position Texture coordinates - 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // top right - 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom right - -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom left - -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // top left - ]; - - private readonly uint[] _indices = - [ - 0, 1, 3, - 1, 2, 3 - ]; + public Mesh Mesh { get; set; } private uint _vertexArrayObject; @@ -74,11 +59,11 @@ private void InitializeBuffers() { var vertexBufferObject = Window.GL.GenBuffer(); Window.GL.BindBuffer(GLEnum.ArrayBuffer, vertexBufferObject); - Window.GL.BufferData(GLEnum.ArrayBuffer, _vertices, GLEnum.StaticDraw); + Window.GL.BufferData(GLEnum.ArrayBuffer, Mesh.GetVertices(), GLEnum.StaticDraw); var elementBufferObject = Window.GL.GenBuffer(); Window.GL.BindBuffer(GLEnum.ElementArrayBuffer, elementBufferObject); - Window.GL.BufferData(GLEnum.ElementArrayBuffer, _indices, GLEnum.StaticDraw); + Window.GL.BufferData(GLEnum.ElementArrayBuffer, Mesh.Indices, GLEnum.StaticDraw); } /// @@ -90,7 +75,7 @@ public override Task Render() _uIShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); - Window.GL.DrawElements(PrimitiveType.Triangles, (uint)_indices.Length, DrawElementsType.UnsignedInt, []); + Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); return Task.CompletedTask; } diff --git a/Core/Primitives/Cube.cs b/Core/Primitives/Cube.cs index 6db45cc..7a82896 100644 --- a/Core/Primitives/Cube.cs +++ b/Core/Primitives/Cube.cs @@ -141,6 +141,32 @@ public static class Cube 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f + ], + Indices = + [ + // Front face + 0, 1, 2, + 2, 3, 0, + + // Back face + 4, 5, 6, + 6, 7, 4, + + // Left face + 4, 0, 3, + 3, 7, 4, + + // Right face + 1, 5, 6, + 6, 2, 1, + + // Top face + 3, 2, 6, + 6, 7, 3, + + // Bottom face + 4, 5, 1, + 1, 0, 4 ] }; } diff --git a/Core/Primitives/Plane.cs b/Core/Primitives/Plane.cs index 1468423..b7c6911 100644 --- a/Core/Primitives/Plane.cs +++ b/Core/Primitives/Plane.cs @@ -12,24 +12,29 @@ public static class Plane { Vertices = [ - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f, // top left + 1f, 1f, 0.0f, // top right + 1f, -1f, 0.0f, // bottom right + -1f, -1f, 0.0f, // bottom left + -1f, 1f, 0.0f, // top left ], Normals = - [ - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - ], - TextureCoordinates = [ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + ], + TextureCoordinates = + [ + 1.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 0.0f, 1.0f, + ], + Indices = + [ + 0, 1, 3, // first triangle + 1, 2, 3, // second triangle ] }; } diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 3cb617a..4ae061d 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -57,8 +57,6 @@ private void InitializeVertexArrays() /// public override Task Render() { - return Task.CompletedTask; - try { Window.GL.Disable(EnableCap.DepthTest); diff --git a/Core/Shaders/LampShader.cs b/Core/Shaders/LampShader.cs index 844141e..465ab1c 100644 --- a/Core/Shaders/LampShader.cs +++ b/Core/Shaders/LampShader.cs @@ -1,4 +1,4 @@ -using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Extensions; using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; diff --git a/Core/Shaders/LightingShader.cs b/Core/Shaders/LightingShader.cs index 4701d04..e7ee010 100644 --- a/Core/Shaders/LightingShader.cs +++ b/Core/Shaders/LightingShader.cs @@ -1,4 +1,4 @@ -using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Extensions; using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; diff --git a/Core/Shaders/UIShader.cs b/Core/Shaders/UIShader.cs index a72a726..99c7b4a 100644 --- a/Core/Shaders/UIShader.cs +++ b/Core/Shaders/UIShader.cs @@ -1,4 +1,5 @@ -using SharpEngine.Core.Extensions; +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Extensions; using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; @@ -25,7 +26,29 @@ public override bool SetAttributes() uint vertexLocationUInt = (uint)vertexLocation; Window.GL.EnableVertexAttribArray(vertexLocationUInt); - Window.GL.VertexAttribPointer(vertexLocationUInt, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0); + Window.GL.VertexAttribPointer(vertexLocationUInt, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); + + /*if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation)) + return false; + + var positionLocationUint = (uint)positionLocation; + Window.GL.EnableVertexAttribArray(positionLocationUint); + Window.GL.VertexAttribPointer(positionLocationUint, VertexData.VerticesSize, VertexAttribPointerType.Float, false, VertexData.Stride, 0); + + if (!Shader!.TryGetAttribLocation(ShaderAttributes.Normal, out int normalLocation)) + return false; + + var normalLocationUint = (uint)normalLocation; + Window.GL.EnableVertexAttribArray(normalLocationUint); + Window.GL.VertexAttribPointer(normalLocationUint, VertexData.NormalsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.NormalsOffset); + + if (!Shader!.TryGetAttribLocation(ShaderAttributes.TexCoords, out int texCoordLocation)) + return false; + + var texCoordLocationUint = (uint)texCoordLocation; + Window.GL.EnableVertexAttribArray(texCoordLocationUint); + Window.GL.VertexAttribPointer(texCoordLocationUint, VertexData.TexCoordsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.TexCoordsOffset); + */ return true; } diff --git a/Core/Shaders/uiShader.frag b/Core/Shaders/uiShader.frag index fe3175f..36a7e45 100644 --- a/Core/Shaders/uiShader.frag +++ b/Core/Shaders/uiShader.frag @@ -1,7 +1,12 @@ #version 330 core + +in vec2 TexCoord; out vec4 FragColor; +uniform sampler2D texture1; + void main() { FragColor = vec4(1.0, 0.0, 0.0, 1.0); // set all 4 vector values to 1.0 + // FragColor = texture(texture1, TexCoord); } \ No newline at end of file diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index aa9fdcc..8d97e16 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -1,15 +1,23 @@ #version 330 core layout (location = 0) in vec3 aPos; +// layout (location = 1) in vec3 aNormal; +// layout (location = 2) in vec2 aTexCoord; uniform mat4 model; +// uniform mat4 view; +// uniform mat4 projection; out vec3 FragPos; void main() { vec4 pos = vec4(aPos, 1.0); - gl_Position = model * pos; - FragPos = vec3(model * pos); + + // gl_Position = vec4(aPos, 1.0) * model * view * projection; + // FragPos = vec3(vec4(aPos, 1.0) * model); + // TexCoord = aTexCoord; + // Normal = aNormal * mat3(transpose(inverse(model))); + // TexCoords = aTexCoords; } \ No newline at end of file diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs index ed19afa..603b8dc 100644 --- a/Editor/Windows/ContextMenuWindow.cs +++ b/Editor/Windows/ContextMenuWindow.cs @@ -85,6 +85,12 @@ private void RenderSceneActions() Scene.Root.AddChild(cube); } } + + if (ImGui.Button("Empty")) + { + var element = Scene.ActiveElement ?? Scene.Root; + element.AddChild(new SceneNode("Empty")); + } } } } diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index e1311bd..eb102c9 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -136,7 +136,7 @@ private void GenerateChunk(int chunkSize, Vector3 chunkPos) /// public override void Update(double deltaTime, IInputContext input) { - UpdateUI(); + // UpdateUI(); _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime); } From 4b5cf13a1bea2b0d60822581193f8763170e6284 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 29 Mar 2025 19:53:39 +0200 Subject: [PATCH 09/28] Cleanup --- Core/Primitives/PrimitiveFactory.cs | 8 ++--- Core/_Resources/Default.cs | 14 ++++++++ Editor/EditorWindow.cs | 4 +++ Examples/MinecraftClone/Block/Dirt.cs | 8 ++--- Examples/MinecraftClone/Block/Stone.cs | 8 ++--- SharpEngine.Shared/Dto/UserProfile.cs | 47 ++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 Core/_Resources/Default.cs create mode 100644 SharpEngine.Shared/Dto/UserProfile.cs diff --git a/Core/Primitives/PrimitiveFactory.cs b/Core/Primitives/PrimitiveFactory.cs index 2c9e437..9a63da5 100644 --- a/Core/Primitives/PrimitiveFactory.cs +++ b/Core/Primitives/PrimitiveFactory.cs @@ -12,17 +12,13 @@ namespace SharpEngine.Core.Primitives; /// public static class PrimitiveFactory { - private static string DebugTexture => PathExtensions.GetAssemblyPath("Textures/DefaultTextures/debug.JPG"); - private static string DefaultVertexShader => PathExtensions.GetAssemblyPath("Shaders/shader.vert"); - private static string DefaultFragmentShader => PathExtensions.GetAssemblyPath("Shaders/lighting.frag"); - /// /// Creates a new by the given primitive type. /// /// The type of primitive to create. /// Where the game object should be placed. public static GameObject Create(PrimitiveType primitiveType, Vector3 position) - => Create(primitiveType, position, DebugTexture); + => Create(primitiveType, position, _Resources.Default.DebugTexture); /// /// The type of primitive to be created. @@ -34,7 +30,7 @@ public static GameObject Create(PrimitiveType primitiveType, Vector3 position) /// A new game object. /// Thrown when the specified primitive type does not exist. public static GameObject Create(PrimitiveType primitiveType, Vector3 position, string diffuseMapFile, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) - => new(diffuseMapFile, specularMapFile ?? DebugTexture, vertShaderFile ?? DefaultVertexShader, fragShaderFile ?? DefaultFragmentShader) + => new(diffuseMapFile, specularMapFile ?? _Resources.Default.DebugTexture, vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader) { Transform = new Transform { Position = position }, Mesh = primitiveType switch diff --git a/Core/_Resources/Default.cs b/Core/_Resources/Default.cs new file mode 100644 index 0000000..77dced2 --- /dev/null +++ b/Core/_Resources/Default.cs @@ -0,0 +1,14 @@ +using SharpEngine.Core.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Core._Resources; +public struct Default +{ + public static string DebugTexture => PathExtensions.GetAssemblyPath("Textures\\DefaultTextures\\debug.JPG"); + public static string VertexShader => PathExtensions.GetAssemblyPath("Shaders\\shader.vert"); + public static string FragmentShader => PathExtensions.GetAssemblyPath("Shaders\\lighting.frag"); +} diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index 807a5b4..87d99de 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -209,6 +209,10 @@ private static void RenderFileMenu() { // Handle Exit action } + if (ImGui.MenuItem("Publish")) + { + // Handle Publish action + } ImGui.EndMenu(); } diff --git a/Examples/MinecraftClone/Block/Dirt.cs b/Examples/MinecraftClone/Block/Dirt.cs index 8aada39..edcb5bb 100644 --- a/Examples/MinecraftClone/Block/Dirt.cs +++ b/Examples/MinecraftClone/Block/Dirt.cs @@ -13,10 +13,10 @@ internal class Dirt : BlockBase /// /// The position where the block should be initialized. /// The name of the block to initialize. - public Dirt(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources/grass.jpg"), - PathExtensions.GetAssemblyPath("Resources/container2_specular.png"), - PathExtensions.GetAssemblyPath("Shaders/shader.vert"), - PathExtensions.GetAssemblyPath("Shaders/lighting.frag")) { } + public Dirt(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\grass.jpg"), + PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"), + PathExtensions.GetAssemblyPath("Shaders\\shader.vert"), + PathExtensions.GetAssemblyPath("Shaders\\lighting.frag")) { } /// public override BlockType BlockType => BlockType.Dirt; diff --git a/Examples/MinecraftClone/Block/Stone.cs b/Examples/MinecraftClone/Block/Stone.cs index 08dc773..599c086 100644 --- a/Examples/MinecraftClone/Block/Stone.cs +++ b/Examples/MinecraftClone/Block/Stone.cs @@ -13,10 +13,10 @@ internal class Stone : BlockBase /// /// The position where the block is created. /// The name of the object in the scene. - public Stone(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources/container2.png"), - PathExtensions.GetAssemblyPath("Resources/container2_specular.png"), - PathExtensions.GetAssemblyPath("Shaders/shader.vert"), - PathExtensions.GetAssemblyPath("Shaders/lighting.frag")) { } + public Stone(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\container2.png"), + PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"), + PathExtensions.GetAssemblyPath("Shaders\\shader.vert"), + PathExtensions.GetAssemblyPath("Shaders\\lighting.frag")) { } /// public override BlockType BlockType => BlockType.Stone; diff --git a/SharpEngine.Shared/Dto/UserProfile.cs b/SharpEngine.Shared/Dto/UserProfile.cs new file mode 100644 index 0000000..6235570 --- /dev/null +++ b/SharpEngine.Shared/Dto/UserProfile.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Shared.Dto; + +public class UserProfile +{ + public string Username { get; set; } = default!; + public string Email { get; set; } = default!; + public string Password + { + get => _password; + set => SetPassword(value); + } + + private string _password; + + public List LinkedSocialMedia { get; set; } = []; + + public List Projects { get; set; } = []; + + public List Achievements { get; set; } = []; + + private void SetPassword(string pwd) + { + // TODO: Figure out a good way to handle encryption + _password = pwd; + } +} + +public class SocialMedia +{ + public string Name { get; set; } + public string Url { get; set; } +} + +public class Achievement +{ + public string Name { get; set; } + public string Description { get; set; } + public DateTime? DateAchieved { get; set; } + + public bool IsAchieved => DateAchieved.HasValue; +} From 8bd4c98f50417e4f15425b602e3ab9395296d4d4 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 29 Mar 2025 20:04:14 +0200 Subject: [PATCH 10/28] Texturing improvements --- Core/Entities/GameObject.cs | 30 +++++++++++++++------------- Core/Entities/Properties/Material.cs | 7 +++++-- Core/Primitives/PrimitiveFactory.cs | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index ba5bdae..7f8b0d6 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -1,7 +1,6 @@ using SharpEngine.Core.Attributes; using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; -using SharpEngine.Core.Extensions; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Textures; @@ -24,8 +23,9 @@ public class GameObject : SceneNode /// public GameObject() { - var shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/lighting.frag"), "lighting"); - var diffuse = TextureService.Instance.LoadTexture(PathExtensions.GetAssemblyPath("Textures/DefaultTextures/debug.JPG")); + // TODO: Using lightning shader here will most likely break stuff, it needs to be refactored out. + var shader = ShaderService.Instance.LoadShader(_Resources.Default.VertexShader, _Resources.Default.FragmentShader, "lighting"); + var diffuse = TextureService.Instance.LoadTexture(_Resources.Default.DebugTexture); Material = new(shader, diffuse); BoundingBox = BoundingBox.CalculateBoundingBox(Transform); @@ -38,16 +38,12 @@ public GameObject() /// The file path of the specular map texture. /// The file path of the vertex shader. /// The file path of the fragment shader. - public GameObject(string diffuseMapFile, string specularMapFile, string vertShaderFile, string fragShaderFile) + public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) { - var diffuse = TextureService.Instance.LoadTexture(diffuseMapFile); - var specularMap = TextureService.Instance.LoadTexture(specularMapFile); - var shader = ShaderService.Instance.LoadShader(vertShaderFile, fragShaderFile, "lighting"); - - Material = new Material(shader, diffuse) - { - SpecularMap = specularMap - }; + var shader = ShaderService.Instance.LoadShader(vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader, "lighting"); + var diffuse = TextureService.Instance.LoadTexture(diffuseMapFile ?? _Resources.Default.DebugTexture); + var specular = string.IsNullOrEmpty(specularMapFile) ? null : TextureService.Instance.LoadTexture(specularMapFile!); + Material = new Material(shader, diffuse, specular); BoundingBox = BoundingBox.CalculateBoundingBox(Transform); } @@ -90,15 +86,21 @@ public override Task Render() Material.DiffuseMap.Use(TextureUnit.Texture0); Material.Shader.SetInt("material.diffuse", Material.diffuseUnit); - if (Material.SpecularMap is not null) + if (Material.UseSpecularMap) { Material.SpecularMap.Use(TextureUnit.Texture1); Material.Shader.SetInt("material.specular", Material.specularUnit); Material.Shader.SetVector3("material.specular", Material.Specular); Material.Shader.SetFloat("material.shininess", Material.Shininess); } + else + { + Material.Shader.SetInt("material.specular", 0); + Material.Shader.SetVector3("material.specular", new System.Numerics.Vector3(0)); + Material.Shader.SetFloat("material.shininess", 0); + } - Material.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); + Material.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); Window.GL.DrawArrays(PrimitiveType.Triangles, 0, 36); return Task.CompletedTask; diff --git a/Core/Entities/Properties/Material.cs b/Core/Entities/Properties/Material.cs index b30f02b..b744d51 100644 --- a/Core/Entities/Properties/Material.cs +++ b/Core/Entities/Properties/Material.cs @@ -12,10 +12,11 @@ public class Material /// /// Initializes a new instance of . /// - public Material(Shader shader, Texture diffuseMap) + public Material(Shader shader, Texture diffuseMap, Texture? specularMap = null) { Shader = shader; DiffuseMap = diffuseMap; + SpecularMap = specularMap ?? diffuseMap; Specular = new(0.5f, 0.5f, 0.5f); Shininess = 32.0f; @@ -29,7 +30,9 @@ public Material(Shader shader, Texture diffuseMap) /// /// Gets or sets the specular map texture. /// - public Texture? SpecularMap { get; set; } + public Texture SpecularMap { get; set; } + + public bool UseSpecularMap => SpecularMap.Handle != DiffuseMap.Handle; /// /// Gets or sets the shader used to render the material. diff --git a/Core/Primitives/PrimitiveFactory.cs b/Core/Primitives/PrimitiveFactory.cs index 9a63da5..497861e 100644 --- a/Core/Primitives/PrimitiveFactory.cs +++ b/Core/Primitives/PrimitiveFactory.cs @@ -30,7 +30,7 @@ public static GameObject Create(PrimitiveType primitiveType, Vector3 position) /// A new game object. /// Thrown when the specified primitive type does not exist. public static GameObject Create(PrimitiveType primitiveType, Vector3 position, string diffuseMapFile, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) - => new(diffuseMapFile, specularMapFile ?? _Resources.Default.DebugTexture, vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader) + => new(diffuseMapFile, specularMapFile, vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader) { Transform = new Transform { Position = position }, Mesh = primitiveType switch From 93c02b6302143b6d5773ea27642b62884bb4bbed Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 30 Mar 2025 00:22:01 +0200 Subject: [PATCH 11/28] Add basic layout placeholders --- Core/Entities/UI/Layouts/FlexLayout.cs | 21 ++++++++ Core/Entities/UI/Layouts/GridLayout.cs | 72 ++++++++++++++++++++++++++ Core/Entities/UI/Layouts/LayoutBase.cs | 24 +++++++++ 3 files changed, 117 insertions(+) create mode 100644 Core/Entities/UI/Layouts/FlexLayout.cs create mode 100644 Core/Entities/UI/Layouts/GridLayout.cs create mode 100644 Core/Entities/UI/Layouts/LayoutBase.cs diff --git a/Core/Entities/UI/Layouts/FlexLayout.cs b/Core/Entities/UI/Layouts/FlexLayout.cs new file mode 100644 index 0000000..62f9528 --- /dev/null +++ b/Core/Entities/UI/Layouts/FlexLayout.cs @@ -0,0 +1,21 @@ +using System; +using System.Numerics; + +namespace SharpEngine.Core.Entities.UI.Layouts; + +/// +/// Represents a layout that dynamically distributes items in a flex layout. +/// +/// The type of items in the collection. +public class FlexLayout : LayoutBase +{ + public int Gap { get; set; } + + public int ContentSize { get; set; } + + public Vector2 ContentAreaSize { get; set; } + + /// + /// + public override T[][] GetValues() => throw new NotImplementedException(); +} diff --git a/Core/Entities/UI/Layouts/GridLayout.cs b/Core/Entities/UI/Layouts/GridLayout.cs new file mode 100644 index 0000000..cc0cde2 --- /dev/null +++ b/Core/Entities/UI/Layouts/GridLayout.cs @@ -0,0 +1,72 @@ +using System; + +namespace SharpEngine.Core.Entities.UI.Layouts; + +/// +/// Represents a grid layout. +/// +/// The type of items that can be stored and retrieved within the grid. +public class GridLayout : LayoutBase +{ + /// Gets or sets the amount of rows in the grid. + public uint Rows { get; set; } = 2; + + /// Gets or sets the amount of columns in the grid. + public uint Columns { get; set; } = 3; + + /// + /// Retrieves the item at [, ]. + /// + /// The row where the items should be retrieved. + /// The column where the item should be retrieved. + /// The item at [, ]. + public T this[uint row, uint column] + { + get + { + uint index = GetIndex(row, column); + return Items[(int)index]; + } + set + { + uint index = GetIndex(row, column); + Items[(int)index] = value; + } + } + + /// + /// Calculates the index in the flat list based on row and column. + /// + /// The row where the items should be retrieved. + /// The column where the item should be retrieved. + /// The index of the item at [, ]. + /// + private uint GetIndex(uint row, uint column) + { + if (row < 0 || column < 0 || column >= Columns) + throw new ArgumentOutOfRangeException(nameof(row), "Invalid row or column index."); + + var index = row * Columns + column; + if (index >= Items.Count) + throw new ArgumentOutOfRangeException(nameof(row), "Index exceeds the number of items."); + + return index; + } + + /// + public override T[][] GetValues() + { + var values = new T[Rows][]; + for (uint i = 0; i < Rows; i++) + { + values[i] = new T[Columns]; + for (uint j = 0; j < Columns; j++) + { + var item = this[i, j]; + values[i][j] = item; + } + } + + return values; + } +} diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs new file mode 100644 index 0000000..be69994 --- /dev/null +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Entities.UI.Layouts; + +public abstract class LayoutBase +{ + public List Items; + + public virtual void AddItem(T item) + { + Items.Add(item); + } + + public virtual bool RemoveItem(T item) + { + return Items.Remove(item); + } + + public abstract T[][] GetValues(); +} From d21d76a7610c6328f63b360f6e60ca1323c30cee Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 30 Mar 2025 00:22:18 +0200 Subject: [PATCH 12/28] UI shader testing (WIP) --- Core/Entities/Properties/Transform2D.cs | 14 +++++++------- Core/Entities/{ => UI}/UIElement.cs | 13 +++++++------ Core/Renderers/Renderer.cs | 1 + Core/Renderers/UIRenderer.cs | 8 ++++++-- Core/Scenes/Scene.cs | 2 +- Core/Shaders/Shader.cs | 21 ++++++++++++++++----- Core/Shaders/UIShader.cs | 12 ++---------- Core/SharpEngine.Core.csproj | 2 +- Examples/MinecraftClone/Minecraft.cs | 1 + 9 files changed, 42 insertions(+), 32 deletions(-) rename Core/Entities/{ => UI}/UIElement.cs (87%) diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index cbcbbee..7c6ea06 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -5,27 +5,27 @@ namespace SharpEngine.Core.Entities.Properties; /// /// Represents a game object transformation in 2D space. /// -public class Transform2D +public class Transform2D : Transform { /// /// Gets or sets the position of the game object. /// - public Vector2 Position { get; set; } + public new Vector2 Position { get; set; } /// /// Gets or sets the scale of the game object. /// - public Vector2 Scale { get; set; } = new(1, 1); + public new Vector2 Scale { get; set; } = new(1, 1); /// /// Gets or sets the rotation of the game object. /// - public float Rotation { get; set; } + public new float Rotation { get; set; } /// /// Gets the transformation of the game object as a model matrix. /// - public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * - Matrix4x4.CreateRotationZ(Rotation) * - Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + public new Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * + Matrix4x4.CreateRotationZ(Rotation) * + Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); } \ No newline at end of file diff --git a/Core/Entities/UIElement.cs b/Core/Entities/UI/UIElement.cs similarity index 87% rename from Core/Entities/UIElement.cs rename to Core/Entities/UI/UIElement.cs index 592180e..f45df13 100644 --- a/Core/Entities/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -7,12 +7,12 @@ using Silk.NET.OpenGL; using System.Threading.Tasks; -namespace SharpEngine.Core.Entities; +namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : SceneNode +public class UIElement : GameObject { /// /// Initializes a new instance of . @@ -27,14 +27,15 @@ public UIElement(string name) private readonly UIShader _uIShader = new(); /// Gets or sets the 2D space transformation of the UI element. - public Transform2D Transform { get; set; } = new() + public new Transform2D Transform { get; set; } = new() { - //Rotation = 180 - Scale = new System.Numerics.Vector2(0.5f, 0.5f) + //Rotation = 180, + Scale = new System.Numerics.Vector2(0.5f, 0.5f), + //Position = new System.Numerics.Vector2(1, 0), }; /// Gets or sets the mesh of the UI element. - public Mesh Mesh { get; set; } + public new Mesh Mesh { get; set; } private uint _vertexArrayObject; diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index 5fd0a5d..0e80c7d 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -49,6 +49,7 @@ public Renderer(CameraView camera, ISettings settings, Scene scene) : base(setti /// public override Task Render() { + return Task.CompletedTask; try { Window.GL.Enable(EnableCap.DepthTest); diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 4ae061d..4c5ca18 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -1,4 +1,4 @@ -using SharpEngine.Core.Entities; +using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; @@ -18,6 +18,7 @@ public class UIRenderer : RendererBase { private readonly Scene _scene; private readonly UIShader _uiShader; + private readonly CameraView _camera; /// public override RenderFlags RenderFlag => RenderFlags.UIRenderer; @@ -25,10 +26,11 @@ public class UIRenderer : RendererBase /// /// Initializes a new instance of . /// - public UIRenderer(CameraView _, ISettings settings, Scene scene) : base(settings) + public UIRenderer(CameraView camera, ISettings settings, Scene scene) : base(settings) { _scene = scene; _uiShader = new UIShader(); + _camera = camera; } /// @@ -62,6 +64,8 @@ public override Task Render() Window.GL.Disable(EnableCap.DepthTest); Window.GL.DepthFunc(DepthFunction.Less); + _camera.SetShaderUniforms(_uiShader.Shader!); + foreach (var item in _scene.UIElements) { item.Bind(); diff --git a/Core/Scenes/Scene.cs b/Core/Scenes/Scene.cs index 0370af4..852f9dd 100644 --- a/Core/Scenes/Scene.cs +++ b/Core/Scenes/Scene.cs @@ -1,5 +1,5 @@ using SharpEngine.Core.Entities; - +using SharpEngine.Core.Entities.UI; using System; using System.Collections.Generic; using System.Linq; diff --git a/Core/Shaders/Shader.cs b/Core/Shaders/Shader.cs index 1ea1e8d..259d0bd 100644 --- a/Core/Shaders/Shader.cs +++ b/Core/Shaders/Shader.cs @@ -88,7 +88,6 @@ public Shader(string vertPath, string fragPath, string name) // The shader is now ready to go, but first, we're going to cache all the shader uniform locations. // Querying this from the shader is very slow, so we do it once on initialization and reuse those values // later. - GetUniformLocations(); } @@ -219,6 +218,18 @@ public bool TryGetAttribLocation(string attribName, out int location) // 2. Get a handle to the location of the uniform with GL.GetUniformLocation. // 3. Use the appropriate GL.Uniform* function to set the uniform. + private bool TrySetUniform(string name, T data, Action setter) + { + if (!_uniformLocations.TryGetValue(name, out int uniform)) + { + Console.WriteLine($"Uniform '{name}' not found in shader program."); + return false; + } + + setter(uniform, data); + return true; + } + /// /// Set a uniform int on this shader. /// @@ -227,7 +238,7 @@ public bool TryGetAttribLocation(string attribName, out int location) public void SetInt(string name, int data) { Window.GL.UseProgram(Handle); - Window.GL.Uniform1(_uniformLocations[name], data); + TrySetUniform(name, data, Window.GL.Uniform1); } /// @@ -238,7 +249,7 @@ public void SetInt(string name, int data) public void SetFloat(string name, float data) { Window.GL.UseProgram(Handle); - Window.GL.Uniform1(_uniformLocations[name], data); + TrySetUniform(name, data, Window.GL.Uniform1); } /// @@ -255,7 +266,7 @@ public void SetFloat(string name, float data) public void SetMatrix4(string name, Matrix4x4 data, bool transpose = true) { Window.GL.UseProgram(Handle); - Window.GL.UniformMatrix4(_uniformLocations[name], transpose, data.ToSpan()); + TrySetUniform(name, data, (uniform, d) => Window.GL.UniformMatrix4(uniform, transpose, d.ToSpan())); } /// @@ -266,6 +277,6 @@ public void SetMatrix4(string name, Matrix4x4 data, bool transpose = true) public void SetVector3(string name, Vector3 data) { Window.GL.UseProgram(Handle); - Window.GL.Uniform3(_uniformLocations[name], data); + TrySetUniform(name, data, Window.GL.Uniform3); } } diff --git a/Core/Shaders/UIShader.cs b/Core/Shaders/UIShader.cs index 99c7b4a..4c5e675 100644 --- a/Core/Shaders/UIShader.cs +++ b/Core/Shaders/UIShader.cs @@ -12,7 +12,7 @@ internal class UIShader : ShaderBase /// public UIShader() { - Shader = new Shader(PathExtensions.GetAssemblyPath("Shaders/uiShader.vert"), PathExtensions.GetAssemblyPath("Shaders/uiShader.frag"), "ui"); + Shader = new Shader(PathExtensions.GetAssemblyPath("Shaders\\uiShader.vert"), PathExtensions.GetAssemblyPath("Shaders\\uiShader.frag"), "ui"); } /// @@ -21,14 +21,7 @@ public override bool SetAttributes() if (!base.SetAttributes()) return false; - if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int vertexLocation)) - return false; - - uint vertexLocationUInt = (uint)vertexLocation; - Window.GL.EnableVertexAttribArray(vertexLocationUInt); - Window.GL.VertexAttribPointer(vertexLocationUInt, 3, VertexAttribPointerType.Float, false, 8 * sizeof(float), 0); - - /*if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation)) + if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation)) return false; var positionLocationUint = (uint)positionLocation; @@ -48,7 +41,6 @@ public override bool SetAttributes() var texCoordLocationUint = (uint)texCoordLocation; Window.GL.EnableVertexAttribArray(texCoordLocationUint); Window.GL.VertexAttribPointer(texCoordLocationUint, VertexData.TexCoordsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.TexCoordsOffset); - */ return true; } diff --git a/Core/SharpEngine.Core.csproj b/Core/SharpEngine.Core.csproj index 5ec1e72..2fda018 100644 --- a/Core/SharpEngine.Core.csproj +++ b/Core/SharpEngine.Core.csproj @@ -41,7 +41,7 @@ - + Always diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index eb102c9..efb3807 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -10,6 +10,7 @@ using System; using System.Numerics; +using SharpEngine.Core.Entities.UI; namespace Minecraft; From 1f50cff5b92e6859c55ead7b0618b49614658bfc Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 30 Mar 2025 01:03:01 +0200 Subject: [PATCH 13/28] UI rendering works --- Core/Entities/Properties/Transform2D.cs | 2 +- Core/Entities/UI/UIElement.cs | 18 ++++++++++---- Core/Renderers/UIRenderer.cs | 4 +--- Core/Shaders/Shader.cs | 17 +++++++++----- Core/Shaders/uiShader.frag | 9 ++++--- Core/Shaders/uiShader.vert | 31 +++++++++++++++---------- 6 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index 7c6ea06..f23f1ee 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -27,5 +27,5 @@ public class Transform2D : Transform /// public new Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * Matrix4x4.CreateRotationZ(Rotation) * - Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + Matrix4x4.CreateTranslation(new Vector3(Position.X * 0.01f, Position.Y * 0.01f, 0)); } \ No newline at end of file diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index f45df13..45576ed 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -29,9 +29,9 @@ public UIElement(string name) /// Gets or sets the 2D space transformation of the UI element. public new Transform2D Transform { get; set; } = new() { - //Rotation = 180, + Rotation = 50, Scale = new System.Numerics.Vector2(0.5f, 0.5f), - //Position = new System.Numerics.Vector2(1, 0), + Position = new System.Numerics.Vector2(0, 20), }; /// Gets or sets the mesh of the UI element. @@ -73,9 +73,17 @@ private void InitializeBuffers() public override Task Render() { Window.GL.BindVertexArray(_vertexArrayObject); - - _uIShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); - + + // Extract position, rotation, and scale from Transform + var position = Transform.Position; + var rotation = Transform.Rotation; + var scale = Transform.Scale; + + // Set the position, rotation, and scale uniforms in the shader + _uIShader.Shader.SetVector2("position", position); + _uIShader.Shader.SetFloat("rotation", rotation); + _uIShader.Shader.SetVector2("scale", scale); + Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); return Task.CompletedTask; diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 4c5ca18..212ab19 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -61,11 +61,9 @@ public override Task Render() { try { - Window.GL.Disable(EnableCap.DepthTest); + Window.GL.Enable(EnableCap.DepthTest); Window.GL.DepthFunc(DepthFunction.Less); - _camera.SetShaderUniforms(_uiShader.Shader!); - foreach (var item in _scene.UIElements) { item.Bind(); diff --git a/Core/Shaders/Shader.cs b/Core/Shaders/Shader.cs index 259d0bd..1b7b160 100644 --- a/Core/Shaders/Shader.cs +++ b/Core/Shaders/Shader.cs @@ -1,12 +1,11 @@ +using SharpEngine.Core.Windowing; +using Silk.NET.OpenGL; using System; -using System.IO; using System.Collections.Generic; - -using System.Text.RegularExpressions; +using System.IO; using System.Numerics; - -using Silk.NET.OpenGL; -using SharpEngine.Core.Windowing; +using System.Text.RegularExpressions; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace SharpEngine.Core.Shaders; @@ -279,4 +278,10 @@ public void SetVector3(string name, Vector3 data) Window.GL.UseProgram(Handle); TrySetUniform(name, data, Window.GL.Uniform3); } + + public void SetVector2(string name, Vector2 data) + { + Window.GL.UseProgram(Handle); + TrySetUniform(name, data, Window.GL.Uniform2); + } } diff --git a/Core/Shaders/uiShader.frag b/Core/Shaders/uiShader.frag index 36a7e45..8e4b912 100644 --- a/Core/Shaders/uiShader.frag +++ b/Core/Shaders/uiShader.frag @@ -1,12 +1,11 @@ #version 330 core -in vec2 TexCoord; -out vec4 FragColor; - uniform sampler2D texture1; +out vec4 FragColor; +in vec2 TexCoords; void main() { - FragColor = vec4(1.0, 0.0, 0.0, 1.0); // set all 4 vector values to 1.0 - // FragColor = texture(texture1, TexCoord); + // FragColor = vec4(1.0, 0.0, 0.0, 1.0); // set all 4 vector values to 1.0 + FragColor = texture(texture1, TexCoords); } \ No newline at end of file diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index 8d97e16..707a1f7 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -1,23 +1,30 @@ #version 330 core layout (location = 0) in vec3 aPos; -// layout (location = 1) in vec3 aNormal; -// layout (location = 2) in vec2 aTexCoord; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; -uniform mat4 model; -// uniform mat4 view; -// uniform mat4 projection; +uniform vec2 position; +uniform float rotation; +uniform vec2 scale; +out vec3 Normal; out vec3 FragPos; +out vec2 TexCoords; void main() { - vec4 pos = vec4(aPos, 1.0); - gl_Position = model * pos; + // Create a 2D rotation matrix + mat2 rot = mat2(cos(rotation), -sin(rotation), + sin(rotation), cos(rotation)); - // gl_Position = vec4(aPos, 1.0) * model * view * projection; - // FragPos = vec3(vec4(aPos, 1.0) * model); - // TexCoord = aTexCoord; - // Normal = aNormal * mat3(transpose(inverse(model))); - // TexCoords = aTexCoords; + // Apply the rotation, scaling, and translation + vec2 transformedPos = (rot * aPos.xy) * scale + (position * 0.01); + + // Set the final position in clip space + gl_Position = vec4(transformedPos, aPos.z, 1.0); + + FragPos = vec3(transformedPos, aPos.z); + Normal = aNormal; // No need to transform normals for 2D UI elements + TexCoords = aTexCoords; } \ No newline at end of file From abafdd9f9fac0134265e7fc99e116d7396c47426 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 30 Mar 2025 01:39:02 +0200 Subject: [PATCH 14/28] Actual fix --- Core/Entities/UI/UIElement.cs | 26 ++++++++++---------------- Core/Renderers/UIRenderer.cs | 23 ----------------------- Examples/MinecraftClone/Minecraft.cs | 17 +++++++---------- 3 files changed, 17 insertions(+), 49 deletions(-) diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 45576ed..7a17eff 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -22,17 +22,14 @@ public UIElement(string name) { Name = name; Mesh = MeshService.Instance.LoadMesh(nameof(Primitives.Plane), Primitives.Plane.Mesh); + + Initialize(); } - private readonly UIShader _uIShader = new(); + private readonly UIShader _uiShader = new(); /// Gets or sets the 2D space transformation of the UI element. - public new Transform2D Transform { get; set; } = new() - { - Rotation = 50, - Scale = new System.Numerics.Vector2(0.5f, 0.5f), - Position = new System.Numerics.Vector2(0, 20), - }; + public new Transform2D Transform { get; set; } = new(); /// Gets or sets the mesh of the UI element. public new Mesh Mesh { get; set; } @@ -46,6 +43,9 @@ public void Initialize() Bind(); InitializeBuffers(); + + _uiShader.Shader.Use(); + _uiShader.SetAttributes(); } /// @@ -74,15 +74,9 @@ public override Task Render() { Window.GL.BindVertexArray(_vertexArrayObject); - // Extract position, rotation, and scale from Transform - var position = Transform.Position; - var rotation = Transform.Rotation; - var scale = Transform.Scale; - - // Set the position, rotation, and scale uniforms in the shader - _uIShader.Shader.SetVector2("position", position); - _uIShader.Shader.SetFloat("rotation", rotation); - _uIShader.Shader.SetVector2("scale", scale); + _uiShader.Shader.SetVector2("position", Transform.Position); + _uiShader.Shader.SetFloat("rotation", Transform.Rotation); + _uiShader.Shader.SetVector2("scale", Transform.Scale); Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 212ab19..ffa8f8a 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -33,29 +33,6 @@ public UIRenderer(CameraView camera, ISettings settings, Scene scene) : base(set _camera = camera; } - /// - public override void Initialize() - { - if (_uiShader.Shader is null) - { - Console.WriteLine("The UI shader is null. This is broken mostly when the exe is started outside the solution. This needs to be fixed later."); - return; - } - - _uiShader.Shader.Use(); - - _scene.UIElements.Add(new UIElement("uiElement")); - - _scene.Iterate(_scene.UIElements, elem => elem.Initialize()); - - InitializeVertexArrays(); - } - - private void InitializeVertexArrays() - { - _uiShader.SetAttributes(); - } - /// public override Task Render() { diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index efb3807..b173bea 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -29,7 +29,7 @@ public class Minecraft : Game private Input _input; private readonly Inventory _inventory; - private UIElement uiElem; + private UIElement _uiElem; /// /// Initializes a new instance of the . @@ -56,8 +56,10 @@ public override void Initialize() _blocksNode = _scene.Root.AddChild("blocks"); // TODO: Fix UI renderer - uiElem = new UIElement("uiElement"); - _scene.UIElements.Add(uiElem); + _uiElem = new UIElement("uiElement"); + _uiElem.Transform.Scale = new Vector2(0.5f, 0.5f); + + _scene.UIElements.Add(_uiElem); InitializeWorld(); } @@ -90,11 +92,6 @@ private void InitializeLights() Diffuse = new Vector3(1.0f, 1.0f, 1.0f), Specular = new Vector3(1.0f, 1.0f, 1.0f), }); - - // var _lightingShader = ShaderService.Instance.LoadShader("Shaders/shader.vert", "Shaders/lighting.frag", "lighting"); - // _lightingShader.SetInt("numDirLights", 1); - // _lightingShader.SetInt("numPointLights", 4); - // _lightingShader.SetInt("numSpotLights", 1); } private void InitializeChunks() @@ -137,12 +134,12 @@ private void GenerateChunk(int chunkSize, Vector3 chunkPos) /// public override void Update(double deltaTime, IInputContext input) { - // UpdateUI(); + UpdateUI(); _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime); } private void UpdateUI() - => uiElem.Transform.Rotation += 0.01f; + => _uiElem.Transform.Rotation += 0.01f; // TODO: Input system to let users change change key bindings? /// From 95fbe33e0d5487cc3c87b70533df7cc2b86e9f0e Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 30 Mar 2025 16:14:49 +0300 Subject: [PATCH 15/28] Generalize functions --- Core/Entities/UI/Layouts/FlexLayout.cs | 5 +++-- Core/Entities/UI/Layouts/GridLayout.cs | 17 +++++++++++++++-- Core/Entities/UI/Layouts/LayoutBase.cs | 17 ++++++++++++++--- Core/Ray.cs | 2 +- Core/Renderers/Renderer.cs | 2 +- Core/Renderers/UIRenderer.cs | 8 +++++--- Core/Scenes/Scene.cs | 23 ++++++++++++----------- Core/Windowing/Window.cs | 4 +++- Examples/MinecraftClone/Minecraft.cs | 20 +++++++++++++++++--- Examples/MinecraftClone/Program.cs | 6 +++++- 10 files changed, 76 insertions(+), 28 deletions(-) diff --git a/Core/Entities/UI/Layouts/FlexLayout.cs b/Core/Entities/UI/Layouts/FlexLayout.cs index 62f9528..6768311 100644 --- a/Core/Entities/UI/Layouts/FlexLayout.cs +++ b/Core/Entities/UI/Layouts/FlexLayout.cs @@ -1,4 +1,5 @@ -using System; +using SharpEngine.Core.Scenes; +using System; using System.Numerics; namespace SharpEngine.Core.Entities.UI.Layouts; @@ -7,7 +8,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a layout that dynamically distributes items in a flex layout. /// /// The type of items in the collection. -public class FlexLayout : LayoutBase +public class FlexLayout : LayoutBase where T : SceneNode { public int Gap { get; set; } diff --git a/Core/Entities/UI/Layouts/GridLayout.cs b/Core/Entities/UI/Layouts/GridLayout.cs index cc0cde2..365323e 100644 --- a/Core/Entities/UI/Layouts/GridLayout.cs +++ b/Core/Entities/UI/Layouts/GridLayout.cs @@ -1,4 +1,5 @@ -using System; +using SharpEngine.Core.Scenes; +using System; namespace SharpEngine.Core.Entities.UI.Layouts; @@ -6,7 +7,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a grid layout. /// /// The type of items that can be stored and retrieved within the grid. -public class GridLayout : LayoutBase +public class GridLayout : LayoutBase where T : GameObject { /// Gets or sets the amount of rows in the grid. public uint Rows { get; set; } = 2; @@ -34,6 +35,18 @@ public class GridLayout : LayoutBase } } + public override void AddItem(T item) + { + base.AddItem(item); + + var offset = -100; // TODO: Fix this, this is just a temporary solution. + + if (Items.Count % Columns != 0) + offset = offset + (Items.Count * 30); + + item.Transform.Position = new System.Numerics.Vector3(offset, 0, 0); + } + /// /// Calculates the index in the flat list based on row and column. /// diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index be69994..d8ec1cc 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -1,4 +1,5 @@ -using System; +using SharpEngine.Core.Scenes; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,9 +7,19 @@ namespace SharpEngine.Core.Entities.UI.Layouts; -public abstract class LayoutBase +public abstract class LayoutBase : SceneNode where T : SceneNode { - public List Items; + public List Items { get; set; } = []; + + public override SceneNode AddChild(params SceneNode[] nodes) + { + base.AddChild(nodes); + + foreach (var node in nodes) + AddItem((T)node); + + return this; + } public virtual void AddItem(T item) { diff --git a/Core/Ray.cs b/Core/Ray.cs index 42bae6e..942346b 100644 --- a/Core/Ray.cs +++ b/Core/Ray.cs @@ -43,7 +43,7 @@ public bool IsGameObjectInView(Scene scene, out GameObject? intersectingObject, const float maxDistance = 100.0f; // Maximum distance to check for intersections const float stepSize = 0.1f; // Step size for ray marching - var sceneNodes = scene.GetAllGameObjects(); + var sceneNodes = scene.GetObjectsOfType(); if (allowedTypes.Length != 0) sceneNodes = [.. sceneNodes.Where(go => allowedTypes.Any(type => type.IsInstanceOfType(go)))]; diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index 0e80c7d..a6f0401 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -49,7 +49,7 @@ public Renderer(CameraView camera, ISettings settings, Scene scene) : base(setti /// public override Task Render() { - return Task.CompletedTask; + //return Task.CompletedTask; try { Window.GL.Enable(EnableCap.DepthTest); diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index ffa8f8a..55513ef 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -7,6 +7,7 @@ using Silk.NET.OpenGL; using System; +using System.Linq; using System.Threading.Tasks; namespace SharpEngine.Core.Renderers; @@ -41,13 +42,14 @@ public override Task Render() Window.GL.Enable(EnableCap.DepthTest); Window.GL.DepthFunc(DepthFunction.Less); - foreach (var item in _scene.UIElements) + _uiShader.Shader.Use(); + + var objects = _scene.GetObjectsOfType(); // TODO: This is really unoptimized, need to figure out a better solution. + foreach (var item in objects) { item.Bind(); } - _uiShader.Shader.Use(); - var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render()); return Task.WhenAll(uiElementRenderTasks); diff --git a/Core/Scenes/Scene.cs b/Core/Scenes/Scene.cs index 852f9dd..3f46da7 100644 --- a/Core/Scenes/Scene.cs +++ b/Core/Scenes/Scene.cs @@ -47,7 +47,7 @@ public Scene(string sceneFile) private List Nodes { get; set; } = []; /// Gets or sets the UI elements in the scene. - public List UIElements { get; private set; } = []; + public List UIElements { get; private set; } = []; /// Gets or sets the active element in the scene. /// Editor only. @@ -104,26 +104,27 @@ public virtual void RemoveNode(SceneNode node) } /// - /// Gets all game objects in the scene. + /// Gets all the objects in the scene of type . /// + /// The type of the objects to be retrieved. /// All the game objects in the current scene. - public List GetAllGameObjects() + public List GetObjectsOfType(SceneNode? root = null) { // TODO: Can we make this async? - var gameObjects = new List(); - static void FindGameObjects(SceneNode node, List gameObjects) + var result = new List(); + static void FindsObjects(SceneNode node, List result) { - if (node is GameObject gameObject) - gameObjects.Add(gameObject); + if (node is T obj) + result.Add(obj); foreach (var child in node.Children) - FindGameObjects(child, gameObjects); + FindsObjects(child, result); } - foreach (var node in Root.Children) - FindGameObjects(node, gameObjects); + foreach (var node in root?.Children ?? Root.Children) + FindsObjects(node, result); - return gameObjects; + return result; } /// diff --git a/Core/Windowing/Window.cs b/Core/Windowing/Window.cs index f4ebdee..85f9c97 100644 --- a/Core/Windowing/Window.cs +++ b/Core/Windowing/Window.cs @@ -323,7 +323,9 @@ protected void OnMouseWheel(IMouse mouse, ScrollWheel sw) protected void OnResize(Vector2D size) { GL.Viewport(size); - Camera.AspectRatio = size.X / size.Y; + + if (size != Vector2D.Zero) + Camera.AspectRatio = size.X / size.Y; } /// diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index b173bea..11785e3 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -11,6 +11,8 @@ using System; using System.Numerics; using SharpEngine.Core.Entities.UI; +using SharpEngine.Core.Windowing; +using SharpEngine.Core.Entities.UI.Layouts; namespace Minecraft; @@ -31,6 +33,8 @@ public class Minecraft : Game private UIElement _uiElem; + public Window Window { get; set; } + /// /// Initializes a new instance of the . /// @@ -55,12 +59,22 @@ public override void Initialize() _lightsNode = _scene.Root.AddChild("lights"); _blocksNode = _scene.Root.AddChild("blocks"); + var gridLayout = new GridLayout(); + // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - _uiElem.Transform.Scale = new Vector2(0.5f, 0.5f); - + _uiElem.Transform.Scale = new Vector2(0.2f, 0.2f); + //_uiElem.Transform.Position = new Vector2(-30, 0); _scene.UIElements.Add(_uiElem); + var uiElem2 = new UIElement("uiElement"); + uiElem2.Transform.Scale = new Vector2(0.2f, 0.2f); + //uiElem2.Transform.Position = new Vector2(30, 0); + + //gridLayout.AddChild(_uiElem, uiElem2); + + _scene.UIElements.Add(gridLayout); + InitializeWorld(); } catch (Exception ex) @@ -134,7 +148,7 @@ private void GenerateChunk(int chunkSize, Vector3 chunkPos) /// public override void Update(double deltaTime, IInputContext input) { - UpdateUI(); + //UpdateUI(); _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime); } diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs index 481bbbb..a753b62 100644 --- a/Examples/MinecraftClone/Program.cs +++ b/Examples/MinecraftClone/Program.cs @@ -19,12 +19,16 @@ private static void Main() Minecraft game = new Minecraft(scene, gameSettings); using var window = new SharpEngine.Core.Windowing.Window(game.Camera, scene, game.Camera.Settings); - window.OnLoaded += () => game.Initialize(); + window.OnLoaded += game.Initialize; window.OnHandleMouse += game.HandleMouse; window.OnUpdate += game.Update; window.OnHandleKeyboard += game.HandleKeyboard; window.OnButtonMouseDown += game.HandleMouseDown; window.HandleMouseWheel += game.HandleMouseWheel; + + // TODO: This needs to be streamlined. + game.Window = window; + window.Run(); } } From 90e9a872dcbdb5c25cd94e1abd0467efb15f7714 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Fri, 4 Apr 2025 00:41:46 +0300 Subject: [PATCH 16/28] Fiddle with Transforms and types (WIP) --- Core/Entities/BezierCurve.cs | 54 +++ Core/Entities/GameObject.cs | 8 +- Core/Entities/Properties/ITransform.cs | 11 + Core/Entities/Properties/Transform.cs | 16 +- Core/Entities/Properties/Transform2D.cs | 13 +- Core/Entities/UI/Layouts/GridLayout.cs | 9 +- Core/Entities/UI/Layouts/LayoutBase.cs | 7 +- Core/Entities/UI/UIElement.cs | 7 +- Core/Numerics/HashCodeHelper.cs | 17 + Core/Numerics/IVector.cs | 5 + Core/Numerics/JitIntrinsicAttribute.cs | 16 + Core/Numerics/Vector2.cs | 458 +++++++++++++++++++++++ Core/Numerics/Vector2_Intrinsics.cs | 298 +++++++++++++++ Core/Numerics/Vector3.cs | 477 ++++++++++++++++++++++++ Core/Numerics/Vector3_Intrinsics.cs | 324 ++++++++++++++++ Core/Scenes/SceneNode.cs | 47 ++- Examples/MinecraftClone/Minecraft.cs | 9 +- 17 files changed, 1733 insertions(+), 43 deletions(-) create mode 100644 Core/Entities/BezierCurve.cs create mode 100644 Core/Entities/Properties/ITransform.cs create mode 100644 Core/Numerics/HashCodeHelper.cs create mode 100644 Core/Numerics/IVector.cs create mode 100644 Core/Numerics/JitIntrinsicAttribute.cs create mode 100644 Core/Numerics/Vector2.cs create mode 100644 Core/Numerics/Vector2_Intrinsics.cs create mode 100644 Core/Numerics/Vector3.cs create mode 100644 Core/Numerics/Vector3_Intrinsics.cs diff --git a/Core/Entities/BezierCurve.cs b/Core/Entities/BezierCurve.cs new file mode 100644 index 0000000..aba3b70 --- /dev/null +++ b/Core/Entities/BezierCurve.cs @@ -0,0 +1,54 @@ +using Silk.NET.Maths; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Entities; +public class BezierCurve +{ + private Vector3D P0, P1, P2, P3; + public Vector3D Start => P0; + public Vector3D End => P3; + + public List> Points { get; set; } + + public BezierCurve(Vector3D initializationPosition) + { + const float defaultWidth = 1; + + P0 = initializationPosition; + P1 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y + defaultWidth, initializationPosition.Z); + P2 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y - defaultWidth, initializationPosition.Z); + P3 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y, initializationPosition.Z); + + Points = GetPoints(20); + } + + public List> GetPoints(int resolution) + { + var points = new List>(); + for (var i = 0; i <= resolution; i++) + { + var t = i / (float)resolution; + points.Add(GetPointAt(t)); + } + return points; + } + + private Vector3D GetPointAt(float t) + { + var x = GetCoordinateAt(t, P0.X, P1.X, P2.X, P3.X); + var y = GetCoordinateAt(t, P0.Y, P1.Y, P2.Y, P3.Y); + var z = GetCoordinateAt(t, P0.Z, P1.Z, P2.Z, P3.Z); + return new(x, y, z); + } + + private static float GetCoordinateAt(float t, float p0, float p1, float p2, float p3) + { + float u = 1 - t; + return MathF.Pow(u, 3) * p0 + 3 * MathF.Pow(u, 2) * t * p1 + 3 * u * MathF.Pow(t, 2) * p2 + MathF.Pow(t, 3) * p3; + } +} diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 7f8b0d6..415a490 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -16,7 +16,7 @@ namespace SharpEngine.Core.Entities; /// /// Represents a game object in the scene. /// -public class GameObject : SceneNode +public class GameObject : SceneNode { /// /// Initializes a new instance of the . @@ -59,12 +59,10 @@ public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, [Inspector(DisplayInInspector = false)] public Material Material { get; set; } - private Transform _transform = new(); - /// /// Gets or sets the transform of the game object. /// - public Transform Transform + public override Transform Transform { get => _transform; set @@ -74,6 +72,8 @@ public Transform Transform } } + private Transform _transform; + /// /// Gets the bounding box of the game object. /// diff --git a/Core/Entities/Properties/ITransform.cs b/Core/Entities/Properties/ITransform.cs new file mode 100644 index 0000000..65108b0 --- /dev/null +++ b/Core/Entities/Properties/ITransform.cs @@ -0,0 +1,11 @@ +using System.Numerics; + +namespace SharpEngine.Core.Entities.Properties; + +public interface ITransform +{ + public abstract IVector Position { get; set; } + public abstract IVector Scale { get; set; } + public abstract Quaternion Rotation { get; set; } + public abstract Matrix4x4 ModelMatrix { get; } +} \ No newline at end of file diff --git a/Core/Entities/Properties/Transform.cs b/Core/Entities/Properties/Transform.cs index 50b8bea..7370aea 100644 --- a/Core/Entities/Properties/Transform.cs +++ b/Core/Entities/Properties/Transform.cs @@ -1,12 +1,20 @@ -using System.Numerics; +using Silk.NET.Maths; +using SharpEngine.Core.Entities; +using System.Numerics; + +using Vector3 = SharpEngine.Core.Numerics.Vector3; namespace SharpEngine.Core.Entities.Properties; /// /// Represents a game object transformation in 3D space. /// -public class Transform +public class Transform : ITransform { + public Transform() + { + } + /// /// Gets or sets the position of the game object. /// @@ -25,7 +33,7 @@ public class Transform /// /// Gets the transformation of the game object as a model matrix. /// - public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(Scale) * + public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new System.Numerics.Vector3(Scale.X, Scale.Y, Scale.Z)) * Matrix4x4.CreateFromAxisAngle(Rotation.Axis, Math.DegreesToRadians(Rotation.Angle)) * - Matrix4x4.CreateTranslation(Position); + Matrix4x4.CreateTranslation(new System.Numerics.Vector3(Position.X, Position.Y, Position.Z)); } diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index f23f1ee..413226c 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -1,31 +1,32 @@ using System.Numerics; +using Vector2 = SharpEngine.Core.Numerics.Vector2; namespace SharpEngine.Core.Entities.Properties; /// /// Represents a game object transformation in 2D space. /// -public class Transform2D : Transform +public class Transform2D : ITransform { /// /// Gets or sets the position of the game object. /// - public new Vector2 Position { get; set; } + public Vector2 Position { get; set; } /// /// Gets or sets the scale of the game object. /// - public new Vector2 Scale { get; set; } = new(1, 1); + public Vector2 Scale { get; set; } = new(1, 1); /// /// Gets or sets the rotation of the game object. /// - public new float Rotation { get; set; } + public Quaternion Rotation { get; set; } = new(); /// /// Gets the transformation of the game object as a model matrix. /// - public new Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * - Matrix4x4.CreateRotationZ(Rotation) * + public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * + Matrix4x4.CreateRotationZ(Rotation.Angle) * Matrix4x4.CreateTranslation(new Vector3(Position.X * 0.01f, Position.Y * 0.01f, 0)); } \ No newline at end of file diff --git a/Core/Entities/UI/Layouts/GridLayout.cs b/Core/Entities/UI/Layouts/GridLayout.cs index 365323e..20bd9b3 100644 --- a/Core/Entities/UI/Layouts/GridLayout.cs +++ b/Core/Entities/UI/Layouts/GridLayout.cs @@ -1,4 +1,6 @@ -using SharpEngine.Core.Scenes; +using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Numerics; +using SharpEngine.Core.Scenes; using System; namespace SharpEngine.Core.Entities.UI.Layouts; @@ -7,7 +9,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a grid layout. /// /// The type of items that can be stored and retrieved within the grid. -public class GridLayout : LayoutBase where T : GameObject +public class GridLayout : LayoutBase where T : SceneNode> { /// Gets or sets the amount of rows in the grid. public uint Rows { get; set; } = 2; @@ -35,6 +37,7 @@ public class GridLayout : LayoutBase where T : GameObject } } + /// public override void AddItem(T item) { base.AddItem(item); @@ -44,7 +47,7 @@ public override void AddItem(T item) if (Items.Count % Columns != 0) offset = offset + (Items.Count * 30); - item.Transform.Position = new System.Numerics.Vector3(offset, 0, 0); + item.Transform.Position.X = offset; } /// diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index d8ec1cc..e21404e 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -1,4 +1,5 @@ -using SharpEngine.Core.Scenes; +using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Scenes; using System; using System.Collections.Generic; using System.Linq; @@ -7,11 +8,11 @@ namespace SharpEngine.Core.Entities.UI.Layouts; -public abstract class LayoutBase : SceneNode where T : SceneNode +public abstract class LayoutBase : EmptyNode where T : SceneNode, new() { public List Items { get; set; } = []; - public override SceneNode AddChild(params SceneNode[] nodes) + public override SceneNode AddChild(params SceneNode[] nodes) { base.AddChild(nodes); diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 7a17eff..ec90445 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -12,7 +12,7 @@ namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : GameObject +public class UIElement : SceneNode { /// /// Initializes a new instance of . @@ -29,10 +29,11 @@ public UIElement(string name) private readonly UIShader _uiShader = new(); /// Gets or sets the 2D space transformation of the UI element. - public new Transform2D Transform { get; set; } = new(); + public override Transform2D Transform { get; set; } = new(); /// Gets or sets the mesh of the UI element. public new Mesh Mesh { get; set; } + //public override Transform2D Transform { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } private uint _vertexArrayObject; @@ -75,7 +76,7 @@ public override Task Render() Window.GL.BindVertexArray(_vertexArrayObject); _uiShader.Shader.SetVector2("position", Transform.Position); - _uiShader.Shader.SetFloat("rotation", Transform.Rotation); + _uiShader.Shader.SetFloat("rotation", Transform.Rotation.Angle); _uiShader.Shader.SetVector2("scale", Transform.Scale); Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); diff --git a/Core/Numerics/HashCodeHelper.cs b/Core/Numerics/HashCodeHelper.cs new file mode 100644 index 0000000..8731224 --- /dev/null +++ b/Core/Numerics/HashCodeHelper.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// https://github.com/microsoft/referencesource/blob/master/System.Numerics/System/Numerics/HashCodeHelper.cs + +namespace SharpEngine.Core.Numerics +{ + internal static class HashCodeHelper + { + /// + /// Combines two hash codes, useful for combining hash codes of individual vector elements + /// + internal static int CombineHashCodes(int h1, int h2) + { + return (((h1 << 5) + h1) ^ h2); + } + } +} \ No newline at end of file diff --git a/Core/Numerics/IVector.cs b/Core/Numerics/IVector.cs new file mode 100644 index 0000000..9fe8509 --- /dev/null +++ b/Core/Numerics/IVector.cs @@ -0,0 +1,5 @@ +namespace SharpEngine.Core.Numerics; + +public interface IVector +{ +} \ No newline at end of file diff --git a/Core/Numerics/JitIntrinsicAttribute.cs b/Core/Numerics/JitIntrinsicAttribute.cs new file mode 100644 index 0000000..0e53d20 --- /dev/null +++ b/Core/Numerics/JitIntrinsicAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// https://github.com/microsoft/referencesource/blob/master/System.Numerics/System/Numerics/JITIntrinsicAttribute.cs + +using System; + +namespace SharpEngine.Core.Numerics +{ + /// + /// An attribute that can be attached to JIT Intrinsic methods/properties + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + internal class JitIntrinsicAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Core/Numerics/Vector2.cs b/Core/Numerics/Vector2.cs new file mode 100644 index 0000000..7956d9f --- /dev/null +++ b/Core/Numerics/Vector2.cs @@ -0,0 +1,458 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// https://github.com/microsoft/referencesource/blob/master/System.Numerics/System/Numerics/Vector2.cs + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; + +using Vector = System.Numerics.Vector; +using Matrix4x4 = System.Numerics.Matrix4x4; +using Matrix3x2 = System.Numerics.Matrix3x2; +using Quaternion = System.Numerics.Quaternion; + +namespace SharpEngine.Core.Numerics +{ + /// + /// A structure encapsulating two single precision floating point values and provides hardware accelerated methods. + /// + public partial struct Vector2 : IVector, IEquatable, IFormattable + { + #region Public Static Properties + /// + /// Returns the vector (0,0). + /// + public static Vector2 Zero => new(); + /// + /// Returns the vector (1,1). + /// + public static Vector2 One => new Vector2(1.0f, 1.0f); + /// + /// Returns the vector (1,0). + /// + public static Vector2 UnitX { get { return new Vector2(1.0f, 0.0f); } } + /// + /// Returns the vector (0,1). + /// + public static Vector2 UnitY { get { return new Vector2(0.0f, 1.0f); } } + #endregion Public Static Properties + + #region Public instance methods + /// + /// Returns the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + int hash = this.X.GetHashCode(); + hash = HashCodeHelper.CombineHashCodes(hash, this.Y.GetHashCode()); + return hash; + } + + /// + /// Returns a boolean indicating whether the given Object is equal to this Vector2 instance. + /// + /// The Object to compare against. + /// True if the Object is equal to this Vector2; False otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (!(obj is Vector2)) + return false; + return Equals((Vector2)obj); + } + + /// + /// Returns a String representing this Vector2 instance. + /// + /// The string representation. + public override string ToString() + { + return ToString("G", CultureInfo.CurrentCulture); + } + + /// + /// Returns a String representing this Vector2 instance, using the specified format to format individual elements. + /// + /// The format of individual elements. + /// The string representation. + public string ToString(string format) + { + return ToString(format, CultureInfo.CurrentCulture); + } + + /// + /// Returns a String representing this Vector2 instance, using the specified format to format individual elements + /// and the given IFormatProvider. + /// + /// The format of individual elements. + /// The format provider to use when formatting elements. + /// The string representation. + public string ToString(string format, IFormatProvider formatProvider) + { + StringBuilder sb = new StringBuilder(); + string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; + sb.Append('<'); + sb.Append(this.X.ToString(format, formatProvider)); + sb.Append(separator); + sb.Append(' '); + sb.Append(this.Y.ToString(format, formatProvider)); + sb.Append('>'); + return sb.ToString(); + } + + /// + /// Returns the length of the vector. + /// + /// The vector's length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Length() + { + if (Vector.IsHardwareAccelerated) + { + float ls = Vector2.Dot(this, this); + return (float)MathF.Sqrt(ls); + } + else + { + float ls = X * X + Y * Y; + return MathF.Sqrt(ls); + } + } + + /// + /// Returns the length of the vector squared. This operation is cheaper than Length(). + /// + /// The vector's length squared. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float LengthSquared() + { + if (Vector.IsHardwareAccelerated) + { + return Vector2.Dot(this, this); + } + else + { + return X * X + Y * Y; + } + } + #endregion Public Instance Methods + + #region Public Static Methods + /// + /// Returns the Euclidean distance between the two given points. + /// + /// The first point. + /// The second point. + /// The distance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Distance(Vector2 value1, Vector2 value2) + { + if (Vector.IsHardwareAccelerated) + { + Vector2 difference = value1 - value2; + float ls = Vector2.Dot(difference, difference); + return (float)System.MathF.Sqrt(ls); + } + else + { + float dx = value1.X - value2.X; + float dy = value1.Y - value2.Y; + + float ls = dx * dx + dy * dy; + + return MathF.Sqrt(ls); + } + } + + /// + /// Returns the Euclidean distance squared between the two given points. + /// + /// The first point. + /// The second point. + /// The distance squared. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DistanceSquared(Vector2 value1, Vector2 value2) + { + if (Vector.IsHardwareAccelerated) + { + Vector2 difference = value1 - value2; + return Vector2.Dot(difference, difference); + } + else + { + float dx = value1.X - value2.X; + float dy = value1.Y - value2.Y; + + return dx * dx + dy * dy; + } + } + + /// + /// Returns a vector with the same direction as the given vector, but with a length of 1. + /// + /// The vector to normalize. + /// The normalized vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Normalize(Vector2 value) + { + if (Vector.IsHardwareAccelerated) + { + float length = value.Length(); + return value / length; + } + else + { + float ls = value.X * value.X + value.Y * value.Y; + float invNorm = 1.0f / MathF.Sqrt(ls); + + return new Vector2( + value.X * invNorm, + value.Y * invNorm); + } + } + + /// + /// Returns the reflection of a vector off a surface that has the specified normal. + /// + /// The source vector. + /// The normal of the surface being reflected off. + /// The reflected vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Reflect(Vector2 vector, Vector2 normal) + { + if (Vector.IsHardwareAccelerated) + { + float dot = Vector2.Dot(vector, normal); + return vector - (2 * dot * normal); + } + else + { + float dot = vector.X * normal.X + vector.Y * normal.Y; + + return new Vector2( + vector.X - 2.0f * dot * normal.X, + vector.Y - 2.0f * dot * normal.Y); + } + } + + /// + /// Restricts a vector between a min and max value. + /// + /// The source vector. + /// The minimum value. + /// The maximum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max) + { + // This compare order is very important!!! + // We must follow HLSL behavior in the case user specified min value is bigger than max value. + float x = value1.X; + x = (x > max.X) ? max.X : x; + x = (x < min.X) ? min.X : x; + + float y = value1.Y; + y = (y > max.Y) ? max.Y : y; + y = (y < min.Y) ? min.Y : y; + + return new Vector2(x, y); + } + + /// + /// Linearly interpolates between two vectors based on the given weighting. + /// + /// The first source vector. + /// The second source vector. + /// Value between 0 and 1 indicating the weight of the second source vector. + /// The interpolated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Lerp(Vector2 value1, Vector2 value2, float amount) + { + return new Vector2( + value1.X + (value2.X - value1.X) * amount, + value1.Y + (value2.Y - value1.Y) * amount); + } + + /// + /// Transforms a vector by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(Vector2 position, Matrix3x2 matrix) + { + return new Vector2( + position.X * matrix.M11 + position.Y * matrix.M21 + matrix.M31, + position.X * matrix.M12 + position.Y * matrix.M22 + matrix.M32); + } + + /// + /// Transforms a vector by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(Vector2 position, Matrix4x4 matrix) + { + return new Vector2( + position.X * matrix.M11 + position.Y * matrix.M21 + matrix.M41, + position.X * matrix.M12 + position.Y * matrix.M22 + matrix.M42); + } + + /// + /// Transforms a vector normal by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 TransformNormal(Vector2 normal, Matrix3x2 matrix) + { + return new Vector2( + normal.X * matrix.M11 + normal.Y * matrix.M21, + normal.X * matrix.M12 + normal.Y * matrix.M22); + } + + /// + /// Transforms a vector normal by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 TransformNormal(Vector2 normal, Matrix4x4 matrix) + { + return new Vector2( + normal.X * matrix.M11 + normal.Y * matrix.M21, + normal.X * matrix.M12 + normal.Y * matrix.M22); + } + + /// + /// Transforms a vector by the given Quaternion rotation value. + /// + /// The source vector to be rotated. + /// The rotation to apply. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(Vector2 value, Quaternion rotation) + { + float x2 = rotation.X + rotation.X; + float y2 = rotation.Y + rotation.Y; + float z2 = rotation.Z + rotation.Z; + + float wz2 = rotation.W * z2; + float xx2 = rotation.X * x2; + float xy2 = rotation.X * y2; + float yy2 = rotation.Y * y2; + float zz2 = rotation.Z * z2; + + return new Vector2( + value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2), + value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2)); + } + #endregion Public Static Methods + + #region Public operator methods + // all the below methods should be inlined as they are + // implemented over JIT intrinsics + + /// + /// Adds two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The summed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Add(Vector2 left, Vector2 right) + { + return left + right; + } + + /// + /// Subtracts the second vector from the first. + /// + /// The first source vector. + /// The second source vector. + /// The difference vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Subtract(Vector2 left, Vector2 right) + { + return left - right; + } + + /// + /// Multiplies two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The product vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Multiply(Vector2 left, Vector2 right) + { + return left * right; + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Multiply(Vector2 left, Single right) + { + return left * right; + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The scalar value. + /// The source vector. + /// The scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Multiply(Single left, Vector2 right) + { + return left * right; + } + + /// + /// Divides the first vector by the second. + /// + /// The first source vector. + /// The second source vector. + /// The vector resulting from the division. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Divide(Vector2 left, Vector2 right) + { + return left / right; + } + + /// + /// Divides the vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The result of the division. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Divide(Vector2 left, Single divisor) + { + return left / divisor; + } + + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Negate(Vector2 value) + { + return -value; + } + #endregion Public operator methods + } +} \ No newline at end of file diff --git a/Core/Numerics/Vector2_Intrinsics.cs b/Core/Numerics/Vector2_Intrinsics.cs new file mode 100644 index 0000000..f9c06bc --- /dev/null +++ b/Core/Numerics/Vector2_Intrinsics.cs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using SharpEngine.Core.Numerics; +using System; +using System.Runtime.CompilerServices; + +namespace SharpEngine.Core.Numerics +{ + // This file contains the definitions for all of the JIT intrinsic methods and properties that are recognized by the current x64 JIT compiler. + // The implementation defined here is used in any circumstance where the JIT fails to recognize these members as intrinsic. + // The JIT recognizes these methods and properties by name and signature: if either is changed, the JIT will no longer recognize the member. + // Some methods declared here are not strictly intrinsic, but delegate to an intrinsic method. For example, only one overload of CopyTo() + + public partial struct Vector2 + { + /// + /// The X component of the vector. + /// + public Single X; + /// + /// The Y component of the vector. + /// + public Single Y; + + #region Constructors + /// + /// Constructs a vector whose elements are all the single specified value. + /// + /// The element to fill the vector with. + [JitIntrinsic] + public Vector2(Single value) : this(value, value) { } + + /// + /// Constructs a vector with the given individual elements. + /// + /// The X component. + /// The Y component. + [JitIntrinsic] + public Vector2(Single x, Single y) + { + X = x; + Y = y; + } + #endregion Constructors + + #region Public Instance Methods + /// + /// Copies the contents of the vector into the given array. + /// + /// The destination array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(Single[] array) + { + CopyTo(array, 0); + } + + /// + /// Copies the contents of the vector into the given array, starting from the given index. + /// + /// If array is null. + /// If array is multidimensional. + /// If index is greater than end of the array or index is less than zero. + /// If number of elements in source vector is greater than those available in destination array + /// or if there are not enough elements to copy. + public void CopyTo(Single[] array, int index) + { + if (array == null) + { + // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull. + throw new NullReferenceException(); + } + if (index < 0 || index >= array.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + if ((array.Length - index) < 2) + { + throw new ArgumentException(nameof(index)); + } + array[index] = X; + array[index + 1] = Y; + } + + /// + /// Returns a boolean indicating whether the given Vector2 is equal to this Vector2 instance. + /// + /// The Vector2 to compare this instance to. + /// True if the other Vector2 is equal to this instance; False otherwise. + [JitIntrinsic] + public bool Equals(Vector2 other) + { + return this.X == other.X && this.Y == other.Y; + } + #endregion Public Instance Methods + + #region Public Static Methods + /// + /// Returns the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Dot(Vector2 value1, Vector2 value2) + { + return value1.X * value2.X + + value1.Y * value2.Y; + } + + /// + /// Returns a vector whose elements are the minimum of each of the pairs of elements in the two source vectors. + /// + /// The first source vector. + /// The second source vector. + /// The minimized vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Min(Vector2 value1, Vector2 value2) + { + return new Vector2( + (value1.X < value2.X) ? value1.X : value2.X, + (value1.Y < value2.Y) ? value1.Y : value2.Y); + } + + /// + /// Returns a vector whose elements are the maximum of each of the pairs of elements in the two source vectors + /// + /// The first source vector + /// The second source vector + /// The maximized vector + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Max(Vector2 value1, Vector2 value2) + { + return new Vector2( + (value1.X > value2.X) ? value1.X : value2.X, + (value1.Y > value2.Y) ? value1.Y : value2.Y); + } + + /// + /// Returns a vector whose elements are the absolute values of each of the source vector's elements. + /// + /// The source vector. + /// The absolute value vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Abs(Vector2 value) + { + return new Vector2(MathF.Abs(value.X), MathF.Abs(value.Y)); + } + + /// + /// Returns a vector whose elements are the square root of each of the source vector's elements. + /// + /// The source vector. + /// The square root vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 SquareRoot(Vector2 value) + { + return new Vector2((Single)MathF.Sqrt(value.X), (Single)MathF.Sqrt(value.Y)); + } + #endregion Public Static Methods + + #region Public Static Operators + /// + /// Adds two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The summed vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator +(Vector2 left, Vector2 right) + { + return new Vector2(left.X + right.X, left.Y + right.Y); + } + + /// + /// Subtracts the second vector from the first. + /// + /// The first source vector. + /// The second source vector. + /// The difference vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator -(Vector2 left, Vector2 right) + { + return new Vector2(left.X - right.X, left.Y - right.Y); + } + + /// + /// Multiplies two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The product vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator *(Vector2 left, Vector2 right) + { + return new Vector2(left.X * right.X, left.Y * right.Y); + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The scalar value. + /// The source vector. + /// The scaled vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator *(Single left, Vector2 right) + { + return new Vector2(left, left) * right; + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The scaled vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator *(Vector2 left, Single right) + { + return left * new Vector2(right, right); + } + + /// + /// Divides the first vector by the second. + /// + /// The first source vector. + /// The second source vector. + /// The vector resulting from the division. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator /(Vector2 left, Vector2 right) + { + return new Vector2(left.X / right.X, left.Y / right.Y); + } + + /// + /// Divides the vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The result of the division. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator /(Vector2 value1, float value2) + { + float invDiv = 1.0f / value2; + return new Vector2( + value1.X * invDiv, + value1.Y * invDiv); + } + + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 operator -(Vector2 value) + { + return Zero - value; + } + + /// + /// Returns a boolean indicating whether the two given vectors are equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are equal; False otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Vector2 left, Vector2 right) + { + return left.Equals(right); + } + + /// + /// Returns a boolean indicating whether the two given vectors are not equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are not equal; False if they are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Vector2 left, Vector2 right) + { + return !(left == right); + } + #endregion Public Static Operators + } +} \ No newline at end of file diff --git a/Core/Numerics/Vector3.cs b/Core/Numerics/Vector3.cs new file mode 100644 index 0000000..bb289f8 --- /dev/null +++ b/Core/Numerics/Vector3.cs @@ -0,0 +1,477 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// https://github.com/microsoft/referencesource/blob/master/System.Numerics/System/Numerics/Vector3.cs + +using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text; + +using Vector = System.Numerics.Vector; +using Matrix4x4 = System.Numerics.Matrix4x4; +using Quaternion = System.Numerics.Quaternion; + +namespace SharpEngine.Core.Numerics +{ + /// + /// A structure encapsulating three single precision floating point values and provides hardware accelerated methods. + /// + public partial struct Vector3 : IVector, IEquatable, IFormattable + { + #region Public Static Properties + /// + /// Returns the vector (0,0,0). + /// + public static Vector3 Zero { get { return new Vector3(); } } + /// + /// Returns the vector (1,1,1). + /// + public static Vector3 One { get { return new Vector3(1.0f, 1.0f, 1.0f); } } + /// + /// Returns the vector (1,0,0). + /// + public static Vector3 UnitX { get { return new Vector3(1.0f, 0.0f, 0.0f); } } + /// + /// Returns the vector (0,1,0). + /// + public static Vector3 UnitY { get { return new Vector3(0.0f, 1.0f, 0.0f); } } + /// + /// Returns the vector (0,0,1). + /// + public static Vector3 UnitZ { get { return new Vector3(0.0f, 0.0f, 1.0f); } } + #endregion Public Static Properties + + #region Public Instance Methods + + /// + /// Returns the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + int hash = this.X.GetHashCode(); + hash = HashCodeHelper.CombineHashCodes(hash, this.Y.GetHashCode()); + hash = HashCodeHelper.CombineHashCodes(hash, this.Z.GetHashCode()); + return hash; + } + + /// + /// Returns a boolean indicating whether the given Object is equal to this Vector3 instance. + /// + /// The Object to compare against. + /// True if the Object is equal to this Vector3; False otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) + { + if (!(obj is Vector3)) + return false; + return Equals((Vector3)obj); + } + + /// + /// Returns a String representing this Vector3 instance. + /// + /// The string representation. + public override string ToString() + { + return ToString("G", CultureInfo.CurrentCulture); + } + + /// + /// Returns a String representing this Vector3 instance, using the specified format to format individual elements. + /// + /// The format of individual elements. + /// The string representation. + public string ToString(string format) + { + return ToString(format, CultureInfo.CurrentCulture); + } + + /// + /// Returns a String representing this Vector3 instance, using the specified format to format individual elements + /// and the given IFormatProvider. + /// + /// The format of individual elements. + /// The format provider to use when formatting elements. + /// The string representation. + public string ToString(string format, IFormatProvider formatProvider) + { + StringBuilder sb = new StringBuilder(); + string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; + sb.Append('<'); + sb.Append(((IFormattable)this.X).ToString(format, formatProvider)); + sb.Append(separator); + sb.Append(' '); + sb.Append(((IFormattable)this.Y).ToString(format, formatProvider)); + sb.Append(separator); + sb.Append(' '); + sb.Append(((IFormattable)this.Z).ToString(format, formatProvider)); + sb.Append('>'); + return sb.ToString(); + } + + /// + /// Returns the length of the vector. + /// + /// The vector's length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float Length() + { + if (Vector.IsHardwareAccelerated) + { + float ls = Vector3.Dot(this, this); + return (float)System.Math.Sqrt(ls); + } + else + { + float ls = X * X + Y * Y + Z * Z; + return (float)System.Math.Sqrt(ls); + } + } + + /// + /// Returns the length of the vector squared. This operation is cheaper than Length(). + /// + /// The vector's length squared. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float LengthSquared() + { + if (Vector.IsHardwareAccelerated) + { + return Vector3.Dot(this, this); + } + else + { + return X * X + Y * Y + Z * Z; + } + } + #endregion Public Instance Methods + + #region Public Static Methods + /// + /// Returns the Euclidean distance between the two given points. + /// + /// The first point. + /// The second point. + /// The distance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Distance(Vector3 value1, Vector3 value2) + { + if (Vector.IsHardwareAccelerated) + { + Vector3 difference = value1 - value2; + float ls = Vector3.Dot(difference, difference); + return (float)System.Math.Sqrt(ls); + } + else + { + float dx = value1.X - value2.X; + float dy = value1.Y - value2.Y; + float dz = value1.Z - value2.Z; + + float ls = dx * dx + dy * dy + dz * dz; + + return (float)System.Math.Sqrt((double)ls); + } + } + + /// + /// Returns the Euclidean distance squared between the two given points. + /// + /// The first point. + /// The second point. + /// The distance squared. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DistanceSquared(Vector3 value1, Vector3 value2) + { + if (Vector.IsHardwareAccelerated) + { + Vector3 difference = value1 - value2; + return Vector3.Dot(difference, difference); + } + else + { + float dx = value1.X - value2.X; + float dy = value1.Y - value2.Y; + float dz = value1.Z - value2.Z; + + return dx * dx + dy * dy + dz * dz; + } + } + + /// + /// Returns a vector with the same direction as the given vector, but with a length of 1. + /// + /// The vector to normalize. + /// The normalized vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Normalize(Vector3 value) + { + if (Vector.IsHardwareAccelerated) + { + float length = value.Length(); + return value / length; + } + else + { + float ls = value.X * value.X + value.Y * value.Y + value.Z * value.Z; + float length = (float)System.Math.Sqrt(ls); + return new Vector3(value.X / length, value.Y / length, value.Z / length); + } + } + + /// + /// Computes the cross product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The cross product. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Cross(Vector3 vector1, Vector3 vector2) + { + return new Vector3( + vector1.Y * vector2.Z - vector1.Z * vector2.Y, + vector1.Z * vector2.X - vector1.X * vector2.Z, + vector1.X * vector2.Y - vector1.Y * vector2.X); + } + + /// + /// Returns the reflection of a vector off a surface that has the specified normal. + /// + /// The source vector. + /// The normal of the surface being reflected off. + /// The reflected vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Reflect(Vector3 vector, Vector3 normal) + { + if (System.Numerics.Vector.IsHardwareAccelerated) + { + float dot = Vector3.Dot(vector, normal); + Vector3 temp = normal * dot * 2f; + return vector - temp; + } + else + { + float dot = vector.X * normal.X + vector.Y * normal.Y + vector.Z * normal.Z; + float tempX = normal.X * dot * 2f; + float tempY = normal.Y * dot * 2f; + float tempZ = normal.Z * dot * 2f; + return new Vector3(vector.X - tempX, vector.Y - tempY, vector.Z - tempZ); + } + } + + /// + /// Restricts a vector between a min and max value. + /// + /// The source vector. + /// The minimum value. + /// The maximum value. + /// The restricted vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Clamp(Vector3 value1, Vector3 min, Vector3 max) + { + // This compare order is very important!!! + // We must follow HLSL behavior in the case user specified min value is bigger than max value. + + float x = value1.X; + x = (x > max.X) ? max.X : x; + x = (x < min.X) ? min.X : x; + + float y = value1.Y; + y = (y > max.Y) ? max.Y : y; + y = (y < min.Y) ? min.Y : y; + + float z = value1.Z; + z = (z > max.Z) ? max.Z : z; + z = (z < min.Z) ? min.Z : z; + + return new Vector3(x, y, z); + } + + /// + /// Linearly interpolates between two vectors based on the given weighting. + /// + /// The first source vector. + /// The second source vector. + /// Value between 0 and 1 indicating the weight of the second source vector. + /// The interpolated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Lerp(Vector3 value1, Vector3 value2, float amount) + { + if (Vector.IsHardwareAccelerated) + { + Vector3 firstInfluence = value1 * (1f - amount); + Vector3 secondInfluence = value2 * amount; + return firstInfluence + secondInfluence; + } + else + { + return new Vector3( + value1.X + (value2.X - value1.X) * amount, + value1.Y + (value2.Y - value1.Y) * amount, + value1.Z + (value2.Z - value1.Z) * amount); + } + } + + /// + /// Transforms a vector by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Transform(Vector3 position, Matrix4x4 matrix) + { + return new Vector3( + position.X * matrix.M11 + position.Y * matrix.M21 + position.Z * matrix.M31 + matrix.M41, + position.X * matrix.M12 + position.Y * matrix.M22 + position.Z * matrix.M32 + matrix.M42, + position.X * matrix.M13 + position.Y * matrix.M23 + position.Z * matrix.M33 + matrix.M43); + } + + /// + /// Transforms a vector normal by the given matrix. + /// + /// The source vector. + /// The transformation matrix. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 TransformNormal(Vector3 normal, Matrix4x4 matrix) + { + return new Vector3( + normal.X * matrix.M11 + normal.Y * matrix.M21 + normal.Z * matrix.M31, + normal.X * matrix.M12 + normal.Y * matrix.M22 + normal.Z * matrix.M32, + normal.X * matrix.M13 + normal.Y * matrix.M23 + normal.Z * matrix.M33); + } + + /// + /// Transforms a vector by the given Quaternion rotation value. + /// + /// The source vector to be rotated. + /// The rotation to apply. + /// The transformed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Transform(Vector3 value, Quaternion rotation) + { + float x2 = rotation.X + rotation.X; + float y2 = rotation.Y + rotation.Y; + float z2 = rotation.Z + rotation.Z; + + float wx2 = rotation.W * x2; + float wy2 = rotation.W * y2; + float wz2 = rotation.W * z2; + float xx2 = rotation.X * x2; + float xy2 = rotation.X * y2; + float xz2 = rotation.X * z2; + float yy2 = rotation.Y * y2; + float yz2 = rotation.Y * z2; + float zz2 = rotation.Z * z2; + + return new Vector3( + value.X * (1.0f - yy2 - zz2) + value.Y * (xy2 - wz2) + value.Z * (xz2 + wy2), + value.X * (xy2 + wz2) + value.Y * (1.0f - xx2 - zz2) + value.Z * (yz2 - wx2), + value.X * (xz2 - wy2) + value.Y * (yz2 + wx2) + value.Z * (1.0f - xx2 - yy2)); + } + #endregion Public Static Methods + + #region Public operator methods + + // All these methods should be inlined as they are implemented + // over JIT intrinsics + + /// + /// Adds two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The summed vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Add(Vector3 left, Vector3 right) + { + return left + right; + } + + /// + /// Subtracts the second vector from the first. + /// + /// The first source vector. + /// The second source vector. + /// The difference vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Subtract(Vector3 left, Vector3 right) + { + return left - right; + } + + /// + /// Multiplies two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The product vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Multiply(Vector3 left, Vector3 right) + { + return left * right; + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Multiply(Vector3 left, Single right) + { + return left * right; + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The scalar value. + /// The source vector. + /// The scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Multiply(Single left, Vector3 right) + { + return left * right; + } + + /// + /// Divides the first vector by the second. + /// + /// The first source vector. + /// The second source vector. + /// The vector resulting from the division. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Divide(Vector3 left, Vector3 right) + { + return left / right; + } + + /// + /// Divides the vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The result of the division. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Divide(Vector3 left, Single divisor) + { + return left / divisor; + } + + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Negate(Vector3 value) + { + return -value; + } + #endregion Public operator methods + } +} \ No newline at end of file diff --git a/Core/Numerics/Vector3_Intrinsics.cs b/Core/Numerics/Vector3_Intrinsics.cs new file mode 100644 index 0000000..40146ad --- /dev/null +++ b/Core/Numerics/Vector3_Intrinsics.cs @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.CompilerServices; + +namespace SharpEngine.Core.Numerics +{ + // This file contains the definitions for all of the JIT intrinsic methods and properties that are recognized by the current x64 JIT compiler. + // The implementation defined here is used in any circumstance where the JIT fails to recognize these members as intrinsic. + // The JIT recognizes these methods and properties by name and signature: if either is changed, the JIT will no longer recognize the member. + // Some methods declared here are not strictly intrinsic, but delegate to an intrinsic method. For example, only one overload of CopyTo() + // is actually recognized by the JIT, but both are here for simplicity. + + public partial struct Vector3 + { + /// + /// The X component of the vector. + /// + public Single X; + /// + /// The Y component of the vector. + /// + public Single Y; + /// + /// The Z component of the vector. + /// + public Single Z; + + #region Constructors + /// + /// Constructs a vector whose elements are all the single specified value. + /// + /// The element to fill the vector with. + [JitIntrinsic] + public Vector3(Single value) : this(value, value, value) { } + + /// + /// Constructs a Vector3 from the given Vector2 and a third value. + /// + /// The Vector to extract X and Y components from. + /// The Z component. + public Vector3(Vector2 value, float z) : this(value.X, value.Y, z) { } + + /// + /// Constructs a vector with the given individual elements. + /// + /// The X component. + /// The Y component. + /// The Z component. + [JitIntrinsic] + public Vector3(Single x, Single y, Single z) + { + X = x; + Y = y; + Z = z; + } + #endregion Constructors + + #region Public Instance Methods + /// + /// Copies the contents of the vector into the given array. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(Single[] array) + { + CopyTo(array, 0); + } + + /// + /// Copies the contents of the vector into the given array, starting from index. + /// + /// If array is null. + /// If array is multidimensional. + /// If index is greater than end of the array or index is less than zero. + /// If number of elements in source vector is greater than those available in destination array. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(Single[] array, int index) + { + if (array == null) + { + // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull. + throw new NullReferenceException(); + } + if (index < 0 || index >= array.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + if ((array.Length - index) < 3) + { + throw new ArgumentException("", nameof(index)); + } + + array[index] = X; + array[index + 1] = Y; + array[index + 2] = Z; + } + + /// + /// Returns a boolean indicating whether the given Vector3 is equal to this Vector3 instance. + /// + /// The Vector3 to compare this instance to. + /// True if the other Vector3 is equal to this instance; False otherwise. + [JitIntrinsic] + public bool Equals(Vector3 other) + { + return X == other.X && + Y == other.Y && + Z == other.Z; + } + #endregion Public Instance Methods + + #region Public Static Methods + /// + /// Returns the dot product of two vectors. + /// + /// The first vector. + /// The second vector. + /// The dot product. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Dot(Vector3 vector1, Vector3 vector2) + { + return vector1.X * vector2.X + + vector1.Y * vector2.Y + + vector1.Z * vector2.Z; + } + + /// + /// Returns a vector whose elements are the minimum of each of the pairs of elements in the two source vectors. + /// + /// The first source vector. + /// The second source vector. + /// The minimized vector. + [JitIntrinsic] + public static Vector3 Min(Vector3 value1, Vector3 value2) + { + return new Vector3( + (value1.X < value2.X) ? value1.X : value2.X, + (value1.Y < value2.Y) ? value1.Y : value2.Y, + (value1.Z < value2.Z) ? value1.Z : value2.Z); + } + + /// + /// Returns a vector whose elements are the maximum of each of the pairs of elements in the two source vectors. + /// + /// The first source vector. + /// The second source vector. + /// The maximized vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Max(Vector3 value1, Vector3 value2) + { + return new Vector3( + (value1.X > value2.X) ? value1.X : value2.X, + (value1.Y > value2.Y) ? value1.Y : value2.Y, + (value1.Z > value2.Z) ? value1.Z : value2.Z); + } + + /// + /// Returns a vector whose elements are the absolute values of each of the source vector's elements. + /// + /// The source vector. + /// The absolute value vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 Abs(Vector3 value) + { + return new Vector3(MathF.Abs(value.X), MathF.Abs(value.Y), MathF.Abs(value.Z)); + } + + /// + /// Returns a vector whose elements are the square root of each of the source vector's elements. + /// + /// The source vector. + /// The square root vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 SquareRoot(Vector3 value) + { + return new Vector3((Single)MathF.Sqrt(value.X), (Single)MathF.Sqrt(value.Y), (Single)MathF.Sqrt(value.Z)); + } + #endregion Public Static Methods + + #region Public Static Operators + /// + /// Adds two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The summed vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator +(Vector3 left, Vector3 right) + { + return new Vector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); + } + + /// + /// Subtracts the second vector from the first. + /// + /// The first source vector. + /// The second source vector. + /// The difference vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator -(Vector3 left, Vector3 right) + { + return new Vector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z); + } + + /// + /// Multiplies two vectors together. + /// + /// The first source vector. + /// The second source vector. + /// The product vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator *(Vector3 left, Vector3 right) + { + return new Vector3(left.X * right.X, left.Y * right.Y, left.Z * right.Z); + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The scaled vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator *(Vector3 left, Single right) + { + return left * new Vector3(right); + } + + /// + /// Multiplies a vector by the given scalar. + /// + /// The scalar value. + /// The source vector. + /// The scaled vector. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator *(Single left, Vector3 right) + { + return new Vector3(left) * right; + } + + /// + /// Divides the first vector by the second. + /// + /// The first source vector. + /// The second source vector. + /// The vector resulting from the division. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator /(Vector3 left, Vector3 right) + { + return new Vector3(left.X / right.X, left.Y / right.Y, left.Z / right.Z); + } + + /// + /// Divides the vector by the given scalar. + /// + /// The source vector. + /// The scalar value. + /// The result of the division. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator /(Vector3 value1, float value2) + { + float invDiv = 1.0f / value2; + + return new Vector3( + value1.X * invDiv, + value1.Y * invDiv, + value1.Z * invDiv); + } + + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3 operator -(Vector3 value) + { + return Zero - value; + } + + /// + /// Returns a boolean indicating whether the two given vectors are equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are equal; False otherwise. + [JitIntrinsic] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Vector3 left, Vector3 right) + { + return (left.X == right.X && + left.Y == right.Y && + left.Z == right.Z); + } + + /// + /// Returns a boolean indicating whether the two given vectors are not equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are not equal; False if they are equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Vector3 left, Vector3 right) + { + return (left.X != right.X || + left.Y != right.Y || + left.Z != right.Z); + } + #endregion Public Static Operators + } +} \ No newline at end of file diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 1fdcee6..106f45e 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -1,13 +1,28 @@ -using System.Collections.Generic; +using SharpEngine.Core.Entities; +using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Numerics; +using System.Collections.Generic; using System.Threading.Tasks; namespace SharpEngine.Core.Scenes; +public class EmptyNode : SceneNode where TTransform : ITransform, new() +{ + public EmptyNode(string name) : base(name) { } + + public override TTransform Transform { get; set; } = new(); + +} + /// /// Represents a node in the scene. /// -public class SceneNode +public abstract class SceneNode where TTransform : ITransform, new() { + public abstract TTransform Transform { get; set; } + + public static SceneNode Empty => new EmptyNode("Empty Node"); + /// /// Gets or sets the name of the node. /// @@ -16,15 +31,15 @@ public class SceneNode /// /// Gets or sets the children of the node. /// - public List Children { get; set; } = []; + public List> Children { get; set; } = []; /// - /// Initializes a new empty . + /// Initializes a new empty . /// public SceneNode() { } /// - /// Initializes a new instance of the with the specified . + /// Initializes a new instance of the with the specified . /// /// The name of the new empty node. public SceneNode(string name) @@ -37,20 +52,20 @@ public SceneNode(string name) /// /// The name of the empty node to be added. /// The created node. - public SceneNode AddChild(string name) + public virtual SceneNode AddChild(string name) { - var node = new SceneNode(name); - Children.Add(node); + var node = new EmptyNode(name) as SceneNode; + Children.Add(node!); - return node; + return node!; } - /// - /// Adds a child node to this node. - /// - /// The nodes to be added. - /// The current node. - public virtual SceneNode AddChild(params SceneNode[] nodes) + /// + /// Adds a child node to this node. + /// + /// The nodes to be added. + /// The current node. + public virtual SceneNode AddChild(params SceneNode[] nodes) { foreach (var node in nodes) Children.Add(node); @@ -62,7 +77,7 @@ public virtual SceneNode AddChild(params SceneNode[] nodes) /// Removes a child node from this node. /// /// The node to be removed. - public void RemoveChild(SceneNode node) + public void RemoveChild(SceneNode node) { Children.Remove(node); } diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 11785e3..136e55a 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -13,6 +13,7 @@ using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Windowing; using SharpEngine.Core.Entities.UI.Layouts; +using SharpEngine.Core.Entities.Properties; namespace Minecraft; @@ -25,8 +26,8 @@ public class Minecraft : Game { private readonly Scene _scene; - private SceneNode _lightsNode; - private SceneNode _blocksNode; + private SceneNode _lightsNode; + private SceneNode _blocksNode; private Input _input; private readonly Inventory _inventory; @@ -71,7 +72,7 @@ public override void Initialize() uiElem2.Transform.Scale = new Vector2(0.2f, 0.2f); //uiElem2.Transform.Position = new Vector2(30, 0); - //gridLayout.AddChild(_uiElem, uiElem2); + gridLayout.AddChild(_uiElem, uiElem2); _scene.UIElements.Add(gridLayout); @@ -153,7 +154,7 @@ public override void Update(double deltaTime, IInputContext input) } private void UpdateUI() - => _uiElem.Transform.Rotation += 0.01f; + => _uiElem.Transform.Rotation.Angle += 0.01f; // TODO: Input system to let users change change key bindings? /// From be73f35bf9cc7ad1c37aa397bc3cd9486feda660 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Fri, 4 Apr 2025 20:57:08 +0300 Subject: [PATCH 17/28] Midway solution (Not finished) --- Core/Entities/GameObject.cs | 6 ++--- Core/Entities/Lights/PointLight.cs | 8 +++---- Core/Entities/Lights/SpotLight.cs | 2 +- Core/Entities/Properties/BoundingBox.cs | 2 +- Core/Entities/Properties/ITransform.cs | 13 +++++----- .../Entities/Properties/Meshes/MeshService.cs | 22 +++++++++++++++-- Core/Entities/Properties/Transform.cs | 11 ++------- Core/Entities/UI/Layouts/FlexLayout.cs | 2 +- Core/Entities/UI/Layouts/GridLayout.cs | 4 ++-- Core/Entities/UI/Layouts/LayoutBase.cs | 7 +++--- Core/Entities/UI/UIElement.cs | 10 ++++---- Core/Numerics/Vector2.cs | 1 + Core/Numerics/Vector2_Intrinsics.cs | 7 ++++++ Core/Numerics/Vector3_Intrinsics.cs | 7 ++++++ Core/Primitives/PrimitiveFactory.cs | 2 +- Core/Renderers/RenderFlags.cs | 3 ++- Core/Renderers/TextRenderer.cs | 20 ++++++++++++++++ Core/Scenes/Scene.cs | 5 ++-- Core/Scenes/SceneNode.cs | 24 +++++++++---------- Editor/Windows/ContextMenuWindow.cs | 2 +- Examples/MinecraftClone/Minecraft.cs | 10 ++++---- 21 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 Core/Renderers/TextRenderer.cs diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 415a490..4c58dd8 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -16,7 +16,7 @@ namespace SharpEngine.Core.Entities; /// /// Represents a game object in the scene. /// -public class GameObject : SceneNode +public class GameObject : SceneNode { /// /// Initializes a new instance of the . @@ -62,7 +62,7 @@ public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, /// /// Gets or sets the transform of the game object. /// - public override Transform Transform + public Transform Transform { get => _transform; set @@ -72,7 +72,7 @@ public override Transform Transform } } - private Transform _transform; + private Transform _transform = new(); /// /// Gets the bounding box of the game object. diff --git a/Core/Entities/Lights/PointLight.cs b/Core/Entities/Lights/PointLight.cs index 0dcdeff..e63d141 100644 --- a/Core/Entities/Lights/PointLight.cs +++ b/Core/Entities/Lights/PointLight.cs @@ -19,7 +19,7 @@ public class PointLight : Light public PointLight(Vector3 position, int index) { Transform.Scale = new(0.2f, 0.2f, 0.2f); - Transform.Position = position; + Transform.Position = (SharpEngine.Core.Numerics.Vector3)position; Constant = 1.0f; Linear = 0.09f; Quadratic = 0.032f; @@ -53,7 +53,7 @@ public PointLight(Vector3 position, int index) /// public override Task Render() { - Material.Shader.SetVector3($"pointLights[{_index}].position", Transform.Position); + Material.Shader.SetVector3($"pointLights[{_index}].position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3($"pointLights[{_index}].ambient", Ambient); Material.Shader.SetVector3($"pointLights[{_index}].diffuse", Diffuse); Material.Shader.SetVector3($"pointLights[{_index}].specular", Specular); @@ -61,8 +61,8 @@ public override Task Render() Material.Shader.SetFloat($"pointLights[{_index}].linear", Linear); Material.Shader.SetFloat($"pointLights[{_index}].quadratic", Quadratic); - var lampMatrix = Matrix4x4.CreateScale(Transform.Scale); - lampMatrix *= Matrix4x4.CreateTranslation(Transform.Position); + var lampMatrix = Matrix4x4.CreateScale((System.Numerics.Vector3)Transform.Scale); + lampMatrix *= Matrix4x4.CreateTranslation((System.Numerics.Vector3)Transform.Position); LampShader.Shader?.SetMatrix4(ShaderAttributes.Model, lampMatrix); diff --git a/Core/Entities/Lights/SpotLight.cs b/Core/Entities/Lights/SpotLight.cs index 4a839f0..e63597d 100644 --- a/Core/Entities/Lights/SpotLight.cs +++ b/Core/Entities/Lights/SpotLight.cs @@ -59,7 +59,7 @@ public SpotLight() /// public override Task Render() { - Material.Shader.SetVector3("spotLight.position", Transform.Position); + Material.Shader.SetVector3("spotLight.position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3("spotLight.direction", Direction); Material.Shader.SetVector3("spotLight.ambient", Ambient); Material.Shader.SetVector3("spotLight.diffuse", Diffuse); diff --git a/Core/Entities/Properties/BoundingBox.cs b/Core/Entities/Properties/BoundingBox.cs index 1119c28..598d519 100644 --- a/Core/Entities/Properties/BoundingBox.cs +++ b/Core/Entities/Properties/BoundingBox.cs @@ -36,6 +36,6 @@ public static BoundingBox CalculateBoundingBox(Transform transform) { var min = transform.Position - (transform.Scale / 2); var max = transform.Position + (transform.Scale / 2); - return new BoundingBox(min, max); + return new BoundingBox((Vector3)min, (Vector3)max); } } diff --git a/Core/Entities/Properties/ITransform.cs b/Core/Entities/Properties/ITransform.cs index 65108b0..5685511 100644 --- a/Core/Entities/Properties/ITransform.cs +++ b/Core/Entities/Properties/ITransform.cs @@ -1,11 +1,12 @@ -using System.Numerics; +using SharpEngine.Core.Numerics; +using System.Numerics; namespace SharpEngine.Core.Entities.Properties; -public interface ITransform +public interface ITransform where TVector : IVector { - public abstract IVector Position { get; set; } - public abstract IVector Scale { get; set; } - public abstract Quaternion Rotation { get; set; } - public abstract Matrix4x4 ModelMatrix { get; } + public TVector Position { get; set; } + public TVector Scale { get; set; } + public Quaternion Rotation { get; set; } + public Matrix4x4 ModelMatrix { get; } } \ No newline at end of file diff --git a/Core/Entities/Properties/Meshes/MeshService.cs b/Core/Entities/Properties/Meshes/MeshService.cs index d50b576..e884bf7 100644 --- a/Core/Entities/Properties/Meshes/MeshService.cs +++ b/Core/Entities/Properties/Meshes/MeshService.cs @@ -2,6 +2,7 @@ using Silk.NET.OpenGL; using System; using System.Collections.Generic; +using System.IO; namespace SharpEngine.Core.Entities.Properties.Meshes; @@ -38,10 +39,27 @@ public Mesh LoadMesh(string identifier, Mesh mesh) return mesh; } + const string FbxExtension = ".fbx"; + const string ObjExtension = ".obj"; + public Mesh LoadMesh(string identifier, string meshFilePath) + => Path.GetExtension(meshFilePath) switch + { + FbxExtension => LoadFbxMesh(identifier, meshFilePath), + ObjExtension => LoadObjMesh(identifier, meshFilePath), + _ => throw new NotSupportedException($"The file extension {Path.GetExtension(meshFilePath)} is not a supported mesh file.") + }; + + // TODO: #2 Load obj mesh from file + private Mesh LoadObjMesh(string identifier, string meshFilePath) + { + throw new NotImplementedException(); + } + + // TODO: #3 Load fbx mesh from file + private Mesh LoadFbxMesh(string identifier, string meshFilePath) { - throw new NotImplementedException("Loading meshes from files is not currently supported."); + throw new NotImplementedException(); } - // TODO: #3 & #2 Load mesh from file } \ No newline at end of file diff --git a/Core/Entities/Properties/Transform.cs b/Core/Entities/Properties/Transform.cs index 7370aea..c934172 100644 --- a/Core/Entities/Properties/Transform.cs +++ b/Core/Entities/Properties/Transform.cs @@ -1,8 +1,5 @@ -using Silk.NET.Maths; -using SharpEngine.Core.Entities; -using System.Numerics; - -using Vector3 = SharpEngine.Core.Numerics.Vector3; +using SharpEngine.Core.Numerics; +using Matrix4x4 = System.Numerics.Matrix4x4; namespace SharpEngine.Core.Entities.Properties; @@ -11,10 +8,6 @@ namespace SharpEngine.Core.Entities.Properties; /// public class Transform : ITransform { - public Transform() - { - } - /// /// Gets or sets the position of the game object. /// diff --git a/Core/Entities/UI/Layouts/FlexLayout.cs b/Core/Entities/UI/Layouts/FlexLayout.cs index 6768311..d65776e 100644 --- a/Core/Entities/UI/Layouts/FlexLayout.cs +++ b/Core/Entities/UI/Layouts/FlexLayout.cs @@ -8,7 +8,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a layout that dynamically distributes items in a flex layout. /// /// The type of items in the collection. -public class FlexLayout : LayoutBase where T : SceneNode +public class FlexLayout : LayoutBase where T : SceneNode, new() { public int Gap { get; set; } diff --git a/Core/Entities/UI/Layouts/GridLayout.cs b/Core/Entities/UI/Layouts/GridLayout.cs index 20bd9b3..b542bcf 100644 --- a/Core/Entities/UI/Layouts/GridLayout.cs +++ b/Core/Entities/UI/Layouts/GridLayout.cs @@ -9,7 +9,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a grid layout. /// /// The type of items that can be stored and retrieved within the grid. -public class GridLayout : LayoutBase where T : SceneNode> +public class GridLayout : LayoutBase where T : SceneNode, new() { /// Gets or sets the amount of rows in the grid. public uint Rows { get; set; } = 2; @@ -47,7 +47,7 @@ public override void AddItem(T item) if (Items.Count % Columns != 0) offset = offset + (Items.Count * 30); - item.Transform.Position.X = offset; + // item.Transform.Position.X = offset; } /// diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index e21404e..cdb5f8d 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -1,4 +1,5 @@ using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using System; using System.Collections.Generic; @@ -8,14 +9,12 @@ namespace SharpEngine.Core.Entities.UI.Layouts; -public abstract class LayoutBase : EmptyNode where T : SceneNode, new() +public abstract class LayoutBase : SceneNode where T : SceneNode, new() { public List Items { get; set; } = []; - public override SceneNode AddChild(params SceneNode[] nodes) + public override LayoutBase AddChild(params SceneNode[] nodes) { - base.AddChild(nodes); - foreach (var node in nodes) AddItem((T)node); diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index ec90445..2e0a549 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -12,8 +12,10 @@ namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : SceneNode +public class UIElement : SceneNode { + public UIElement() : this("UIElement") { } + /// /// Initializes a new instance of . /// @@ -29,7 +31,7 @@ public UIElement(string name) private readonly UIShader _uiShader = new(); /// Gets or sets the 2D space transformation of the UI element. - public override Transform2D Transform { get; set; } = new(); + public Transform2D Transform { get; set; } = new(); /// Gets or sets the mesh of the UI element. public new Mesh Mesh { get; set; } @@ -75,9 +77,9 @@ public override Task Render() { Window.GL.BindVertexArray(_vertexArrayObject); - _uiShader.Shader.SetVector2("position", Transform.Position); + _uiShader.Shader.SetVector2("position", (System.Numerics.Vector2)Transform.Position); _uiShader.Shader.SetFloat("rotation", Transform.Rotation.Angle); - _uiShader.Shader.SetVector2("scale", Transform.Scale); + _uiShader.Shader.SetVector2("scale", (System.Numerics.Vector2)Transform.Scale); Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); diff --git a/Core/Numerics/Vector2.cs b/Core/Numerics/Vector2.cs index 7956d9f..231abec 100644 --- a/Core/Numerics/Vector2.cs +++ b/Core/Numerics/Vector2.cs @@ -453,6 +453,7 @@ public static Vector2 Negate(Vector2 value) { return -value; } + #endregion Public operator methods } } \ No newline at end of file diff --git a/Core/Numerics/Vector2_Intrinsics.cs b/Core/Numerics/Vector2_Intrinsics.cs index f9c06bc..6619057 100644 --- a/Core/Numerics/Vector2_Intrinsics.cs +++ b/Core/Numerics/Vector2_Intrinsics.cs @@ -293,6 +293,13 @@ public static Vector2 SquareRoot(Vector2 value) { return !(left == right); } + + public static explicit operator System.Numerics.Vector2(Vector2 value) + => new(value.X, value.Y); + + public static explicit operator Vector2(System.Numerics.Vector2 value) + => new(value.X, value.Y); + #endregion Public Static Operators } } \ No newline at end of file diff --git a/Core/Numerics/Vector3_Intrinsics.cs b/Core/Numerics/Vector3_Intrinsics.cs index 40146ad..71456df 100644 --- a/Core/Numerics/Vector3_Intrinsics.cs +++ b/Core/Numerics/Vector3_Intrinsics.cs @@ -319,6 +319,13 @@ public static Vector3 SquareRoot(Vector3 value) left.Y != right.Y || left.Z != right.Z); } + + public static explicit operator System.Numerics.Vector3(Vector3 value) + => new(value.X, value.Y, value.Z); + + public static explicit operator Vector3(System.Numerics.Vector3 value) + => new(value.X, value.Y, value.Z); + #endregion Public Static Operators } } \ No newline at end of file diff --git a/Core/Primitives/PrimitiveFactory.cs b/Core/Primitives/PrimitiveFactory.cs index 497861e..51f03d0 100644 --- a/Core/Primitives/PrimitiveFactory.cs +++ b/Core/Primitives/PrimitiveFactory.cs @@ -32,7 +32,7 @@ public static GameObject Create(PrimitiveType primitiveType, Vector3 position) public static GameObject Create(PrimitiveType primitiveType, Vector3 position, string diffuseMapFile, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) => new(diffuseMapFile, specularMapFile, vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader) { - Transform = new Transform { Position = position }, + Transform = new Transform { Position = (SharpEngine.Core.Numerics.Vector3)position }, Mesh = primitiveType switch { PrimitiveType.Cube => [Cube.Mesh], diff --git a/Core/Renderers/RenderFlags.cs b/Core/Renderers/RenderFlags.cs index 68d5137..2e208a2 100644 --- a/Core/Renderers/RenderFlags.cs +++ b/Core/Renderers/RenderFlags.cs @@ -22,5 +22,6 @@ public enum RenderFlags /// /// Enables all renderers. /// - All = 3 + All = 3, + Text = 4 } diff --git a/Core/Renderers/TextRenderer.cs b/Core/Renderers/TextRenderer.cs new file mode 100644 index 0000000..eff8504 --- /dev/null +++ b/Core/Renderers/TextRenderer.cs @@ -0,0 +1,20 @@ +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Interfaces; +using SharpEngine.Core.Scenes; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Renderers; +internal class TextRenderer : RendererBase +{ + public TextRenderer(CameraView camera, ISettings settings, Scene scene) : base(settings) + { + } + + public override RenderFlags RenderFlag => RenderFlags.Text; + + /// + public override Task Render() + { + return Task.CompletedTask; + } +} diff --git a/Core/Scenes/Scene.cs b/Core/Scenes/Scene.cs index 3f46da7..80f71ab 100644 --- a/Core/Scenes/Scene.cs +++ b/Core/Scenes/Scene.cs @@ -1,4 +1,5 @@ using SharpEngine.Core.Entities; +using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.UI; using System; using System.Collections.Generic; @@ -41,7 +42,7 @@ public Scene(string sceneFile) /// /// Gets or sets the root node of the scene. /// - public SceneNode Root { get; set; } = new SceneNode("Root"); + public SceneNode Root { get; set; } = new EmptyNode("Root"); /// Gets or sets the nodes in the scene. private List Nodes { get; set; } = []; @@ -59,7 +60,7 @@ public Scene(string sceneFile) /// The name of the new empty node. public void AddNode(string name) { - var node = new SceneNode(name); + var node = new EmptyNode(name); Nodes.Add(node); Root.Children.Add(node); } diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 106f45e..792bd9d 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -6,22 +6,20 @@ namespace SharpEngine.Core.Scenes; -public class EmptyNode : SceneNode where TTransform : ITransform, new() +public class EmptyNode : SceneNode { public EmptyNode(string name) : base(name) { } - public override TTransform Transform { get; set; } = new(); + public Transform Transform { get; set; } } /// /// Represents a node in the scene. /// -public abstract class SceneNode where TTransform : ITransform, new() +public abstract class SceneNode { - public abstract TTransform Transform { get; set; } - - public static SceneNode Empty => new EmptyNode("Empty Node"); + public static SceneNode Empty => new EmptyNode("Empty Node"); /// /// Gets or sets the name of the node. @@ -31,15 +29,15 @@ public EmptyNode(string name) : base(name) { } /// /// Gets or sets the children of the node. /// - public List> Children { get; set; } = []; + public List Children { get; set; } = []; /// - /// Initializes a new empty . + /// Initializes a new empty . /// public SceneNode() { } /// - /// Initializes a new instance of the with the specified . + /// Initializes a new instance of the with the specified . /// /// The name of the new empty node. public SceneNode(string name) @@ -52,9 +50,9 @@ public SceneNode(string name) /// /// The name of the empty node to be added. /// The created node. - public virtual SceneNode AddChild(string name) + public virtual SceneNode AddChild(string name) { - var node = new EmptyNode(name) as SceneNode; + var node = new EmptyNode(name) as SceneNode; Children.Add(node!); return node!; @@ -65,7 +63,7 @@ public virtual SceneNode AddChild(string name) /// /// The nodes to be added. /// The current node. - public virtual SceneNode AddChild(params SceneNode[] nodes) + public virtual SceneNode AddChild(params SceneNode[] nodes) { foreach (var node in nodes) Children.Add(node); @@ -77,7 +75,7 @@ public virtual SceneNode AddChild(params SceneNode[] nod /// Removes a child node from this node. /// /// The node to be removed. - public void RemoveChild(SceneNode node) + public void RemoveChild(SceneNode node) { Children.Remove(node); } diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs index 603b8dc..cba5351 100644 --- a/Editor/Windows/ContextMenuWindow.cs +++ b/Editor/Windows/ContextMenuWindow.cs @@ -89,7 +89,7 @@ private void RenderSceneActions() if (ImGui.Button("Empty")) { var element = Scene.ActiveElement ?? Scene.Root; - element.AddChild(new SceneNode("Empty")); + element.AddChild(SceneNode.Empty); } } } diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 136e55a..4f6fe91 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -26,8 +26,8 @@ public class Minecraft : Game { private readonly Scene _scene; - private SceneNode _lightsNode; - private SceneNode _blocksNode; + private SceneNode _lightsNode; + private SceneNode _blocksNode; private Input _input; private readonly Inventory _inventory; @@ -64,12 +64,12 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - _uiElem.Transform.Scale = new Vector2(0.2f, 0.2f); + _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); //_uiElem.Transform.Position = new Vector2(-30, 0); _scene.UIElements.Add(_uiElem); var uiElem2 = new UIElement("uiElement"); - uiElem2.Transform.Scale = new Vector2(0.2f, 0.2f); + uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); //uiElem2.Transform.Position = new Vector2(30, 0); gridLayout.AddChild(_uiElem, uiElem2); @@ -250,7 +250,7 @@ private void PlaceBlock() private static Vector3 GetNewBlockPosition(Vector3 hitPosition, GameObject intersectingObject) { Vector3 normal = Ray.GetClosestFaceNormal(hitPosition, intersectingObject); - return intersectingObject.Transform.Position + (normal * intersectingObject.Transform.Scale); + return (System.Numerics.Vector3)intersectingObject.Transform.Position + (normal * (System.Numerics.Vector3)intersectingObject.Transform.Scale); } /// From 9c01d8a5c9844dc78fbbaee0276c828b06948529 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Fri, 4 Apr 2025 21:16:49 +0300 Subject: [PATCH 18/28] Getting there --- Core/Entities/GameObject.cs | 7 ++++--- Core/Entities/UI/Layouts/GridLayout.cs | 14 +++++++------- Core/Entities/UI/Layouts/LayoutBase.cs | 14 +++++++------- Core/Entities/UI/UIElement.cs | 6 +++--- Core/Scenes/Scene.cs | 4 ++-- Core/Scenes/SceneNode.cs | 23 +++++++++++++---------- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 4c58dd8..3ea4adc 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -1,6 +1,7 @@ using SharpEngine.Core.Attributes; using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Textures; @@ -16,12 +17,12 @@ namespace SharpEngine.Core.Entities; /// /// Represents a game object in the scene. /// -public class GameObject : SceneNode +public class GameObject : EmptyNode { /// /// Initializes a new instance of the . /// - public GameObject() + public GameObject() : base(string.Empty) { // TODO: Using lightning shader here will most likely break stuff, it needs to be refactored out. var shader = ShaderService.Instance.LoadShader(_Resources.Default.VertexShader, _Resources.Default.FragmentShader, "lighting"); @@ -38,7 +39,7 @@ public GameObject() /// The file path of the specular map texture. /// The file path of the vertex shader. /// The file path of the fragment shader. - public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) + public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, string? vertShaderFile = null, string? fragShaderFile = null) : base(string.Empty) { var shader = ShaderService.Instance.LoadShader(vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader, "lighting"); var diffuse = TextureService.Instance.LoadTexture(diffuseMapFile ?? _Resources.Default.DebugTexture); diff --git a/Core/Entities/UI/Layouts/GridLayout.cs b/Core/Entities/UI/Layouts/GridLayout.cs index b542bcf..993a73c 100644 --- a/Core/Entities/UI/Layouts/GridLayout.cs +++ b/Core/Entities/UI/Layouts/GridLayout.cs @@ -8,8 +8,8 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// /// Represents a grid layout. /// -/// The type of items that can be stored and retrieved within the grid. -public class GridLayout : LayoutBase where T : SceneNode, new() +/// The type of items that can be stored and retrieved within the grid. +public class GridLayout : LayoutBase where TItem : SceneNode, new() { /// Gets or sets the amount of rows in the grid. public uint Rows { get; set; } = 2; @@ -23,7 +23,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// The row where the items should be retrieved. /// The column where the item should be retrieved. /// The item at [, ]. - public T this[uint row, uint column] + public TItem this[uint row, uint column] { get { @@ -38,7 +38,7 @@ namespace SharpEngine.Core.Entities.UI.Layouts; } /// - public override void AddItem(T item) + public override void AddItem(TItem item) { base.AddItem(item); @@ -70,12 +70,12 @@ private uint GetIndex(uint row, uint column) } /// - public override T[][] GetValues() + public override TItem[][] GetValues() { - var values = new T[Rows][]; + var values = new TItem[Rows][]; for (uint i = 0; i < Rows; i++) { - values[i] = new T[Columns]; + values[i] = new TItem[Columns]; for (uint j = 0; j < Columns; j++) { var item = this[i, j]; diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index cdb5f8d..948f425 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -9,27 +9,27 @@ namespace SharpEngine.Core.Entities.UI.Layouts; -public abstract class LayoutBase : SceneNode where T : SceneNode, new() +public abstract class LayoutBase : SceneNode where TItem : SceneNode, new() { - public List Items { get; set; } = []; + public List Items { get; set; } = []; - public override LayoutBase AddChild(params SceneNode[] nodes) + public override LayoutBase AddChild(params SceneNode[] nodes) { foreach (var node in nodes) - AddItem((T)node); + AddItem((TItem)node); return this; } - public virtual void AddItem(T item) + public virtual void AddItem(TItem item) { Items.Add(item); } - public virtual bool RemoveItem(T item) + public virtual bool RemoveItem(TItem item) { return Items.Remove(item); } - public abstract T[][] GetValues(); + public abstract TItem[][] GetValues(); } diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 2e0a549..de5a9cb 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; @@ -12,7 +13,7 @@ namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : SceneNode +public class UIElement : EmptyNode { public UIElement() : this("UIElement") { } @@ -20,9 +21,8 @@ public UIElement() : this("UIElement") { } /// Initializes a new instance of . /// /// The name of the UI element. - public UIElement(string name) + public UIElement(string name) : base(name) { - Name = name; Mesh = MeshService.Instance.LoadMesh(nameof(Primitives.Plane), Primitives.Plane.Mesh); Initialize(); diff --git a/Core/Scenes/Scene.cs b/Core/Scenes/Scene.cs index 80f71ab..5d9dd5b 100644 --- a/Core/Scenes/Scene.cs +++ b/Core/Scenes/Scene.cs @@ -42,7 +42,7 @@ public Scene(string sceneFile) /// /// Gets or sets the root node of the scene. /// - public SceneNode Root { get; set; } = new EmptyNode("Root"); + public SceneNode Root { get; set; } = new EmptyNode("Root"); /// Gets or sets the nodes in the scene. private List Nodes { get; set; } = []; @@ -60,7 +60,7 @@ public Scene(string sceneFile) /// The name of the new empty node. public void AddNode(string name) { - var node = new EmptyNode(name); + var node = new EmptyNode(name); Nodes.Add(node); Root.Children.Add(node); } diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 792bd9d..2ed0e80 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -6,11 +6,11 @@ namespace SharpEngine.Core.Scenes; -public class EmptyNode : SceneNode +public class EmptyNode : SceneNode where TVector : IVector { public EmptyNode(string name) : base(name) { } - public Transform Transform { get; set; } + public ITransform Transform { get; set; } } @@ -19,7 +19,7 @@ public EmptyNode(string name) : base(name) { } /// public abstract class SceneNode { - public static SceneNode Empty => new EmptyNode("Empty Node"); + public static SceneNode Empty => new EmptyNode("Empty Node"); /// /// Gets or sets the name of the node. @@ -45,24 +45,27 @@ public SceneNode(string name) Name = name; } + public virtual SceneNode AddChild(string name) + => AddChild(name); + /// /// Adds an empty child node to this node by the given . /// /// The name of the empty node to be added. /// The created node. - public virtual SceneNode AddChild(string name) + public virtual SceneNode AddChild(string name) where TVector : IVector { - var node = new EmptyNode(name) as SceneNode; + var node = new EmptyNode(name) as SceneNode; Children.Add(node!); return node!; } - /// - /// Adds a child node to this node. - /// - /// The nodes to be added. - /// The current node. + /// + /// Adds a child node to this node. + /// + /// The nodes to be added. + /// The current node. public virtual SceneNode AddChild(params SceneNode[] nodes) { foreach (var node in nodes) From fc04807669f27abd0e75bc1965032367d588895b Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Fri, 4 Apr 2025 21:37:35 +0300 Subject: [PATCH 19/28] Transform abstraction --- Core/Entities/GameObject.cs | 6 +++--- Core/Entities/Properties/BoundingBox.cs | 2 +- Core/Entities/Properties/ITransform.cs | 2 +- Core/Entities/Properties/Transform.cs | 2 ++ Core/Entities/UI/UIElement.cs | 7 ++----- Core/Scenes/Scene.cs | 4 ++-- Core/Scenes/SceneNode.cs | 13 ++++++------- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 3ea4adc..7f61043 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -17,7 +17,7 @@ namespace SharpEngine.Core.Entities; /// /// Represents a game object in the scene. /// -public class GameObject : EmptyNode +public class GameObject : EmptyNode { /// /// Initializes a new instance of the . @@ -63,7 +63,7 @@ public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, /// /// Gets or sets the transform of the game object. /// - public Transform Transform + public override Transform Transform { get => _transform; set @@ -72,7 +72,7 @@ public Transform Transform BoundingBox = BoundingBox.CalculateBoundingBox(_transform); } } - + private Transform _transform = new(); /// diff --git a/Core/Entities/Properties/BoundingBox.cs b/Core/Entities/Properties/BoundingBox.cs index 598d519..f2a9bf6 100644 --- a/Core/Entities/Properties/BoundingBox.cs +++ b/Core/Entities/Properties/BoundingBox.cs @@ -32,7 +32,7 @@ public BoundingBox(Vector3 min, Vector3 max) /// Calculates the bounding box of the game object. /// /// The bounding box of the game object. - public static BoundingBox CalculateBoundingBox(Transform transform) + public static BoundingBox CalculateBoundingBox(ITransform transform) { var min = transform.Position - (transform.Scale / 2); var max = transform.Position + (transform.Scale / 2); diff --git a/Core/Entities/Properties/ITransform.cs b/Core/Entities/Properties/ITransform.cs index 5685511..7f2cb89 100644 --- a/Core/Entities/Properties/ITransform.cs +++ b/Core/Entities/Properties/ITransform.cs @@ -3,7 +3,7 @@ namespace SharpEngine.Core.Entities.Properties; -public interface ITransform where TVector : IVector +public interface ITransform where TVector : IVector, new() { public TVector Position { get; set; } public TVector Scale { get; set; } diff --git a/Core/Entities/Properties/Transform.cs b/Core/Entities/Properties/Transform.cs index c934172..92dc88c 100644 --- a/Core/Entities/Properties/Transform.cs +++ b/Core/Entities/Properties/Transform.cs @@ -8,6 +8,8 @@ namespace SharpEngine.Core.Entities.Properties; /// public class Transform : ITransform { + public Transform() { } + /// /// Gets or sets the position of the game object. /// diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index de5a9cb..a01e899 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -13,7 +13,7 @@ namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : EmptyNode +public class UIElement : EmptyNode { public UIElement() : this("UIElement") { } @@ -30,11 +30,8 @@ public UIElement(string name) : base(name) private readonly UIShader _uiShader = new(); - /// Gets or sets the 2D space transformation of the UI element. - public Transform2D Transform { get; set; } = new(); - /// Gets or sets the mesh of the UI element. - public new Mesh Mesh { get; set; } + public Mesh Mesh { get; set; } //public override Transform2D Transform { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } private uint _vertexArrayObject; diff --git a/Core/Scenes/Scene.cs b/Core/Scenes/Scene.cs index 5d9dd5b..6e4706f 100644 --- a/Core/Scenes/Scene.cs +++ b/Core/Scenes/Scene.cs @@ -42,7 +42,7 @@ public Scene(string sceneFile) /// /// Gets or sets the root node of the scene. /// - public SceneNode Root { get; set; } = new EmptyNode("Root"); + public SceneNode Root { get; set; } = new EmptyNode("Root"); /// Gets or sets the nodes in the scene. private List Nodes { get; set; } = []; @@ -60,7 +60,7 @@ public Scene(string sceneFile) /// The name of the new empty node. public void AddNode(string name) { - var node = new EmptyNode(name); + var node = new EmptyNode(name); Nodes.Add(node); Root.Children.Add(node); } diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 2ed0e80..55d3538 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -6,12 +6,11 @@ namespace SharpEngine.Core.Scenes; -public class EmptyNode : SceneNode where TVector : IVector +public class EmptyNode : SceneNode where TTransform : ITransform, new() where TVector : IVector, new() { public EmptyNode(string name) : base(name) { } - public ITransform Transform { get; set; } - + public virtual TTransform Transform { get; set; } = new(); } /// @@ -19,7 +18,7 @@ public EmptyNode(string name) : base(name) { } /// public abstract class SceneNode { - public static SceneNode Empty => new EmptyNode("Empty Node"); + public static SceneNode Empty => new EmptyNode("Empty Node"); /// /// Gets or sets the name of the node. @@ -46,16 +45,16 @@ public SceneNode(string name) } public virtual SceneNode AddChild(string name) - => AddChild(name); + => AddChild(name); /// /// Adds an empty child node to this node by the given . /// /// The name of the empty node to be added. /// The created node. - public virtual SceneNode AddChild(string name) where TVector : IVector + public virtual SceneNode AddChild(string name) where TTransform : ITransform, new() where TVector : IVector, new() { - var node = new EmptyNode(name) as SceneNode; + var node = new EmptyNode(name) as SceneNode; Children.Add(node!); return node!; From 4877f4618222b06ef48ab056dad1f84e8b0d0f0d Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 5 Apr 2025 13:08:23 +0300 Subject: [PATCH 20/28] UI element ortographic projection --- Core/Entities/GameObject.cs | 5 +-- Core/Entities/Lights/DirectionalLight.cs | 3 +- Core/Entities/Lights/PointLight.cs | 5 +-- Core/Entities/Lights/SpotLight.cs | 5 +-- Core/Entities/Properties/Transform2D.cs | 6 ++-- Core/Entities/UI/Layouts/LayoutBase.cs | 2 ++ Core/Entities/UI/UIElement.cs | 39 +++++++++++++++++++++++- Core/Numerics/IVector.cs | 6 +++- Core/Numerics/Vector2.cs | 2 +- Core/Numerics/Vector2_Intrinsics.cs | 7 +++-- Core/Numerics/Vector3.cs | 2 +- Core/Numerics/Vector3_Intrinsics.cs | 10 +++--- Core/Renderers/Renderer.cs | 4 +-- Core/Renderers/RendererBase.cs | 1 + Core/Renderers/UIRenderer.cs | 18 ++++++----- Core/Scenes/SceneNode.cs | 3 +- Core/Shaders/Shader.cs | 15 +++++++-- Core/Shaders/uiShader.frag | 1 - Core/Shaders/uiShader.vert | 25 +++++++++------ Examples/MinecraftClone/Minecraft.cs | 14 +++++---- 20 files changed, 122 insertions(+), 51 deletions(-) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index 7f61043..d355d7b 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -1,6 +1,7 @@ using SharpEngine.Core.Attributes; using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; @@ -82,7 +83,7 @@ public override Transform Transform public BoundingBox BoundingBox { get; set; } /// - public override Task Render() + public override Task Render(CameraView camera) { Material.DiffuseMap.Use(TextureUnit.Texture0); Material.Shader.SetInt("material.diffuse", Material.diffuseUnit); @@ -101,7 +102,7 @@ public override Task Render() Material.Shader.SetFloat("material.shininess", 0); } - Material.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); + Material.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); Window.GL.DrawArrays(PrimitiveType.Triangles, 0, 36); return Task.CompletedTask; diff --git a/Core/Entities/Lights/DirectionalLight.cs b/Core/Entities/Lights/DirectionalLight.cs index d320965..7b4a495 100644 --- a/Core/Entities/Lights/DirectionalLight.cs +++ b/Core/Entities/Lights/DirectionalLight.cs @@ -1,4 +1,5 @@ using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; @@ -29,7 +30,7 @@ public DirectionalLight() } /// - public override Task Render() + public override Task Render(CameraView camera) { Material.Shader.SetVector3("dirLight.direction", Direction); Material.Shader.SetVector3("dirLight.ambient", Ambient); diff --git a/Core/Entities/Lights/PointLight.cs b/Core/Entities/Lights/PointLight.cs index e63d141..85dbade 100644 --- a/Core/Entities/Lights/PointLight.cs +++ b/Core/Entities/Lights/PointLight.cs @@ -1,4 +1,5 @@ -using SharpEngine.Core.Extensions; +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; using System.Numerics; @@ -51,7 +52,7 @@ public PointLight(Vector3 position, int index) public float Quadratic { get; set; } /// - public override Task Render() + public override Task Render(CameraView camera) { Material.Shader.SetVector3($"pointLights[{_index}].position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3($"pointLights[{_index}].ambient", Ambient); diff --git a/Core/Entities/Lights/SpotLight.cs b/Core/Entities/Lights/SpotLight.cs index e63597d..f66bc55 100644 --- a/Core/Entities/Lights/SpotLight.cs +++ b/Core/Entities/Lights/SpotLight.cs @@ -1,4 +1,5 @@ -using SharpEngine.Core.Extensions; +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; using System.Numerics; @@ -57,7 +58,7 @@ public SpotLight() public float Quadratic { get; set; } /// - public override Task Render() + public override Task Render(CameraView camera) { Material.Shader.SetVector3("spotLight.position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3("spotLight.direction", Direction); diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index 413226c..14c1078 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -11,7 +11,7 @@ public class Transform2D : ITransform /// /// Gets or sets the position of the game object. /// - public Vector2 Position { get; set; } + public Vector2 Position { get; set; } = new(0, 0); /// /// Gets or sets the scale of the game object. @@ -27,6 +27,6 @@ public class Transform2D : ITransform /// Gets the transformation of the game object as a model matrix. /// public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * - Matrix4x4.CreateRotationZ(Rotation.Angle) * - Matrix4x4.CreateTranslation(new Vector3(Position.X * 0.01f, Position.Y * 0.01f, 0)); + Matrix4x4.CreateRotationZ(Rotation.Angle) * + Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); } \ No newline at end of file diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index 948f425..3b584d4 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -15,6 +15,8 @@ namespace SharpEngine.Core.Entities.UI.Layouts; public override LayoutBase AddChild(params SceneNode[] nodes) { + base.AddChild(nodes); + foreach (var node in nodes) AddItem((TItem)node); diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index a01e899..0fc185c 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -1,12 +1,16 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; using Silk.NET.OpenGL; +using System.Numerics; using System.Threading.Tasks; +using Vector2 = SharpEngine.Core.Numerics.Vector2; +using Vector3 = System.Numerics.Vector3; namespace SharpEngine.Core.Entities.UI; @@ -70,16 +74,49 @@ private void InitializeBuffers() /// /// Render the UI element. /// - public override Task Render() + public override Task Render(CameraView camera) { Window.GL.BindVertexArray(_vertexArrayObject); + // TODO: These should come from somewhere else. + const float screenWidth = 1280; + const float screenHeight = 720; + + var orthoMatrix = Matrix4x4.CreateOrthographicOffCenter(0, screenWidth, screenHeight, 0, -1, 1); + + + _uiShader.Shader.SetVector2("screenSize", new System.Numerics.Vector2(screenWidth, screenHeight)); _uiShader.Shader.SetVector2("position", (System.Numerics.Vector2)Transform.Position); _uiShader.Shader.SetFloat("rotation", Transform.Rotation.Angle); _uiShader.Shader.SetVector2("scale", (System.Numerics.Vector2)Transform.Scale); + _uiShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); + _uiShader.Shader.SetMatrix4("orthoMatrix", orthoMatrix); // Pass the orthographic matrix to the shader + + _uiShader.Shader!.SetMatrix4("view", camera.GetViewMatrix()); + _uiShader.Shader!.SetMatrix4("projection", camera.GetProjectionMatrix()); + _uiShader.Shader!.SetVector3("viewPos", camera.Position); + + + var pos = new Vector3(Transform.Position.X, Transform.Position.Y, 0); + var clipSpace = WorldToClipSpace(pos, Transform.ModelMatrix, camera.GetViewMatrix(), camera.GetProjectionMatrix()); + _uiShader.Shader.SetVector4("clipSpace", clipSpace); Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); return Task.CompletedTask; } + + public static Vector4 WorldToClipSpace(Vector3 position, Matrix4x4 model, Matrix4x4 view, Matrix4x4 projection) + { + // Combine all transforms into MVP + Matrix4x4 mvp = model * view * projection; + + // Convert Vector3 to Vector4 for matrix multiplication + Vector4 worldPos = new Vector4(position, 1.0f); + + // Transform to clip space (i.e., ready to assign to gl_Position) + Vector4 clipSpacePos = Vector4.Transform(worldPos, mvp); + + return clipSpacePos; + } } diff --git a/Core/Numerics/IVector.cs b/Core/Numerics/IVector.cs index 9fe8509..b6f41dd 100644 --- a/Core/Numerics/IVector.cs +++ b/Core/Numerics/IVector.cs @@ -1,5 +1,9 @@ -namespace SharpEngine.Core.Numerics; +using System; + +namespace SharpEngine.Core.Numerics; public interface IVector { + public Single X { get; set; } + public Single Y { get; set; } } \ No newline at end of file diff --git a/Core/Numerics/Vector2.cs b/Core/Numerics/Vector2.cs index 231abec..6423d16 100644 --- a/Core/Numerics/Vector2.cs +++ b/Core/Numerics/Vector2.cs @@ -17,7 +17,7 @@ namespace SharpEngine.Core.Numerics /// /// A structure encapsulating two single precision floating point values and provides hardware accelerated methods. /// - public partial struct Vector2 : IVector, IEquatable, IFormattable + public partial struct Vector2 : IEquatable, IFormattable { #region Public Static Properties /// diff --git a/Core/Numerics/Vector2_Intrinsics.cs b/Core/Numerics/Vector2_Intrinsics.cs index 6619057..6a04d6c 100644 --- a/Core/Numerics/Vector2_Intrinsics.cs +++ b/Core/Numerics/Vector2_Intrinsics.cs @@ -12,16 +12,17 @@ namespace SharpEngine.Core.Numerics // The JIT recognizes these methods and properties by name and signature: if either is changed, the JIT will no longer recognize the member. // Some methods declared here are not strictly intrinsic, but delegate to an intrinsic method. For example, only one overload of CopyTo() - public partial struct Vector2 + public partial struct Vector2 : IVector { /// /// The X component of the vector. /// - public Single X; + public Single X { get; set; } + /// /// The Y component of the vector. /// - public Single Y; + public Single Y { get; set; } #region Constructors /// diff --git a/Core/Numerics/Vector3.cs b/Core/Numerics/Vector3.cs index bb289f8..1a1e2b7 100644 --- a/Core/Numerics/Vector3.cs +++ b/Core/Numerics/Vector3.cs @@ -16,7 +16,7 @@ namespace SharpEngine.Core.Numerics /// /// A structure encapsulating three single precision floating point values and provides hardware accelerated methods. /// - public partial struct Vector3 : IVector, IEquatable, IFormattable + public partial struct Vector3 : IEquatable, IFormattable { #region Public Static Properties /// diff --git a/Core/Numerics/Vector3_Intrinsics.cs b/Core/Numerics/Vector3_Intrinsics.cs index 71456df..6a6e7f5 100644 --- a/Core/Numerics/Vector3_Intrinsics.cs +++ b/Core/Numerics/Vector3_Intrinsics.cs @@ -12,20 +12,22 @@ namespace SharpEngine.Core.Numerics // Some methods declared here are not strictly intrinsic, but delegate to an intrinsic method. For example, only one overload of CopyTo() // is actually recognized by the JIT, but both are here for simplicity. - public partial struct Vector3 + public partial struct Vector3 : IVector { /// /// The X component of the vector. /// - public Single X; + public Single X { get; set; } + /// /// The Y component of the vector. /// - public Single Y; + public Single Y { get; set; } + /// /// The Z component of the vector. /// - public Single Z; + public Single Z { get; set; } #region Constructors /// diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index a6f0401..77752ad 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -49,7 +49,7 @@ public Renderer(CameraView camera, ISettings settings, Scene scene) : base(setti /// public override Task Render() { - //return Task.CompletedTask; + return Task.CompletedTask; try { Window.GL.Enable(EnableCap.DepthTest); @@ -82,7 +82,7 @@ private Task RenderGameObject(SceneNode node) return Task.CompletedTask; // TODO: Skip blocks that are behind others relative to the camera - return gameObject.Render(); + return gameObject.Render(_camera); } private static bool IsInViewFrustum(BoundingBox boundingBox, CameraView camera) diff --git a/Core/Renderers/RendererBase.cs b/Core/Renderers/RendererBase.cs index c36bd03..1491fab 100644 --- a/Core/Renderers/RendererBase.cs +++ b/Core/Renderers/RendererBase.cs @@ -1,3 +1,4 @@ +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using System; using System.Threading.Tasks; diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 55513ef..ff9c067 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -1,14 +1,18 @@ -using SharpEngine.Core.Entities.UI; +using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; +using Silk.NET.Input; using Silk.NET.OpenGL; using System; using System.Linq; +using System.Numerics; using System.Threading.Tasks; +using static Silk.NET.Core.Native.WinString; namespace SharpEngine.Core.Renderers; @@ -42,15 +46,13 @@ public override Task Render() Window.GL.Enable(EnableCap.DepthTest); Window.GL.DepthFunc(DepthFunction.Less); - _uiShader.Shader.Use(); + // Disable face culling to render both sides of the quad + Window.GL.Disable(EnableCap.CullFace); - var objects = _scene.GetObjectsOfType(); // TODO: This is really unoptimized, need to figure out a better solution. - foreach (var item in objects) - { - item.Bind(); - } + // _camera.SetShaderUniforms(_uiShader.Shader!); + _uiShader.Shader.Use(); - var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render()); + var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera)); return Task.WhenAll(uiElementRenderTasks); } diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 55d3538..a0ce9b8 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Entities; using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; using System.Collections.Generic; using System.Threading.Tasks; @@ -86,5 +87,5 @@ public void RemoveChild(SceneNode node) /// Renders the current object to the screen. /// /// A representing an asynchronous operation. - public virtual Task Render() => Task.CompletedTask; + public virtual Task Render(CameraView camera) => Task.CompletedTask; } diff --git a/Core/Shaders/Shader.cs b/Core/Shaders/Shader.cs index 1b7b160..0197abb 100644 --- a/Core/Shaders/Shader.cs +++ b/Core/Shaders/Shader.cs @@ -268,6 +268,12 @@ public void SetMatrix4(string name, Matrix4x4 data, bool transpose = true) TrySetUniform(name, data, (uniform, d) => Window.GL.UniformMatrix4(uniform, transpose, d.ToSpan())); } + public void SetVector2(string name, Vector2 data) + { + Window.GL.UseProgram(Handle); + TrySetUniform(name, data, Window.GL.Uniform2); + } + /// /// Set a uniform Vector3 on this shader. /// @@ -279,9 +285,14 @@ public void SetVector3(string name, Vector3 data) TrySetUniform(name, data, Window.GL.Uniform3); } - public void SetVector2(string name, Vector2 data) + /// + /// Set a uniform Vector3 on this shader. + /// + /// The name of the uniform. + /// The data to set. + public void SetVector4(string name, Vector4 data) { Window.GL.UseProgram(Handle); - TrySetUniform(name, data, Window.GL.Uniform2); + TrySetUniform(name, data, Window.GL.Uniform4); } } diff --git a/Core/Shaders/uiShader.frag b/Core/Shaders/uiShader.frag index 8e4b912..1cc0a9e 100644 --- a/Core/Shaders/uiShader.frag +++ b/Core/Shaders/uiShader.frag @@ -6,6 +6,5 @@ in vec2 TexCoords; void main() { - // FragColor = vec4(1.0, 0.0, 0.0, 1.0); // set all 4 vector values to 1.0 FragColor = texture(texture1, TexCoords); } \ No newline at end of file diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index 707a1f7..dd21a8d 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -7,6 +7,14 @@ layout (location = 2) in vec2 aTexCoords; uniform vec2 position; uniform float rotation; uniform vec2 scale; +uniform vec2 screenSize; +uniform mat4 clipSpace; +uniform mat4 orthoMatrix; + +uniform mat4 view; +uniform mat4 projection; +uniform mat4 model; +uniform vec3 viewPos; out vec3 Normal; out vec3 FragPos; @@ -14,17 +22,14 @@ out vec2 TexCoords; void main() { - // Create a 2D rotation matrix - mat2 rot = mat2(cos(rotation), -sin(rotation), - sin(rotation), cos(rotation)); - - // Apply the rotation, scaling, and translation - vec2 transformedPos = (rot * aPos.xy) * scale + (position * 0.01); + // Calculate the model-view-projection matrix using the orthographic projection matrix + mat4 mvp = projection * view * model; - // Set the final position in clip space - gl_Position = vec4(transformedPos, aPos.z, 1.0); + // Apply the MVP matrix to the vertex position with scaling + gl_Position = model * orthoMatrix * vec4(aPos, 1); - FragPos = vec3(transformedPos, aPos.z); - Normal = aNormal; // No need to transform normals for 2D UI elements + // Pass through other attributes + Normal = mat3(model) * aNormal; // Transform the normal + FragPos = vec3(model * vec4(aPos, 1.0)); // Transform the fragment position TexCoords = aTexCoords; } \ No newline at end of file diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 4f6fe91..70e4481 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -64,17 +64,19 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); - //_uiElem.Transform.Position = new Vector2(-30, 0); + _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.5f, 0.5f); + _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(0.5f, 0); _scene.UIElements.Add(_uiElem); - var uiElem2 = new UIElement("uiElement"); - uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); + //var uiElem2 = new UIElement("uiElement"); + //uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); //uiElem2.Transform.Position = new Vector2(30, 0); - gridLayout.AddChild(_uiElem, uiElem2); + // gridLayout.AddChild(_uiElem, uiElem2); + _scene.UIElements.Add(_uiElem); + // _scene.UIElements.Add(uiElem2); - _scene.UIElements.Add(gridLayout); + //_scene.UIElements.Add(gridLayout); InitializeWorld(); } From b64d9174c11d345310c250eedb0bc27c0c7ba415 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 5 Apr 2025 14:18:27 +0300 Subject: [PATCH 21/28] Translating somewhat works --- Core/Entities/Properties/Transform2D.cs | 5 +++-- Core/Entities/UI/UIElement.cs | 2 +- Core/Shaders/uiShader.vert | 7 +++++-- Examples/MinecraftClone/Minecraft.cs | 25 +++++++++++++++++++++++-- Examples/MinecraftClone/Program.cs | 1 + 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index 14c1078..b1339d8 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -21,12 +21,13 @@ public class Transform2D : ITransform /// /// Gets or sets the rotation of the game object. /// - public Quaternion Rotation { get; set; } = new(); + public Quaternion Rotation { get; set; } = new() { Angle = 0 }; /// /// Gets the transformation of the game object as a model matrix. /// public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * Matrix4x4.CreateRotationZ(Rotation.Angle) * - Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + //Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + Matrix4x4.CreateTranslation(new Vector3(0, 0, 0)); } \ No newline at end of file diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 0fc185c..a2af4ec 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -82,7 +82,7 @@ public override Task Render(CameraView camera) const float screenWidth = 1280; const float screenHeight = 720; - var orthoMatrix = Matrix4x4.CreateOrthographicOffCenter(0, screenWidth, screenHeight, 0, -1, 1); + var orthoMatrix = Matrix4x4.CreateOrthographicOffCenter(-1, 1, -1, 1, -1, 1); _uiShader.Shader.SetVector2("screenSize", new System.Numerics.Vector2(screenWidth, screenHeight)); diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index dd21a8d..ae38486 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -22,11 +22,14 @@ out vec2 TexCoords; void main() { + float reciprScaleOnscreen = 0.001f; + // Calculate the model-view-projection matrix using the orthographic projection matrix - mat4 mvp = projection * view * model; + //mat4 mvp = projection * view * model; + vec4 mvp = model * vec4(1.0) * orthoMatrix; // Apply the MVP matrix to the vertex position with scaling - gl_Position = model * orthoMatrix * vec4(aPos, 1); + gl_Position = mvp * vec4(aPos.x + (position.x * reciprScaleOnscreen), aPos.y + (position.y * reciprScaleOnscreen), aPos.z, 1); // Pass through other attributes Normal = mat3(model) * aNormal; // Transform the normal diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 70e4481..b84ede9 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -14,6 +14,7 @@ using SharpEngine.Core.Windowing; using SharpEngine.Core.Entities.UI.Layouts; using SharpEngine.Core.Entities.Properties; +using ImGuiNET; namespace Minecraft; @@ -34,7 +35,7 @@ public class Minecraft : Game private UIElement _uiElem; - public Window Window { get; set; } + public Window? Window { get; set; } /// /// Initializes a new instance of the . @@ -45,6 +46,7 @@ public Minecraft(Scene scene, ISettings settings) CoreSettings = settings; _inventory = new Inventory(); + } /// @@ -65,7 +67,8 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.5f, 0.5f); - _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(0.5f, 0); + _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(2000, 0); + _uiElem.Transform.Rotation.Angle = 90; _scene.UIElements.Add(_uiElem); //var uiElem2 = new UIElement("uiElement"); @@ -86,6 +89,24 @@ public override void Initialize() } } + public void OnAfterRender(double frameTime) + { + var x = _uiElem.Transform.Position.X; + var y = _uiElem.Transform.Position.Y; + + ImGui.Begin("Debug"); + ImGui.Text($"FPS: {frameTime }"); + ImGui.Text($"Camera position: {Camera.Position}"); + ImGui.Text($"UI Element position: {_uiElem.Transform.Position}"); + + ImGui.SliderFloat("X", ref x, -1000, 1000); + ImGui.SliderFloat("Y", ref y, -1000, 1000); + + ImGui.End(); + + _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(x, y); + } + private void InitializeWorld() { InitializeLights(); diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs index a753b62..46a0d1b 100644 --- a/Examples/MinecraftClone/Program.cs +++ b/Examples/MinecraftClone/Program.cs @@ -25,6 +25,7 @@ private static void Main() window.OnHandleKeyboard += game.HandleKeyboard; window.OnButtonMouseDown += game.HandleMouseDown; window.HandleMouseWheel += game.HandleMouseWheel; + window.OnAfterRender += game.OnAfterRender; // TODO: This needs to be streamlined. game.Window = window; From 7793ffcb0d70b301758f77920964a0c440c0b504 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sat, 5 Apr 2025 16:15:35 +0300 Subject: [PATCH 22/28] UI transform behaving correctly? --- Core/Entities/Properties/Transform2D.cs | 5 +++-- Core/Entities/UI/UIElement.cs | 2 +- Core/Shaders/uiShader.vert | 27 +++++++++++++++++++------ Core/Windowing/Frame.cs | 18 +++++++++++++++++ Examples/MinecraftClone/Minecraft.cs | 24 ++++++++++++++++------ 5 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 Core/Windowing/Frame.cs diff --git a/Core/Entities/Properties/Transform2D.cs b/Core/Entities/Properties/Transform2D.cs index b1339d8..8a44d56 100644 --- a/Core/Entities/Properties/Transform2D.cs +++ b/Core/Entities/Properties/Transform2D.cs @@ -27,7 +27,8 @@ public class Transform2D : ITransform /// Gets the transformation of the game object as a model matrix. /// public Matrix4x4 ModelMatrix => Matrix4x4.CreateScale(new Vector3(Scale.X, Scale.Y, 0)) * - Matrix4x4.CreateRotationZ(Rotation.Angle) * - //Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + // Matrix4x4.CreateRotationZ(Math.DegreesToRadians(Rotation.Angle)) * + Matrix4x4.CreateRotationZ(0) * + // Matrix4x4.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); Matrix4x4.CreateTranslation(new Vector3(0, 0, 0)); } \ No newline at end of file diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index a2af4ec..3f3fecd 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -87,7 +87,7 @@ public override Task Render(CameraView camera) _uiShader.Shader.SetVector2("screenSize", new System.Numerics.Vector2(screenWidth, screenHeight)); _uiShader.Shader.SetVector2("position", (System.Numerics.Vector2)Transform.Position); - _uiShader.Shader.SetFloat("rotation", Transform.Rotation.Angle); + _uiShader.Shader.SetFloat("rotation", Math.DegreesToRadians(Transform.Rotation.Angle)); _uiShader.Shader.SetVector2("scale", (System.Numerics.Vector2)Transform.Scale); _uiShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); _uiShader.Shader.SetMatrix4("orthoMatrix", orthoMatrix); // Pass the orthographic matrix to the shader diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index ae38486..36a425e 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -22,14 +22,29 @@ out vec2 TexCoords; void main() { - float reciprScaleOnscreen = 0.001f; + float reciprScaleOnscreen = 0.005f; - // Calculate the model-view-projection matrix using the orthographic projection matrix - //mat4 mvp = projection * view * model; - vec4 mvp = model * vec4(1.0) * orthoMatrix; + // Calculate the aspect ratio + float aspectRatio = screenSize.x / screenSize.y; + + // Create the rotation matrix for the z-axis + mat4 rotationMatrix = mat4( + cos(rotation), -sin(rotation), 0.0, 0.0, + sin(rotation), cos(rotation), 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); - // Apply the MVP matrix to the vertex position with scaling - gl_Position = mvp * vec4(aPos.x + (position.x * reciprScaleOnscreen), aPos.y + (position.y * reciprScaleOnscreen), aPos.z, 1); + // Calculate the model-view-projection matrix using the orthographic projection matrix + mat4 mvp = orthoMatrix * rotationMatrix * model; + + // Apply the MVP matrix to the vertex position with scaling and aspect ratio + gl_Position = mvp * vec4( + (aPos.x + (position.x * reciprScaleOnscreen)) * aspectRatio, + aPos.y + (position.y * reciprScaleOnscreen), + aPos.z, + 1 + ); // Pass through other attributes Normal = mat3(model) * aNormal; // Transform the normal diff --git a/Core/Windowing/Frame.cs b/Core/Windowing/Frame.cs new file mode 100644 index 0000000..a15969a --- /dev/null +++ b/Core/Windowing/Frame.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Windowing; + +public class Frame +{ + public Frame(double frameTime) + { + FrameTime = frameTime; + } + + public double FrameTime { get; set; } + public float FrameRate => (float)(1 / FrameTime); +} diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index b84ede9..d917363 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -66,9 +66,6 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.5f, 0.5f); - _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(2000, 0); - _uiElem.Transform.Rotation.Angle = 90; _scene.UIElements.Add(_uiElem); //var uiElem2 = new UIElement("uiElement"); @@ -94,17 +91,32 @@ public void OnAfterRender(double frameTime) var x = _uiElem.Transform.Position.X; var y = _uiElem.Transform.Position.Y; + var sx = _uiElem.Transform.Scale.X; + var sy = _uiElem.Transform.Scale.Y; + + var rotation = _uiElem.Transform.Rotation.Angle; + ImGui.Begin("Debug"); ImGui.Text($"FPS: {frameTime }"); ImGui.Text($"Camera position: {Camera.Position}"); ImGui.Text($"UI Element position: {_uiElem.Transform.Position}"); - - ImGui.SliderFloat("X", ref x, -1000, 1000); - ImGui.SliderFloat("Y", ref y, -1000, 1000); + + ImGui.SliderFloat("X", ref x, -2000, 2000); + ImGui.SliderFloat("Y", ref y, -2000, 2000); + + ImGui.Text($"UI Element scale: {_uiElem.Transform.Scale}"); + + ImGui.SliderFloat("sX", ref sx, 0, 1); + ImGui.SliderFloat("sY", ref sy, 0, 1); + + ImGui.Text($"UI Element scale: {_uiElem.Transform.Scale}"); + ImGui.SliderFloat("rotation", ref rotation, 0, 360); ImGui.End(); _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(x, y); + _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(sx, sy); + _uiElem.Transform.Rotation.Angle = rotation; } private void InitializeWorld() From 0ba636e0082b2904fc89d5eeeac22033a3817ffe Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 6 Apr 2025 21:04:50 +0300 Subject: [PATCH 23/28] Add documentation / general enhancements --- Core/{Interfaces => Abstractions}/IGame.cs | 0 Core/Abstractions/IRenderable.cs | 28 +++++++ .../{Interfaces => Abstractions}/ISettings.cs | 3 + Core/Entities/BezierCurve.cs | 29 ++++--- Core/Entities/Properties/BoundingBox.cs | 1 + Core/Entities/Properties/ITransform.cs | 11 +++ Core/Entities/Properties/Material.cs | 1 + Core/Entities/Properties/Meshes/Mesh.cs | 3 + .../Entities/Properties/Meshes/MeshService.cs | 77 +++++++++++++++++-- .../Entities/Properties/Meshes/ObjMeshData.cs | 22 ++++++ Core/Entities/Properties/Transform.cs | 3 + Core/Entities/UI/Layouts/FlexLayout.cs | 12 +-- Core/Entities/UI/Layouts/LayoutBase.cs | 31 +++++--- Core/Numerics/IVector.cs | 6 ++ Core/Numerics/Vector2.cs | 6 +- Core/Numerics/Vector2_Intrinsics.cs | 8 ++ Core/Numerics/Vector3.cs | 4 +- Core/Numerics/Vector3_Intrinsics.cs | 8 ++ Core/Renderers/Renderer.cs | 2 +- Core/Scenes/SceneNode.cs | 20 ++++- Core/Shaders/Shader.cs | 5 ++ Core/Shaders/uiShader.vert | 5 -- Core/Windowing/Frame.cs | 18 +++-- Core/_Resources/Default.cs | 14 ++-- Editor/EditorWindow.cs | 48 +++++++----- Editor/Windows/ContextMenuWindow.cs | 2 +- Editor/Windows/ImGuiWindowBase.cs | 3 + Examples/MinecraftClone/Minecraft.cs | 21 ++++- Examples/MinecraftClone/Program.cs | 3 +- 29 files changed, 314 insertions(+), 80 deletions(-) rename Core/{Interfaces => Abstractions}/IGame.cs (100%) create mode 100644 Core/Abstractions/IRenderable.cs rename Core/{Interfaces => Abstractions}/ISettings.cs (90%) create mode 100644 Core/Entities/Properties/Meshes/ObjMeshData.cs diff --git a/Core/Interfaces/IGame.cs b/Core/Abstractions/IGame.cs similarity index 100% rename from Core/Interfaces/IGame.cs rename to Core/Abstractions/IGame.cs diff --git a/Core/Abstractions/IRenderable.cs b/Core/Abstractions/IRenderable.cs new file mode 100644 index 0000000..6b00f6f --- /dev/null +++ b/Core/Abstractions/IRenderable.cs @@ -0,0 +1,28 @@ +using SharpEngine.Core.Entities.Properties.Meshes; + +namespace SharpEngine.Core.Interfaces; + +// TODO: Make an abstract class and move Render method here? + +/// +/// Contains definitions for rendering objects. +/// +public interface IRenderable +{ + /// + /// Gets or sets the vertex array object of the object. + /// + public uint VAO { get; set; } + + /// + /// Initializes the necessary buffers for the object. + /// + /// The mesh that should the buffers should be initialized with. + public void InitializeBuffers(Mesh mesh); + + /// + /// Binds the VAO to the current context. + /// + public void Bind(); + +} diff --git a/Core/Interfaces/ISettings.cs b/Core/Abstractions/ISettings.cs similarity index 90% rename from Core/Interfaces/ISettings.cs rename to Core/Abstractions/ISettings.cs index 17aa676..23888d2 100644 --- a/Core/Interfaces/ISettings.cs +++ b/Core/Abstractions/ISettings.cs @@ -22,7 +22,10 @@ public interface ISettings /// Gets or sets which renderer should be enabled for the current window. public RenderFlags RendererFlags { get; set; } + /// Gets or sets the primary mouse button. public MouseButton PrimaryButton { get; set; } + + /// Gets or sets the secondary mouse button. public MouseButton SecondaryButton { get; set; } } diff --git a/Core/Entities/BezierCurve.cs b/Core/Entities/BezierCurve.cs index aba3b70..cd2547c 100644 --- a/Core/Entities/BezierCurve.cs +++ b/Core/Entities/BezierCurve.cs @@ -1,20 +1,27 @@ using Silk.NET.Maths; using System; using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Text; -using System.Threading.Tasks; namespace SharpEngine.Core.Entities; + +/// +/// Represents a cubic Bezier curve. +/// public class BezierCurve { private Vector3D P0, P1, P2, P3; + + /// Gets the start point of the curve. public Vector3D Start => P0; - public Vector3D End => P3; - public List> Points { get; set; } + /// Gets the end point of the curve. + public Vector3D End => P3; + + /// + /// Initializes a new instance of . + /// + /// The point where the curve should be created. public BezierCurve(Vector3D initializationPosition) { const float defaultWidth = 1; @@ -23,11 +30,14 @@ public BezierCurve(Vector3D initializationPosition) P1 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y + defaultWidth, initializationPosition.Z); P2 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y - defaultWidth, initializationPosition.Z); P3 = new Vector3D(initializationPosition.X + defaultWidth, initializationPosition.Y, initializationPosition.Z); - - Points = GetPoints(20); } - public List> GetPoints(int resolution) + /// + /// Gets the points of the curve. + /// + /// The distance between each point, defines the roughness of the curve. + /// The points for the curve with the given . + public List> GetPoints(uint resolution) { var points = new List>(); for (var i = 0; i <= resolution; i++) @@ -35,6 +45,7 @@ public List> GetPoints(int resolution) var t = i / (float)resolution; points.Add(GetPointAt(t)); } + return points; } diff --git a/Core/Entities/Properties/BoundingBox.cs b/Core/Entities/Properties/BoundingBox.cs index f2a9bf6..e0a4e67 100644 --- a/Core/Entities/Properties/BoundingBox.cs +++ b/Core/Entities/Properties/BoundingBox.cs @@ -28,6 +28,7 @@ public BoundingBox(Vector3 min, Vector3 max) Max = max; } + // TODO: This should be calculated by the mesh. /// /// Calculates the bounding box of the game object. /// diff --git a/Core/Entities/Properties/ITransform.cs b/Core/Entities/Properties/ITransform.cs index 7f2cb89..a3b5dbd 100644 --- a/Core/Entities/Properties/ITransform.cs +++ b/Core/Entities/Properties/ITransform.cs @@ -3,10 +3,21 @@ namespace SharpEngine.Core.Entities.Properties; +/// +/// Represents the transform of an object in dimensions. +/// +/// public interface ITransform where TVector : IVector, new() { + /// Gets or set the the position of the object. public TVector Position { get; set; } + + /// Gets or set the the scale of the object. public TVector Scale { get; set; } + + /// Gets or set the the rotation of the object. public Quaternion Rotation { get; set; } + + /// Gets or set the the model matrix of the object. public Matrix4x4 ModelMatrix { get; } } \ No newline at end of file diff --git a/Core/Entities/Properties/Material.cs b/Core/Entities/Properties/Material.cs index b744d51..5f9083e 100644 --- a/Core/Entities/Properties/Material.cs +++ b/Core/Entities/Properties/Material.cs @@ -32,6 +32,7 @@ public Material(Shader shader, Texture diffuseMap, Texture? specularMap = null) /// public Texture SpecularMap { get; set; } + /// Gets whether the material uses a specular map. public bool UseSpecularMap => SpecularMap.Handle != DiffuseMap.Handle; /// diff --git a/Core/Entities/Properties/Meshes/Mesh.cs b/Core/Entities/Properties/Meshes/Mesh.cs index 88ca01c..dcd8d94 100644 --- a/Core/Entities/Properties/Meshes/Mesh.cs +++ b/Core/Entities/Properties/Meshes/Mesh.cs @@ -28,6 +28,9 @@ public class Mesh [Inspector(DisplayInInspector = false)] public float[] TextureCoordinates { get; set; } = []; + /// + /// Gets or sets the indicies of the mesh. + /// [Inspector(DisplayInInspector = false)] public uint[] Indices { get; set; } = []; } \ No newline at end of file diff --git a/Core/Entities/Properties/Meshes/MeshService.cs b/Core/Entities/Properties/Meshes/MeshService.cs index e884bf7..9b34f87 100644 --- a/Core/Entities/Properties/Meshes/MeshService.cs +++ b/Core/Entities/Properties/Meshes/MeshService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace SharpEngine.Core.Entities.Properties.Meshes; @@ -42,22 +43,86 @@ public Mesh LoadMesh(string identifier, Mesh mesh) const string FbxExtension = ".fbx"; const string ObjExtension = ".obj"; - public Mesh LoadMesh(string identifier, string meshFilePath) + /// + /// Loads a mesh based on the file extension of the provided path. + /// + /// Used to specify the unique identifier for the mesh being loaded. + /// Specifies the file path of the mesh to be loaded, which determines the loading method based on its extension. + /// A collection of objects loaded from the specified file. + /// Thrown when the file extension of the provided path is not recognized as a supported mesh format. + public IEnumerable LoadMesh(string identifier, string meshFilePath) => Path.GetExtension(meshFilePath) switch { - FbxExtension => LoadFbxMesh(identifier, meshFilePath), - ObjExtension => LoadObjMesh(identifier, meshFilePath), + FbxExtension => LoadFbx(identifier, meshFilePath), + ObjExtension => LoadObj(identifier, meshFilePath), _ => throw new NotSupportedException($"The file extension {Path.GetExtension(meshFilePath)} is not a supported mesh file.") }; // TODO: #2 Load obj mesh from file - private Mesh LoadObjMesh(string identifier, string meshFilePath) + private IEnumerable LoadObj(string identifier, string meshFilePath) { - throw new NotImplementedException(); + var vertices = new List(); + var normals = new List(); + var textureCoordinates = new List(); + var indices = new List(); + + Mesh LoadMeshIntoBuffer() + { + var mesh = LoadMesh(identifier, new Mesh + { + Vertices = [.. vertices], + Normals = [.. normals], + TextureCoordinates = [.. textureCoordinates], + Indices = [.. indices] + }); + + vertices.Clear(); + normals.Clear(); + textureCoordinates.Clear(); + indices.Clear(); + + return mesh; + } + + static IEnumerable GetData(string[] parts) => parts.Skip(1).Select(float.Parse); + + var contents = File.ReadAllLines(meshFilePath); + foreach (var line in contents) + { + var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 0) continue; + + switch (parts[0]) + { + case ObjMeshData.VERTEX: + vertices.AddRange(GetData(parts)); + break; + case ObjMeshData.VERTEX_NORMAL: + normals.AddRange(GetData(parts)); + break; + case ObjMeshData.VERTEX_TEXTURE: + textureCoordinates.AddRange(GetData(parts)); + break; + case ObjMeshData.FACE: // OBJ faces are defined as a list of vertex indices + foreach (var part in parts.Skip(1)) + { + var indicesParts = part.Split('/'); + indices.Add(uint.Parse(indicesParts[0]) - 1); // OBJ indices are 1-based + } + break; + case ObjMeshData.OBJECT: + if (vertices.Count > 0) + yield return LoadMeshIntoBuffer(); + break; + } + } + + if (vertices.Count > 0) + yield return LoadMeshIntoBuffer(); } // TODO: #3 Load fbx mesh from file - private Mesh LoadFbxMesh(string identifier, string meshFilePath) + private IEnumerable LoadFbx(string identifier, string meshFilePath) { throw new NotImplementedException(); } diff --git a/Core/Entities/Properties/Meshes/ObjMeshData.cs b/Core/Entities/Properties/Meshes/ObjMeshData.cs new file mode 100644 index 0000000..e1e45de --- /dev/null +++ b/Core/Entities/Properties/Meshes/ObjMeshData.cs @@ -0,0 +1,22 @@ +namespace SharpEngine.Core.Entities.Properties.Meshes; + +/// +/// Contains the different types of data the .obj file contains. +/// +public static class ObjMeshData +{ + /// Represents a line of vertex data. + public const string VERTEX = "v"; + + /// Represents a line of vertex normal data. + public const string VERTEX_NORMAL = "vn"; + + /// Represents a line of uv coordinate data. + public const string VERTEX_TEXTURE = "vt"; + + /// Represents a line of vertex indicie data. + public const string FACE = "f"; + + /// Represents that a new object begins. + public const string OBJECT = "o"; +} \ No newline at end of file diff --git a/Core/Entities/Properties/Transform.cs b/Core/Entities/Properties/Transform.cs index 92dc88c..81f0467 100644 --- a/Core/Entities/Properties/Transform.cs +++ b/Core/Entities/Properties/Transform.cs @@ -8,6 +8,9 @@ namespace SharpEngine.Core.Entities.Properties; /// public class Transform : ITransform { + /// + /// Initializes a new instance of the class. + /// public Transform() { } /// diff --git a/Core/Entities/UI/Layouts/FlexLayout.cs b/Core/Entities/UI/Layouts/FlexLayout.cs index d65776e..b0dded0 100644 --- a/Core/Entities/UI/Layouts/FlexLayout.cs +++ b/Core/Entities/UI/Layouts/FlexLayout.cs @@ -1,4 +1,5 @@ -using SharpEngine.Core.Scenes; +using SharpEngine.Core.Numerics; +using SharpEngine.Core.Scenes; using System; using System.Numerics; @@ -8,13 +9,14 @@ namespace SharpEngine.Core.Entities.UI.Layouts; /// Represents a layout that dynamically distributes items in a flex layout. /// /// The type of items in the collection. -public class FlexLayout : LayoutBase where T : SceneNode, new() +/// The amount of dimensions in the content area. +public class FlexLayout : LayoutBase where T : SceneNode, new() where TVector : IVector, new() { + /// Gets or sets the gap between each object. public int Gap { get; set; } - public int ContentSize { get; set; } - - public Vector2 ContentAreaSize { get; set; } + /// Gets or sets the dimensions of the content area. + public required IVector ContentAreaSize { get; set; } /// /// diff --git a/Core/Entities/UI/Layouts/LayoutBase.cs b/Core/Entities/UI/Layouts/LayoutBase.cs index 3b584d4..bb65554 100644 --- a/Core/Entities/UI/Layouts/LayoutBase.cs +++ b/Core/Entities/UI/Layouts/LayoutBase.cs @@ -1,18 +1,22 @@ -using SharpEngine.Core.Entities.Properties; -using SharpEngine.Core.Numerics; -using SharpEngine.Core.Scenes; -using System; +using SharpEngine.Core.Scenes; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpEngine.Core.Entities.UI.Layouts; +/// +/// Represents the base class for all layout types. +/// +/// public abstract class LayoutBase : SceneNode where TItem : SceneNode, new() { + /// Represents the items in the layout. public List Items { get; set; } = []; + /// + /// Adds a new item to the layout. + /// + /// The items to be added. + /// The object itself. public override LayoutBase AddChild(params SceneNode[] nodes) { base.AddChild(nodes); @@ -23,15 +27,18 @@ public override LayoutBase AddChild(params SceneNode[] nodes) return this; } + /// + /// Adds and item to the container. + /// + /// The item to be added. public virtual void AddItem(TItem item) { Items.Add(item); } - public virtual bool RemoveItem(TItem item) - { - return Items.Remove(item); - } - + /// + /// Gets the items in the container. + /// + /// The items as a 2D array representation. public abstract TItem[][] GetValues(); } diff --git a/Core/Numerics/IVector.cs b/Core/Numerics/IVector.cs index b6f41dd..c581a9b 100644 --- a/Core/Numerics/IVector.cs +++ b/Core/Numerics/IVector.cs @@ -2,8 +2,14 @@ namespace SharpEngine.Core.Numerics; +/// +/// An abstraction of the different vector types. +/// public interface IVector { + /// public Single X { get; set; } + + /// public Single Y { get; set; } } \ No newline at end of file diff --git a/Core/Numerics/Vector2.cs b/Core/Numerics/Vector2.cs index 6423d16..70b52ef 100644 --- a/Core/Numerics/Vector2.cs +++ b/Core/Numerics/Vector2.cs @@ -56,9 +56,9 @@ public override int GetHashCode() /// The Object to compare against. /// True if the Object is equal to this Vector2; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (!(obj is Vector2)) + if (obj is not Vector2) return false; return Equals((Vector2)obj); } @@ -89,7 +89,7 @@ public string ToString(string format) /// The format of individual elements. /// The format provider to use when formatting elements. /// The string representation. - public string ToString(string format, IFormatProvider formatProvider) + public string ToString(string? format, IFormatProvider? formatProvider) { StringBuilder sb = new StringBuilder(); string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; diff --git a/Core/Numerics/Vector2_Intrinsics.cs b/Core/Numerics/Vector2_Intrinsics.cs index 6a04d6c..2ac4fb6 100644 --- a/Core/Numerics/Vector2_Intrinsics.cs +++ b/Core/Numerics/Vector2_Intrinsics.cs @@ -295,9 +295,17 @@ public static Vector2 SquareRoot(Vector2 value) return !(left == right); } + /// + /// Converts the SharpEngine representation to a System.Numerics representation. + /// + /// The value to convert public static explicit operator System.Numerics.Vector2(Vector2 value) => new(value.X, value.Y); + /// + /// Converts the System.Numerics representation to a SharpEngine representation. + /// + /// The value to convert public static explicit operator Vector2(System.Numerics.Vector2 value) => new(value.X, value.Y); diff --git a/Core/Numerics/Vector3.cs b/Core/Numerics/Vector3.cs index 1a1e2b7..f34601a 100644 --- a/Core/Numerics/Vector3.cs +++ b/Core/Numerics/Vector3.cs @@ -61,7 +61,7 @@ public override int GetHashCode() /// The Object to compare against. /// True if the Object is equal to this Vector3; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (!(obj is Vector3)) return false; @@ -94,7 +94,7 @@ public string ToString(string format) /// The format of individual elements. /// The format provider to use when formatting elements. /// The string representation. - public string ToString(string format, IFormatProvider formatProvider) + public string ToString(string? format, IFormatProvider? formatProvider) { StringBuilder sb = new StringBuilder(); string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; diff --git a/Core/Numerics/Vector3_Intrinsics.cs b/Core/Numerics/Vector3_Intrinsics.cs index 6a6e7f5..5cccbcc 100644 --- a/Core/Numerics/Vector3_Intrinsics.cs +++ b/Core/Numerics/Vector3_Intrinsics.cs @@ -322,9 +322,17 @@ public static Vector3 SquareRoot(Vector3 value) left.Z != right.Z); } + /// + /// Converts the SharpEngine representation to a System.Numerics representation. + /// + /// The value to convert public static explicit operator System.Numerics.Vector3(Vector3 value) => new(value.X, value.Y, value.Z); + /// + /// Converts the System.Numerics representation to a SharpEngine representation. + /// + /// The value to convert public static explicit operator Vector3(System.Numerics.Vector3 value) => new(value.X, value.Y, value.Z); diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index 77752ad..fcbc7c7 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -49,7 +49,7 @@ public Renderer(CameraView camera, ISettings settings, Scene scene) : base(setti /// public override Task Render() { - return Task.CompletedTask; + // return Task.CompletedTask; try { Window.GL.Enable(EnableCap.DepthTest); diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index a0ce9b8..bf49421 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -1,16 +1,28 @@ -using SharpEngine.Core.Entities; using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; + using System.Collections.Generic; using System.Threading.Tasks; namespace SharpEngine.Core.Scenes; +/// +/// Represents a empty node in a scene. +/// +/// Specifies the type used for transformations. +/// Defines vector used by the transform. public class EmptyNode : SceneNode where TTransform : ITransform, new() where TVector : IVector, new() { + /// + /// Initializes a new instance of . + /// + /// public EmptyNode(string name) : base(name) { } + /// + /// Gets or sets the transform of the node. + /// public virtual TTransform Transform { get; set; } = new(); } @@ -19,6 +31,7 @@ public EmptyNode(string name) : base(name) { } /// public abstract class SceneNode { + /// Gets a new empty node. public static SceneNode Empty => new EmptyNode("Empty Node"); /// @@ -45,6 +58,11 @@ public SceneNode(string name) Name = name; } + /// + /// Adds an empty child node to this node by the given . + /// + /// The name of the empty node to be added. + /// The created node. public virtual SceneNode AddChild(string name) => AddChild(name); diff --git a/Core/Shaders/Shader.cs b/Core/Shaders/Shader.cs index 0197abb..9b117c0 100644 --- a/Core/Shaders/Shader.cs +++ b/Core/Shaders/Shader.cs @@ -268,6 +268,11 @@ public void SetMatrix4(string name, Matrix4x4 data, bool transpose = true) TrySetUniform(name, data, (uniform, d) => Window.GL.UniformMatrix4(uniform, transpose, d.ToSpan())); } + /// + /// Set a uniform Vector2 on this shader. + /// + /// The name of the uniform. + /// The data to set. public void SetVector2(string name, Vector2 data) { Window.GL.UseProgram(Handle); diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index 36a425e..959393c 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -6,15 +6,10 @@ layout (location = 2) in vec2 aTexCoords; uniform vec2 position; uniform float rotation; -uniform vec2 scale; uniform vec2 screenSize; -uniform mat4 clipSpace; uniform mat4 orthoMatrix; -uniform mat4 view; -uniform mat4 projection; uniform mat4 model; -uniform vec3 viewPos; out vec3 Normal; out vec3 FragPos; diff --git a/Core/Windowing/Frame.cs b/Core/Windowing/Frame.cs index a15969a..9b43fbf 100644 --- a/Core/Windowing/Frame.cs +++ b/Core/Windowing/Frame.cs @@ -1,18 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharpEngine.Core.Windowing; +namespace SharpEngine.Core.Windowing; +/// +/// Represents a frame with a specific duration in time. +/// public class Frame { + /// + /// Initializes a new instance of . + /// + /// Represents the duration of the frame in seconds. public Frame(double frameTime) { FrameTime = frameTime; } + /// Gets or sets the time taken to handle the previous frame. public double FrameTime { get; set; } + + /// Gets the frame rate based on the frame time. public float FrameRate => (float)(1 / FrameTime); } diff --git a/Core/_Resources/Default.cs b/Core/_Resources/Default.cs index 77dced2..fb078fb 100644 --- a/Core/_Resources/Default.cs +++ b/Core/_Resources/Default.cs @@ -1,14 +1,18 @@ using SharpEngine.Core.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpEngine.Core._Resources; + +/// +/// Contains default resources used by the engine. +/// public struct Default { + /// Gets the path to the debug texture. public static string DebugTexture => PathExtensions.GetAssemblyPath("Textures\\DefaultTextures\\debug.JPG"); + + /// Gets the path to the default vertex shader. public static string VertexShader => PathExtensions.GetAssemblyPath("Shaders\\shader.vert"); + + /// Gets the path to the lighting fragment shader. public static string FragmentShader => PathExtensions.GetAssemblyPath("Shaders\\lighting.frag"); } diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index 87d99de..6c11873 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -95,34 +95,43 @@ public override void OnLoad() } } + private bool showUnsavedChangesDialog = true; + /// - protected override void AfterRender(double deltaTime) + protected override void AfterRender(double frame) { - base.AfterRender(deltaTime); + base.AfterRender(frame); EnableDocking(); RenderMenuBar(); - bool visible = true; - ImGui.Begin("Unsaved changes", ref visible, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove); + ImGui.Begin("Unsaved changes", ref showUnsavedChangesDialog, ImGuiWindowFlags.NoResize | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoMove); ImGui.Text("There are unsaved changes in the project. Are you sure you want to exist without saving?"); - if (ImGui.Button("Save")) - { - // Save the scene and exit - } + bool cancelled = false; + if (ImGui.Button("Cancel")) + cancelled = true; - if (ImGui.Button("Exit without saving")) + if (!cancelled && ImGui.Button("Save & close")) { - // TODO: shut down the application + Save(); + CurrentWindow.Close(); } + if (!cancelled && ImGui.Button("Exit without saving")) + CurrentWindow.Close(); + ImGui.End(); foreach (var window in _windows) window.RenderWindow(); } + private void Save() + { + // TODO: Save all changes. + } + private static void EnableDocking() { // Enable docking @@ -189,29 +198,31 @@ private void RenderMenuBar() } } - private static void RenderFileMenu() + private void RenderFileMenu() { if (ImGui.BeginMenu("File")) { if (ImGui.MenuItem("New")) { - // Handle New action + // TODO: Handle New action } + if (ImGui.MenuItem("Open")) { - // Handle Open action + // TODO: Handle Open action } + if (ImGui.MenuItem("Save")) - { - // Handle Save action - } + Save(); + if (ImGui.MenuItem("Exit")) { - // Handle Exit action + // TODO: Handle Exit action } + if (ImGui.MenuItem("Publish")) { - // Handle Publish action + // TODO: Handle Publish action } ImGui.EndMenu(); @@ -241,6 +252,7 @@ public override void OnMouseClick(IMouse mouse, MouseButton button, Vector2 vect if (button == Settings.SecondaryButton) { + // TODO: Make this work. if (ImGui.IsItemClicked((ImGuiMouseButton)button)) { // Clicked on an ImGui component diff --git a/Editor/Windows/ContextMenuWindow.cs b/Editor/Windows/ContextMenuWindow.cs index cba5351..1233710 100644 --- a/Editor/Windows/ContextMenuWindow.cs +++ b/Editor/Windows/ContextMenuWindow.cs @@ -66,7 +66,7 @@ private void RenderIntersectedObjectActions() { if (ImGui.Button("Delete")) { - Scene.Root.RemoveChild(IntersectedObject); + Scene.Root.RemoveChild(IntersectedObject!); IntersectedObject = null; } } diff --git a/Editor/Windows/ImGuiWindowBase.cs b/Editor/Windows/ImGuiWindowBase.cs index 8e62aa9..5fabc93 100644 --- a/Editor/Windows/ImGuiWindowBase.cs +++ b/Editor/Windows/ImGuiWindowBase.cs @@ -47,6 +47,9 @@ public ImGuiWindowBase() /// The new project. public void SetProject(Project project) => Project = project; + /// + /// Makes the ImGui window visible. + /// public void Open() => _isVisible = true; /// diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index d917363..12ae347 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -15,6 +15,8 @@ using SharpEngine.Core.Entities.UI.Layouts; using SharpEngine.Core.Entities.Properties; using ImGuiNET; +using SharpEngine.Core.Entities.Properties.Meshes; +using System.Linq; namespace Minecraft; @@ -35,6 +37,9 @@ public class Minecraft : Game private UIElement _uiElem; + /// + /// Gets the main window. + /// public Window? Window { get; set; } /// @@ -66,14 +71,14 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - _scene.UIElements.Add(_uiElem); + //_scene.UIElements.Add(_uiElem); //var uiElem2 = new UIElement("uiElement"); //uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); //uiElem2.Transform.Position = new Vector2(30, 0); // gridLayout.AddChild(_uiElem, uiElem2); - _scene.UIElements.Add(_uiElem); + // _scene.UIElements.Add(_uiElem); // _scene.UIElements.Add(uiElem2); //_scene.UIElements.Add(gridLayout); @@ -86,7 +91,11 @@ public override void Initialize() } } - public void OnAfterRender(double frameTime) + /// + /// Handles rendering after the frame is drawn. + /// + /// Information about the frame. + public void OnAfterRender(Frame frame) { var x = _uiElem.Transform.Position.X; var y = _uiElem.Transform.Position.Y; @@ -97,7 +106,7 @@ public void OnAfterRender(double frameTime) var rotation = _uiElem.Transform.Rotation.Angle; ImGui.Begin("Debug"); - ImGui.Text($"FPS: {frameTime }"); + ImGui.Text($"FPS: {frame.FrameRate}"); ImGui.Text($"Camera position: {Camera.Position}"); ImGui.Text($"UI Element position: {_uiElem.Transform.Position}"); @@ -123,6 +132,10 @@ private void InitializeWorld() { InitializeLights(); InitializeChunks(); + + // var torus = MeshService.Instance.LoadMesh("torus", @"C:\Users\antti\Documents\Untitled2.obj"); + // var go = new GameObject(); + // go.Meshes.Add(torus.First()); } private void InitializeLights() diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs index 46a0d1b..0c5de89 100644 --- a/Examples/MinecraftClone/Program.cs +++ b/Examples/MinecraftClone/Program.cs @@ -1,5 +1,6 @@ using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; +using SharpEngine.Core.Windowing; namespace Minecraft; @@ -25,7 +26,7 @@ private static void Main() window.OnHandleKeyboard += game.HandleKeyboard; window.OnButtonMouseDown += game.HandleMouseDown; window.HandleMouseWheel += game.HandleMouseWheel; - window.OnAfterRender += game.OnAfterRender; + window.OnAfterRender += deltaTime => game.OnAfterRender(new Frame(deltaTime)); // TODO: This needs to be streamlined. game.Window = window; From c6d7764e03e49eb947e218ec50624715d59e399c Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 6 Apr 2025 21:13:19 +0300 Subject: [PATCH 24/28] #2 Framing and rendering changes --- Core/Entities/GameObject.cs | 45 +++++++++++++- Core/Entities/Lights/DirectionalLight.cs | 2 +- Core/Entities/Lights/PointLight.cs | 2 +- Core/Entities/Lights/SpotLight.cs | 2 +- Core/Entities/UI/UIElement.cs | 70 ++++++++-------------- Core/Primitives/PrimitiveFactory.cs | 2 +- Core/Renderers/Renderer.cs | 6 +- Core/Renderers/TextRenderer.cs | 3 +- Core/Renderers/UIRenderer.cs | 13 ++-- Core/Scenes/SceneNode.cs | 4 +- Core/Windowing/SilkWindow.cs | 4 +- Core/Windowing/Window.cs | 30 +++++----- Editor/EditorWindow.cs | 2 +- Examples/MinecraftClone/Block/BlockBase.cs | 2 +- Examples/MinecraftClone/Minecraft.cs | 1 + 15 files changed, 104 insertions(+), 84 deletions(-) diff --git a/Core/Entities/GameObject.cs b/Core/Entities/GameObject.cs index d355d7b..c228615 100644 --- a/Core/Entities/GameObject.cs +++ b/Core/Entities/GameObject.cs @@ -2,6 +2,7 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Interfaces; using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; @@ -18,7 +19,7 @@ namespace SharpEngine.Core.Entities; /// /// Represents a game object in the scene. /// -public class GameObject : EmptyNode +public class GameObject : EmptyNode, IRenderable { /// /// Initializes a new instance of the . @@ -53,8 +54,9 @@ public GameObject(string? diffuseMapFile = null, string? specularMapFile = null, /// /// Gets or sets the mesh of the game object. /// - public List Mesh { get; set; } = []; + public List Meshes { get; set; } = []; + // TODO: Each mesh should have its own material. /// /// Gets or sets the material of the game object. /// @@ -83,7 +85,40 @@ public override Transform Transform public BoundingBox BoundingBox { get; set; } /// - public override Task Render(CameraView camera) + public uint VAO { get; set; } + + /// + public void Initialize() + { + VAO = Window.GL.GenVertexArray(); + Bind(); + + foreach (var mesh in Meshes) + InitializeBuffers(mesh); + + Material.Shader.Use(); + } + + /// + public void Bind() + { + Window.GL.BindVertexArray(VAO); + } + + /// + public void InitializeBuffers(Mesh mesh) + { + var vertexBufferObject = Window.GL.GenBuffer(); + Window.GL.BindBuffer(GLEnum.ArrayBuffer, vertexBufferObject); + Window.GL.BufferData(GLEnum.ArrayBuffer, mesh.GetVertices(), GLEnum.StaticDraw); + + var elementBufferObject = Window.GL.GenBuffer(); + Window.GL.BindBuffer(GLEnum.ElementArrayBuffer, elementBufferObject); + Window.GL.BufferData(GLEnum.ElementArrayBuffer, mesh.Indices, GLEnum.StaticDraw); + } + + /// + public override Task Render(CameraView camera, Window window) { Material.DiffuseMap.Use(TextureUnit.Texture0); Material.Shader.SetInt("material.diffuse", Material.diffuseUnit); @@ -103,8 +138,12 @@ public override Task Render(CameraView camera) } Material.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); + Window.GL.DrawArrays(PrimitiveType.Triangles, 0, 36); + // foreach (var mesh in Meshes) + // Window.GL.DrawElements(PrimitiveType.Triangles, (uint)mesh.Indices.Length, DrawElementsType.UnsignedInt, []); + return Task.CompletedTask; } } diff --git a/Core/Entities/Lights/DirectionalLight.cs b/Core/Entities/Lights/DirectionalLight.cs index 7b4a495..b5d0367 100644 --- a/Core/Entities/Lights/DirectionalLight.cs +++ b/Core/Entities/Lights/DirectionalLight.cs @@ -30,7 +30,7 @@ public DirectionalLight() } /// - public override Task Render(CameraView camera) + public override Task Render(CameraView camera, Windowing.Window window) { Material.Shader.SetVector3("dirLight.direction", Direction); Material.Shader.SetVector3("dirLight.ambient", Ambient); diff --git a/Core/Entities/Lights/PointLight.cs b/Core/Entities/Lights/PointLight.cs index 85dbade..4ce5537 100644 --- a/Core/Entities/Lights/PointLight.cs +++ b/Core/Entities/Lights/PointLight.cs @@ -52,7 +52,7 @@ public PointLight(Vector3 position, int index) public float Quadratic { get; set; } /// - public override Task Render(CameraView camera) + public override Task Render(CameraView camera, Windowing.Window window) { Material.Shader.SetVector3($"pointLights[{_index}].position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3($"pointLights[{_index}].ambient", Ambient); diff --git a/Core/Entities/Lights/SpotLight.cs b/Core/Entities/Lights/SpotLight.cs index f66bc55..085d382 100644 --- a/Core/Entities/Lights/SpotLight.cs +++ b/Core/Entities/Lights/SpotLight.cs @@ -58,7 +58,7 @@ public SpotLight() public float Quadratic { get; set; } /// - public override Task Render(CameraView camera) + public override Task Render(CameraView camera, Windowing.Window window) { Material.Shader.SetVector3("spotLight.position", (System.Numerics.Vector3)Transform.Position); Material.Shader.SetVector3("spotLight.direction", Direction); diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 3f3fecd..2bbf712 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -1,7 +1,7 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Entities.Views; -using SharpEngine.Core.Numerics; +using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; @@ -10,15 +10,17 @@ using System.Numerics; using System.Threading.Tasks; using Vector2 = SharpEngine.Core.Numerics.Vector2; -using Vector3 = System.Numerics.Vector3; namespace SharpEngine.Core.Entities.UI; /// /// Represents a User Interface entity. /// -public class UIElement : EmptyNode +public class UIElement : EmptyNode, IRenderable { + /// + /// Initializes a new instance of . + /// public UIElement() : this("UIElement") { } /// @@ -27,6 +29,7 @@ public UIElement() : this("UIElement") { } /// The name of the UI element. public UIElement(string name) : base(name) { + // TODO: Support custom meshes? Mesh = MeshService.Instance.LoadMesh(nameof(Primitives.Plane), Primitives.Plane.Mesh); Initialize(); @@ -36,87 +39,64 @@ public UIElement(string name) : base(name) /// Gets or sets the mesh of the UI element. public Mesh Mesh { get; set; } - //public override Transform2D Transform { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } - private uint _vertexArrayObject; + /// + public uint VAO { get; set; } /// public void Initialize() { - _vertexArrayObject = Window.GL.GenVertexArray(); + VAO = Window.GL.GenVertexArray(); Bind(); - InitializeBuffers(); + InitializeBuffers(Mesh); - _uiShader.Shader.Use(); + _uiShader.Shader?.Use(); _uiShader.SetAttributes(); } - /// - /// Binds the VAO to the current context. - /// + /// public void Bind() { - Window.GL.BindVertexArray(_vertexArrayObject); + Window.GL.BindVertexArray(VAO); } - private void InitializeBuffers() + /// + public void InitializeBuffers(Mesh mesh) { var vertexBufferObject = Window.GL.GenBuffer(); Window.GL.BindBuffer(GLEnum.ArrayBuffer, vertexBufferObject); - Window.GL.BufferData(GLEnum.ArrayBuffer, Mesh.GetVertices(), GLEnum.StaticDraw); + Window.GL.BufferData(GLEnum.ArrayBuffer, mesh.GetVertices(), GLEnum.StaticDraw); var elementBufferObject = Window.GL.GenBuffer(); Window.GL.BindBuffer(GLEnum.ElementArrayBuffer, elementBufferObject); - Window.GL.BufferData(GLEnum.ElementArrayBuffer, Mesh.Indices, GLEnum.StaticDraw); + Window.GL.BufferData(GLEnum.ElementArrayBuffer, mesh.Indices, GLEnum.StaticDraw); } + Matrix4x4 OrthoMatrix = Matrix4x4.CreateOrthographicOffCenter(-1, 1, -1, 1, -1, 1); + /// /// Render the UI element. /// - public override Task Render(CameraView camera) + public override Task Render(CameraView camera, Window window) { - Window.GL.BindVertexArray(_vertexArrayObject); + if (_uiShader.Shader is null) + return Task.CompletedTask; + + Bind(); // TODO: These should come from somewhere else. const float screenWidth = 1280; const float screenHeight = 720; - var orthoMatrix = Matrix4x4.CreateOrthographicOffCenter(-1, 1, -1, 1, -1, 1); - - _uiShader.Shader.SetVector2("screenSize", new System.Numerics.Vector2(screenWidth, screenHeight)); _uiShader.Shader.SetVector2("position", (System.Numerics.Vector2)Transform.Position); _uiShader.Shader.SetFloat("rotation", Math.DegreesToRadians(Transform.Rotation.Angle)); - _uiShader.Shader.SetVector2("scale", (System.Numerics.Vector2)Transform.Scale); _uiShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); - _uiShader.Shader.SetMatrix4("orthoMatrix", orthoMatrix); // Pass the orthographic matrix to the shader - - _uiShader.Shader!.SetMatrix4("view", camera.GetViewMatrix()); - _uiShader.Shader!.SetMatrix4("projection", camera.GetProjectionMatrix()); - _uiShader.Shader!.SetVector3("viewPos", camera.Position); - - - var pos = new Vector3(Transform.Position.X, Transform.Position.Y, 0); - var clipSpace = WorldToClipSpace(pos, Transform.ModelMatrix, camera.GetViewMatrix(), camera.GetProjectionMatrix()); - _uiShader.Shader.SetVector4("clipSpace", clipSpace); + _uiShader.Shader.SetMatrix4("orthoMatrix", OrthoMatrix); // Pass the orthographic matrix to the shader Window.GL.DrawElements(PrimitiveType.Triangles, (uint)Mesh.Indices.Length, DrawElementsType.UnsignedInt, []); return Task.CompletedTask; } - - public static Vector4 WorldToClipSpace(Vector3 position, Matrix4x4 model, Matrix4x4 view, Matrix4x4 projection) - { - // Combine all transforms into MVP - Matrix4x4 mvp = model * view * projection; - - // Convert Vector3 to Vector4 for matrix multiplication - Vector4 worldPos = new Vector4(position, 1.0f); - - // Transform to clip space (i.e., ready to assign to gl_Position) - Vector4 clipSpacePos = Vector4.Transform(worldPos, mvp); - - return clipSpacePos; - } } diff --git a/Core/Primitives/PrimitiveFactory.cs b/Core/Primitives/PrimitiveFactory.cs index 51f03d0..8dd935e 100644 --- a/Core/Primitives/PrimitiveFactory.cs +++ b/Core/Primitives/PrimitiveFactory.cs @@ -33,7 +33,7 @@ public static GameObject Create(PrimitiveType primitiveType, Vector3 position, s => new(diffuseMapFile, specularMapFile, vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader) { Transform = new Transform { Position = (SharpEngine.Core.Numerics.Vector3)position }, - Mesh = primitiveType switch + Meshes = primitiveType switch { PrimitiveType.Cube => [Cube.Mesh], PrimitiveType.Plane => [Plane.Mesh], diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index fcbc7c7..62a889f 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -23,6 +23,7 @@ public class Renderer : RendererBase private readonly CameraView _camera; private readonly Scene _scene; + private readonly Window _window; // Read only once, load into OpenGL buffer once. // TODO: Multiple meshes @@ -36,10 +37,11 @@ public class Renderer : RendererBase /// The game the renderer is being used for. /// The settings for the renderer. /// The game scene to be rendered. - public Renderer(CameraView camera, ISettings settings, Scene scene) : base(settings) + public Renderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) { _camera = camera; _scene = scene; + _window = window; // TODO: These should be refactored out. The minimum build shouldn't need to use these. _lightingShader = new LightingShader(); @@ -82,7 +84,7 @@ private Task RenderGameObject(SceneNode node) return Task.CompletedTask; // TODO: Skip blocks that are behind others relative to the camera - return gameObject.Render(_camera); + return gameObject.Render(_camera, _window); } private static bool IsInViewFrustum(BoundingBox boundingBox, CameraView camera) diff --git a/Core/Renderers/TextRenderer.cs b/Core/Renderers/TextRenderer.cs index eff8504..e8bef69 100644 --- a/Core/Renderers/TextRenderer.cs +++ b/Core/Renderers/TextRenderer.cs @@ -1,12 +1,13 @@ using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; +using SharpEngine.Core.Windowing; using System.Threading.Tasks; namespace SharpEngine.Core.Renderers; internal class TextRenderer : RendererBase { - public TextRenderer(CameraView camera, ISettings settings, Scene scene) : base(settings) + public TextRenderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) { } diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index ff9c067..67d3166 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -1,18 +1,13 @@ -using SharpEngine.Core.Entities.Properties; -using SharpEngine.Core.Entities.UI; +using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; -using Silk.NET.Input; using Silk.NET.OpenGL; using System; -using System.Linq; -using System.Numerics; using System.Threading.Tasks; -using static Silk.NET.Core.Native.WinString; namespace SharpEngine.Core.Renderers; @@ -24,6 +19,7 @@ public class UIRenderer : RendererBase private readonly Scene _scene; private readonly UIShader _uiShader; private readonly CameraView _camera; + private readonly Window _window; /// public override RenderFlags RenderFlag => RenderFlags.UIRenderer; @@ -31,11 +27,12 @@ public class UIRenderer : RendererBase /// /// Initializes a new instance of . /// - public UIRenderer(CameraView camera, ISettings settings, Scene scene) : base(settings) + public UIRenderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) { _scene = scene; _uiShader = new UIShader(); _camera = camera; + _window = window; } /// @@ -52,7 +49,7 @@ public override Task Render() // _camera.SetShaderUniforms(_uiShader.Shader!); _uiShader.Shader.Use(); - var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera)); + var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera, _window)); return Task.WhenAll(uiElementRenderTasks); } diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index bf49421..89f25a3 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -1,7 +1,7 @@ using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; - +using SharpEngine.Core.Windowing; using System.Collections.Generic; using System.Threading.Tasks; @@ -105,5 +105,5 @@ public void RemoveChild(SceneNode node) /// Renders the current object to the screen. /// /// A representing an asynchronous operation. - public virtual Task Render(CameraView camera) => Task.CompletedTask; + public virtual Task Render(CameraView camera, Window window) => Task.CompletedTask; } diff --git a/Core/Windowing/SilkWindow.cs b/Core/Windowing/SilkWindow.cs index 64ae012..aacfd26 100644 --- a/Core/Windowing/SilkWindow.cs +++ b/Core/Windowing/SilkWindow.cs @@ -304,13 +304,13 @@ public virtual void OnLoad() /// Handles operations needed to be executed after the renderers are finished. /// /// Time since the last frame. - protected virtual void AfterRender(double deltaTime) => OnAfterRender?.Invoke(deltaTime); + protected virtual void AfterRender(Frame frame) => OnAfterRender?.Invoke(frame.FrameTime); /// /// Handles operations needed to be executed before the renderers are executed. /// /// Time since the last frame. - protected virtual void PreRender(double deltaTime) { } + protected virtual void PreRender(Frame frame) { } // TODO: Scene unsaved changes warning. /// diff --git a/Core/Windowing/Window.cs b/Core/Windowing/Window.cs index 85f9c97..3d4f44f 100644 --- a/Core/Windowing/Window.cs +++ b/Core/Windowing/Window.cs @@ -84,7 +84,7 @@ public Window(CameraView camera, Scene scene, IViewSettings settings) Scene = scene; Settings = settings; Camera = camera; - Initialize(); + InitializeWindow(); } /// @@ -98,16 +98,16 @@ public Window(Scene scene, IViewSettings settings) Settings = settings; Camera = new(Vector3.One, settings); - Initialize(); + InitializeWindow(); } private bool _windowInitialized; - private void Initialize() + private void InitializeWindow() { CurrentWindow = CreateWindow(Settings.WindowOptions); - CurrentWindow.Update += OnUpdateFrame; - CurrentWindow.Render += RenderFrame; + CurrentWindow.Update += deltaTime => OnUpdateFrame(new Frame(deltaTime)); + CurrentWindow.Render += deltaTime => RenderFrame(new Frame(deltaTime)); CurrentWindow.Resize += OnResize; CurrentWindow.Load += OnLoad; CurrentWindow.Closing += OnClosing; @@ -162,7 +162,7 @@ public override void OnLoad() { // Make sure the renderer has the correct constructor parameters! // TODO: The static reference to the context will not work when multiple windows are implemented, since the context will be different. - var requiredArguments = new object[] { Camera, Settings, Scene }; + var requiredArguments = new object[] { Camera, this, Settings, Scene }; var renderer = (RendererBase)Activator.CreateInstance(type, requiredArguments)!; _renderers = _renderers.Append(renderer); @@ -186,8 +186,8 @@ public override void OnLoad() /// /// Renders the current view and all specified renderers. /// - /// The time since the last frame. - protected void RenderFrame(double deltaTime) + /// Contains information about the previous frame. + protected void RenderFrame(Frame frame) { while (!_initialized) { @@ -196,9 +196,9 @@ protected void RenderFrame(double deltaTime) try { - PreRender(deltaTime); + PreRender(frame); - _imGuiController?.Update((float)deltaTime); + _imGuiController?.Update((float)frame.FrameTime); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); @@ -215,7 +215,7 @@ protected void RenderFrame(double deltaTime) // TODO: This call causes flickering in the new framework. Investigate why. // _window.SwapBuffers(); - AfterRender(deltaTime); + AfterRender(frame); _imGuiController?.Render(); } @@ -243,7 +243,7 @@ private void UseShaders() } /// - protected void OnUpdateFrame(double deltaTime) + protected void OnUpdateFrame(Frame frame) { while (!_initialized) { @@ -255,7 +255,7 @@ protected void OnUpdateFrame(double deltaTime) // return; if (Settings.PrintFrameRate) - Console.WriteLine($"FPS: {1f / deltaTime}"); + Console.WriteLine($"FPS: {frame.FrameRate}"); // TODO: Handle multiple mice? var mouse = Input?.Mice[0]; @@ -271,11 +271,11 @@ protected void OnUpdateFrame(double deltaTime) if (keyboard.IsKeyPressed(Key.Escape)) CurrentWindow.Close(); - OnHandleKeyboard?.Invoke(keyboard, deltaTime); + OnHandleKeyboard?.Invoke(keyboard, frame.FrameTime); } if (Input is not null) - OnUpdate?.Invoke(deltaTime, Input); + OnUpdate?.Invoke(frame.FrameTime, Input); } // TODO: #21 Input system diff --git a/Editor/EditorWindow.cs b/Editor/EditorWindow.cs index 6c11873..4746b74 100644 --- a/Editor/EditorWindow.cs +++ b/Editor/EditorWindow.cs @@ -98,7 +98,7 @@ public override void OnLoad() private bool showUnsavedChangesDialog = true; /// - protected override void AfterRender(double frame) + protected override void AfterRender(Frame frame) { base.AfterRender(frame); diff --git a/Examples/MinecraftClone/Block/BlockBase.cs b/Examples/MinecraftClone/Block/BlockBase.cs index 15c7a93..7f06016 100644 --- a/Examples/MinecraftClone/Block/BlockBase.cs +++ b/Examples/MinecraftClone/Block/BlockBase.cs @@ -57,6 +57,6 @@ private void AssignProperties(GameObject cube, string name) Name = name; Material = cube.Material; - Mesh = cube.Mesh; + Meshes = cube.Meshes; } } diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 12ae347..ce99a91 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -133,6 +133,7 @@ private void InitializeWorld() InitializeLights(); InitializeChunks(); + // TODO: Does not work yet. // var torus = MeshService.Instance.LoadMesh("torus", @"C:\Users\antti\Documents\Untitled2.obj"); // var go = new GameObject(); // go.Meshes.Add(torus.First()); From f3090984ebcc8835b922825a446168acd77876ae Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Sun, 6 Apr 2025 21:16:09 +0300 Subject: [PATCH 25/28] Pop stash --- Core/Entities/Lights/DirectionalLight.cs | 6 +- Core/Entities/Lights/PointLight.cs | 12 ++-- Core/Entities/Lights/SpotLight.cs | 8 +-- Core/Renderers/RenderFlags.cs | 14 ++--- Core/Renderers/Renderer.cs | 1 + Core/Renderers/TextRenderer.cs | 3 +- Core/Renderers/UIRenderer.cs | 8 ++- Core/Scenes/SceneNode.cs | 1 + Core/Windowing/SilkWindow.cs | 75 +++++++++++++++++------- 9 files changed, 82 insertions(+), 46 deletions(-) diff --git a/Core/Entities/Lights/DirectionalLight.cs b/Core/Entities/Lights/DirectionalLight.cs index b5d0367..432f980 100644 --- a/Core/Entities/Lights/DirectionalLight.cs +++ b/Core/Entities/Lights/DirectionalLight.cs @@ -1,8 +1,8 @@ -using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Properties; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; - +using SharpEngine.Core.Windowing; using System.Numerics; using System.Threading.Tasks; @@ -30,7 +30,7 @@ public DirectionalLight() } /// - public override Task Render(CameraView camera, Windowing.Window window) + public override Task Render(CameraView camera, Window window) { Material.Shader.SetVector3("dirLight.direction", Direction); Material.Shader.SetVector3("dirLight.ambient", Ambient); diff --git a/Core/Entities/Lights/PointLight.cs b/Core/Entities/Lights/PointLight.cs index 4ce5537..970d32b 100644 --- a/Core/Entities/Lights/PointLight.cs +++ b/Core/Entities/Lights/PointLight.cs @@ -1,7 +1,7 @@ -using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; - +using SharpEngine.Core.Windowing; using System.Numerics; using System.Threading.Tasks; @@ -52,9 +52,9 @@ public PointLight(Vector3 position, int index) public float Quadratic { get; set; } /// - public override Task Render(CameraView camera, Windowing.Window window) + public override Task Render(CameraView camera, Window window) { - Material.Shader.SetVector3($"pointLights[{_index}].position", (System.Numerics.Vector3)Transform.Position); + Material.Shader.SetVector3($"pointLights[{_index}].position", (Vector3)Transform.Position); Material.Shader.SetVector3($"pointLights[{_index}].ambient", Ambient); Material.Shader.SetVector3($"pointLights[{_index}].diffuse", Diffuse); Material.Shader.SetVector3($"pointLights[{_index}].specular", Specular); @@ -62,8 +62,8 @@ public override Task Render(CameraView camera, Windowing.Window window) Material.Shader.SetFloat($"pointLights[{_index}].linear", Linear); Material.Shader.SetFloat($"pointLights[{_index}].quadratic", Quadratic); - var lampMatrix = Matrix4x4.CreateScale((System.Numerics.Vector3)Transform.Scale); - lampMatrix *= Matrix4x4.CreateTranslation((System.Numerics.Vector3)Transform.Position); + var lampMatrix = Matrix4x4.CreateScale((Vector3)Transform.Scale); + lampMatrix *= Matrix4x4.CreateTranslation((Vector3)Transform.Position); LampShader.Shader?.SetMatrix4(ShaderAttributes.Model, lampMatrix); diff --git a/Core/Entities/Lights/SpotLight.cs b/Core/Entities/Lights/SpotLight.cs index 085d382..b2ef9c9 100644 --- a/Core/Entities/Lights/SpotLight.cs +++ b/Core/Entities/Lights/SpotLight.cs @@ -1,7 +1,7 @@ -using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; - +using SharpEngine.Core.Windowing; using System.Numerics; using System.Threading.Tasks; @@ -58,9 +58,9 @@ public SpotLight() public float Quadratic { get; set; } /// - public override Task Render(CameraView camera, Windowing.Window window) + public override Task Render(CameraView camera, Window window) { - Material.Shader.SetVector3("spotLight.position", (System.Numerics.Vector3)Transform.Position); + Material.Shader.SetVector3("spotLight.position", (Vector3)Transform.Position); Material.Shader.SetVector3("spotLight.direction", Direction); Material.Shader.SetVector3("spotLight.ambient", Ambient); Material.Shader.SetVector3("spotLight.diffuse", Diffuse); diff --git a/Core/Renderers/RenderFlags.cs b/Core/Renderers/RenderFlags.cs index 2e208a2..ac45eb1 100644 --- a/Core/Renderers/RenderFlags.cs +++ b/Core/Renderers/RenderFlags.cs @@ -14,14 +14,12 @@ public enum RenderFlags /// All the vertices will for the mesh will be rendered. Renderer3D = 1, - /// - /// The front face of the mesh will be rendered. - /// + /// The front face of the mesh will be rendered. UIRenderer = 2, - /// - /// Enables all renderers. - /// - All = 3, - Text = 4 + /// Text components will be rendered. + Text = 3, + + /// Enables all renderers. + All = 4, } diff --git a/Core/Renderers/Renderer.cs b/Core/Renderers/Renderer.cs index 62a889f..3768c84 100644 --- a/Core/Renderers/Renderer.cs +++ b/Core/Renderers/Renderer.cs @@ -35,6 +35,7 @@ public class Renderer : RendererBase /// Initializes a new instance of . /// /// The game the renderer is being used for. + /// The window executing the renderer. /// The settings for the renderer. /// The game scene to be rendered. public Renderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) diff --git a/Core/Renderers/TextRenderer.cs b/Core/Renderers/TextRenderer.cs index e8bef69..5787fc5 100644 --- a/Core/Renderers/TextRenderer.cs +++ b/Core/Renderers/TextRenderer.cs @@ -1,7 +1,8 @@ -using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Windowing; + using System.Threading.Tasks; namespace SharpEngine.Core.Renderers; diff --git a/Core/Renderers/UIRenderer.cs b/Core/Renderers/UIRenderer.cs index 67d3166..6c0e29e 100644 --- a/Core/Renderers/UIRenderer.cs +++ b/Core/Renderers/UIRenderer.cs @@ -1,9 +1,10 @@ -using SharpEngine.Core.Entities.UI; +using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; + using Silk.NET.OpenGL; using System; @@ -38,6 +39,9 @@ public UIRenderer(CameraView camera, Window window, ISettings settings, Scene sc /// public override Task Render() { + if (_uiShader.Shader is null) + return Task.CompletedTask; + try { Window.GL.Enable(EnableCap.DepthTest); @@ -47,7 +51,7 @@ public override Task Render() Window.GL.Disable(EnableCap.CullFace); // _camera.SetShaderUniforms(_uiShader.Shader!); - _uiShader.Shader.Use(); + _uiShader.Shader?.Use(); var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera, _window)); diff --git a/Core/Scenes/SceneNode.cs b/Core/Scenes/SceneNode.cs index 89f25a3..13b3526 100644 --- a/Core/Scenes/SceneNode.cs +++ b/Core/Scenes/SceneNode.cs @@ -2,6 +2,7 @@ using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Numerics; using SharpEngine.Core.Windowing; + using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Core/Windowing/SilkWindow.cs b/Core/Windowing/SilkWindow.cs index aacfd26..3cae2f5 100644 --- a/Core/Windowing/SilkWindow.cs +++ b/Core/Windowing/SilkWindow.cs @@ -1,4 +1,4 @@ -using Silk.NET.Core; +using Silk.NET.Core; using Silk.NET.Core.Contexts; using Silk.NET.Input; using Silk.NET.Maths; @@ -22,10 +22,14 @@ namespace SharpEngine.Core.Windowing; public abstract class SilkWindow : IWindow { /// - public virtual IWindowHost? Parent { get; } + public virtual IWindowHost? Parent => CurrentWindow.Parent; /// - public IMonitor? Monitor { get; set; } + public IMonitor? Monitor + { + get => CurrentWindow.Monitor; + set => CurrentWindow.Monitor = value; + } /// public bool IsClosing @@ -69,7 +73,11 @@ public string Title } /// - public WindowState WindowState { get; set; } + public WindowState WindowState + { + get => CurrentWindow.WindowState; + set => CurrentWindow.WindowState = value; + } /// public WindowBorder WindowBorder @@ -107,25 +115,49 @@ public bool TopMost public bool IsInitialized => CurrentWindow.IsInitialized; /// - public bool ShouldSwapAutomatically { get; set; } + public bool ShouldSwapAutomatically + { + get => CurrentWindow.ShouldSwapAutomatically; + set => CurrentWindow.ShouldSwapAutomatically = value; + } /// - public bool IsEventDriven { get; set; } - + public bool IsEventDriven + { + get => CurrentWindow.ShouldSwapAutomatically; + set => CurrentWindow.ShouldSwapAutomatically = value; + } + /// - public bool IsContextControlDisabled { get; set; } + public bool IsContextControlDisabled + { + get => CurrentWindow.ShouldSwapAutomatically; + set => CurrentWindow.ShouldSwapAutomatically = value; + } /// - public double FramesPerSecond { get; set; } - + public double FramesPerSecond + { + get => CurrentWindow.FramesPerSecond; + set => CurrentWindow.FramesPerSecond = value; + } + /// - public double UpdatesPerSecond { get; set; } + public double UpdatesPerSecond + { + get => CurrentWindow.UpdatesPerSecond; + set => CurrentWindow.UpdatesPerSecond = value; + } /// - public GraphicsAPI API { get; } + public GraphicsAPI API => CurrentWindow.API; /// - public bool VSync { get; set; } + public bool VSync + { + get => CurrentWindow.ShouldSwapAutomatically; + set => CurrentWindow.ShouldSwapAutomatically = value; + } /// public VideoMode VideoMode => CurrentWindow.VideoMode; @@ -183,7 +215,7 @@ public bool TopMost public event Action? Render; /// Raised after the scene has been rendered. - public event Action? OnAfterRender; + public event Action? OnAfterRender; /// An event executed when the window has loaded. public event Action? OnLoaded; @@ -249,16 +281,16 @@ public void Dispose() public virtual void Initialize() => CurrentWindow.Initialize(); /// - public virtual object Invoke(Delegate d, params object[] args) => new(); + public virtual object Invoke(Delegate d, params object[] args) => CurrentWindow.Invoke(d, args); /// - public virtual Vector2D PointToClient(Vector2D point) => throw new NotImplementedException(); + public virtual Vector2D PointToClient(Vector2D point) => CurrentWindow.PointToClient(point); /// - public virtual Vector2D PointToFramebuffer(Vector2D point) => throw new NotImplementedException(); + public virtual Vector2D PointToFramebuffer(Vector2D point) => CurrentWindow.PointToFramebuffer(point); /// - public virtual Vector2D PointToScreen(Vector2D point) => throw new NotImplementedException(); + public virtual Vector2D PointToScreen(Vector2D point) => CurrentWindow.PointToScreen(point); /// public virtual void Reset() => CurrentWindow.Reset(); @@ -303,16 +335,15 @@ public virtual void OnLoad() /// /// Handles operations needed to be executed after the renderers are finished. /// - /// Time since the last frame. - protected virtual void AfterRender(Frame frame) => OnAfterRender?.Invoke(frame.FrameTime); + /// Contains information about the previous frame. + protected virtual void AfterRender(Frame frame) => OnAfterRender?.Invoke(frame); /// /// Handles operations needed to be executed before the renderers are executed. /// - /// Time since the last frame. + /// Contains information about the previous frame. protected virtual void PreRender(Frame frame) { } - // TODO: Scene unsaved changes warning. /// /// Handles closing the application. /// From a0c2105c2c0d0e3dcc480233d1a8d81a9846624c Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Tue, 8 Apr 2025 20:48:01 +0300 Subject: [PATCH 26/28] #26 Fix build --- Core/Entities/UI/UIElement.cs | 8 ++++++++ Examples/MinecraftClone/Minecraft.cs | 2 +- Examples/MinecraftClone/Program.cs | 4 ++-- Examples/Minimal/Program.cs | 6 ++---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 2bbf712..85596c4 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -37,6 +37,12 @@ public UIElement(string name) : base(name) private readonly UIShader _uiShader = new(); + /// Gets or sets the width of the ui element. + public float Width { get; set; } + + /// Gets or sets the height of the ui element. + public float Height { get; set; } + /// Gets or sets the mesh of the UI element. public Mesh Mesh { get; set; } @@ -89,6 +95,8 @@ public override Task Render(CameraView camera, Window window) const float screenWidth = 1280; const float screenHeight = 720; + _uiShader.Shader.SetFloat("width", Width); + _uiShader.Shader.SetFloat("height", Height); _uiShader.Shader.SetVector2("screenSize", new System.Numerics.Vector2(screenWidth, screenHeight)); _uiShader.Shader.SetVector2("position", (System.Numerics.Vector2)Transform.Position); _uiShader.Shader.SetFloat("rotation", Math.DegreesToRadians(Transform.Rotation.Angle)); diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index ce99a91..3b3bfeb 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -71,7 +71,7 @@ public override void Initialize() // TODO: Fix UI renderer _uiElem = new UIElement("uiElement"); - //_scene.UIElements.Add(_uiElem); + _scene.UIElements.Add(_uiElem); //var uiElem2 = new UIElement("uiElement"); //uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs index 0c5de89..bf3e6a3 100644 --- a/Examples/MinecraftClone/Program.cs +++ b/Examples/MinecraftClone/Program.cs @@ -19,14 +19,14 @@ private static void Main() Scene scene = new Scene(); Minecraft game = new Minecraft(scene, gameSettings); - using var window = new SharpEngine.Core.Windowing.Window(game.Camera, scene, game.Camera.Settings); + using var window = new Window(game.Camera, scene, game.Camera.Settings); window.OnLoaded += game.Initialize; window.OnHandleMouse += game.HandleMouse; window.OnUpdate += game.Update; window.OnHandleKeyboard += game.HandleKeyboard; window.OnButtonMouseDown += game.HandleMouseDown; window.HandleMouseWheel += game.HandleMouseWheel; - window.OnAfterRender += deltaTime => game.OnAfterRender(new Frame(deltaTime)); + window.OnAfterRender += frame => game.OnAfterRender(frame); // TODO: This needs to be streamlined. game.Window = window; diff --git a/Examples/Minimal/Program.cs b/Examples/Minimal/Program.cs index dd6f183..9751459 100644 --- a/Examples/Minimal/Program.cs +++ b/Examples/Minimal/Program.cs @@ -1,8 +1,6 @@ using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; - -using Silk.NET.Maths; -using Silk.NET.Windowing; +using SharpEngine.Core.Windowing; namespace Minimal; @@ -19,7 +17,7 @@ public static void Main(string[] _) var game = new Minimal(new DefaultSettings()); var scene = new Scene(); - using var window = new SharpEngine.Core.Windowing.Window(game.Camera, scene, game.Camera.Settings); + using var window = new Window(game.Camera, scene, game.Camera.Settings); window.Run(); } } From 4f76569a9e3fe30862864d4d3dba2708b37f72e0 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Tue, 8 Apr 2025 21:30:37 +0300 Subject: [PATCH 27/28] #26 Fix flags --- Core/Renderers/RenderFlags.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Renderers/RenderFlags.cs b/Core/Renderers/RenderFlags.cs index ac45eb1..005999b 100644 --- a/Core/Renderers/RenderFlags.cs +++ b/Core/Renderers/RenderFlags.cs @@ -18,8 +18,8 @@ public enum RenderFlags UIRenderer = 2, /// Text components will be rendered. - Text = 3, + Text = 4, /// Enables all renderers. - All = 4, + All = 7, } From 48652aa255676e2ebc41444c2cc7fe6d16aef488 Mon Sep 17 00:00:00 2001 From: Antti Veikkolainen Date: Tue, 8 Apr 2025 21:37:43 +0300 Subject: [PATCH 28/28] #26 Use width and height --- Core/Entities/UI/UIElement.cs | 4 ++-- Core/Shaders/uiShader.vert | 18 ++++++++++++++---- Examples/MinecraftClone/Minecraft.cs | 12 ++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Core/Entities/UI/UIElement.cs b/Core/Entities/UI/UIElement.cs index 85596c4..068a40f 100644 --- a/Core/Entities/UI/UIElement.cs +++ b/Core/Entities/UI/UIElement.cs @@ -38,10 +38,10 @@ public UIElement(string name) : base(name) private readonly UIShader _uiShader = new(); /// Gets or sets the width of the ui element. - public float Width { get; set; } + public float Width { get; set; } = 10; /// Gets or sets the height of the ui element. - public float Height { get; set; } + public float Height { get; set; } = 10; /// Gets or sets the mesh of the UI element. public Mesh Mesh { get; set; } diff --git a/Core/Shaders/uiShader.vert b/Core/Shaders/uiShader.vert index 959393c..2967764 100644 --- a/Core/Shaders/uiShader.vert +++ b/Core/Shaders/uiShader.vert @@ -4,6 +4,9 @@ layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; +uniform float width; +uniform float height; + uniform vec2 position; uniform float rotation; uniform vec2 screenSize; @@ -30,14 +33,21 @@ void main() 0.0, 0.0, 0.0, 1.0 ); + // Scale the vertex position using width and height + vec3 scaledPos = vec3( + aPos.x * width * reciprScaleOnscreen, + aPos.y * height * reciprScaleOnscreen, + aPos.z + ); + // Calculate the model-view-projection matrix using the orthographic projection matrix mat4 mvp = orthoMatrix * rotationMatrix * model; - // Apply the MVP matrix to the vertex position with scaling and aspect ratio + // Apply the MVP matrix to the scaled vertex position with aspect ratio gl_Position = mvp * vec4( - (aPos.x + (position.x * reciprScaleOnscreen)) * aspectRatio, - aPos.y + (position.y * reciprScaleOnscreen), - aPos.z, + (scaledPos.x + (position.x * reciprScaleOnscreen)) * aspectRatio, + scaledPos.y + (position.y * reciprScaleOnscreen), + scaledPos.z, 1 ); diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs index 3b3bfeb..036d8c7 100644 --- a/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -105,6 +105,9 @@ public void OnAfterRender(Frame frame) var rotation = _uiElem.Transform.Rotation.Angle; + var height = _uiElem.Height; + var width = _uiElem.Width; + ImGui.Begin("Debug"); ImGui.Text($"FPS: {frame.FrameRate}"); ImGui.Text($"Camera position: {Camera.Position}"); @@ -121,11 +124,20 @@ public void OnAfterRender(Frame frame) ImGui.Text($"UI Element scale: {_uiElem.Transform.Scale}"); ImGui.SliderFloat("rotation", ref rotation, 0, 360); + ImGui.Text($"UI Element width: {_uiElem.Width}"); + ImGui.SliderFloat("width", ref width, 0, 100); + + ImGui.Text($"UI Element height: {_uiElem.Height}"); + ImGui.SliderFloat("height", ref height, 0, 100); + ImGui.End(); _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(x, y); _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(sx, sy); _uiElem.Transform.Rotation.Angle = rotation; + + _uiElem.Height = height; + _uiElem.Width = width; } private void InitializeWorld()