+
diff --git a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs b/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs
similarity index 97%
rename from Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs
rename to Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs
index ad2eaff..7d1b324 100644
--- a/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.cs
+++ b/Engine/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/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css b/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.css
new file mode 100644
index 0000000..a76d394
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css b/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.min.css
new file mode 100644
index 0000000..0ee1ee3
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss b/Engine/Launcher/UI/Components/Dialogs/CreateProjectDialog.razor.scss
new file mode 100644
index 0000000..6457dd0
--- /dev/null
+++ b/Engine/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/Dialogs/DialogBase.cs b/Engine/Launcher/UI/Components/Dialogs/DialogBase.cs
similarity index 100%
rename from Launcher/UI/Components/Dialogs/DialogBase.cs
rename to Engine/Launcher/UI/Components/Dialogs/DialogBase.cs
diff --git a/Engine/Launcher/UI/Components/Flyouts/Flyout.razor b/Engine/Launcher/UI/Components/Flyouts/Flyout.razor
new file mode 100644
index 0000000..5fca179
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Flyouts/Flyout.razor
@@ -0,0 +1,56 @@
+@using Launcher.Enums
+
+
+@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; }
+
+ private string _isVisibleStyle => IsVisible ? "grid" : "none";
+
+ private async Task Close()
+ {
+ IsVisible = false;
+ await IsVisibleChanged.InvokeAsync(IsVisible);
+ }
+}
diff --git a/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.css b/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.css
new file mode 100644
index 0000000..82882fa
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.min.css b/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.min.css
new file mode 100644
index 0000000..f1bb45f
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.scss b/Engine/Launcher/UI/Components/Flyouts/Flyout.razor.scss
new file mode 100644
index 0000000..decc8e0
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Flyouts/FlyoutBase.cs b/Engine/Launcher/UI/Components/Flyouts/FlyoutBase.cs
new file mode 100644
index 0000000..0d0d302
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Flyouts/FlyoutBase.cs
@@ -0,0 +1,46 @@
+using Microsoft.AspNetCore.Components;
+
+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();
+ await OnIsVisibleChangedAsync(false);
+ }
+}
diff --git a/Engine/Launcher/UI/Components/Flyouts/SettingsFlyout.razor b/Engine/Launcher/UI/Components/Flyouts/SettingsFlyout.razor
new file mode 100644
index 0000000..e8c7930
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Flyouts/SettingsFlyout.razor
@@ -0,0 +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/Engine/Launcher/UI/Components/Grid/Grid.razor b/Engine/Launcher/UI/Components/Grid/Grid.razor
new file mode 100644
index 0000000..2b23f84
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Grid/Grid.razor
@@ -0,0 +1,84 @@
+@using System.ComponentModel
+@using SharpEngine.Shared.Attributes
+
+@typeparam TItem
+
+
+
+
+
+
+
+ @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/Engine/Launcher/UI/Components/Grid/Grid.razor.css b/Engine/Launcher/UI/Components/Grid/Grid.razor.css
new file mode 100644
index 0000000..96e0633
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/Grid.razor.min.css b/Engine/Launcher/UI/Components/Grid/Grid.razor.min.css
new file mode 100644
index 0000000..74f740a
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/Grid.razor.scss b/Engine/Launcher/UI/Components/Grid/Grid.razor.scss
new file mode 100644
index 0000000..daf24c1
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/GridHeader.cs b/Engine/Launcher/UI/Components/Grid/GridHeader.cs
new file mode 100644
index 0000000..91e07ec
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Grid/GridHeader.cs
@@ -0,0 +1,24 @@
+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;
+ AllowFiltering = allowFiltering;
+ }
+}
diff --git a/Engine/Launcher/UI/Components/Grid/GridHeader.razor b/Engine/Launcher/UI/Components/Grid/GridHeader.razor
new file mode 100644
index 0000000..e5a8481
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Grid/GridHeader.razor
@@ -0,0 +1,47 @@
+
+
+ |
+
+@code {
+ /// Gets or sets the contents of the grid row header.
+ [Parameter]
+ public string Title { get; set; } = default!;
+
+ /// Gets or sets whether filtering is allowed for the header.
+ [Parameter]
+ public bool AllowFiltering { get; set; }
+
+ /// Gets or sets the event executed when the filter mode changes.
+ [Parameter]
+ public EventCallback FilterChanged { get; set; }
+
+ /// Gets or sets the filter mode of the header.
+ [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/Engine/Launcher/UI/Components/Grid/GridHeader.razor.css b/Engine/Launcher/UI/Components/Grid/GridHeader.razor.css
new file mode 100644
index 0000000..642257e
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/GridHeader.razor.min.css b/Engine/Launcher/UI/Components/Grid/GridHeader.razor.min.css
new file mode 100644
index 0000000..b5f165e
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/GridHeader.razor.scss b/Engine/Launcher/UI/Components/Grid/GridHeader.razor.scss
new file mode 100644
index 0000000..e0f25a0
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Grid/GridRow.razor
similarity index 100%
rename from Launcher/UI/Components/GridRow.razor
rename to Engine/Launcher/UI/Components/Grid/GridRow.razor
diff --git a/Launcher/UI/Components/DiscardIcon.razor b/Engine/Launcher/UI/Components/Icons/DiscardIcon.razor
similarity index 54%
rename from Launcher/UI/Components/DiscardIcon.razor
rename to Engine/Launcher/UI/Components/Icons/DiscardIcon.razor
index 0b36549..ee38ca5 100644
--- a/Launcher/UI/Components/DiscardIcon.razor
+++ b/Engine/Launcher/UI/Components/Icons/DiscardIcon.razor
@@ -1,11 +1,10 @@
-✖
+
+
+
@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/Engine/Launcher/UI/Components/Icons/Icon.razor b/Engine/Launcher/UI/Components/Icons/Icon.razor
new file mode 100644
index 0000000..4c2b415
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Icons/Icon.razor
@@ -0,0 +1,73 @@
+@using Launcher.Enums
+
+@switch (IconType)
+{
+ case SvgIcon.Home:
+
+ break;
+ case SvgIcon.Cog:
+
+ break;
+ case SvgIcon.Lens:
+
+ break;
+ case SvgIcon.Discard:
+ ✖
+ break;
+ case SvgIcon.Close:
+
+ break;
+ case SvgIcon.ChevronDown:
+
+ break;
+ case SvgIcon.ChevronUp:
+
+ break;
+ default:
+ break;
+}
+
+@code {
+
+ /// Gets or sets the type of the icon.
+ [Parameter]
+ public SvgIcon IconType { get; set; }
+
+ /// Gets or sets the size of the icon in pixels.
+ [Parameter]
+ public int SizePx { get; set; } = 16;
+}
diff --git a/Engine/Launcher/UI/Components/Layout.razor b/Engine/Launcher/UI/Components/Layout.razor
new file mode 100644
index 0000000..507dd2d
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Layout.razor
@@ -0,0 +1,23 @@
+@inherits LayoutComponentBase
+
+
+
+
+
+
+
+
+
+
+ @Body
+
+
+
+
+
+@code {
+ private bool _settingsVisible;
+
+ private void OpenSettingsFlyout()
+ => _settingsVisible = true;
+}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Layout.razor.css b/Engine/Launcher/UI/Components/Layout.razor.css
new file mode 100644
index 0000000..cb2bbbb
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Layout.razor.css
@@ -0,0 +1,6 @@
+.content {
+ margin: 0 1em;
+ padding: 0;
+ display: grid;
+ grid-template-rows: 100px auto;
+}
diff --git a/Engine/Launcher/UI/Components/Layout.razor.min.css b/Engine/Launcher/UI/Components/Layout.razor.min.css
new file mode 100644
index 0000000..aee90ef
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Layout.razor.min.css
@@ -0,0 +1 @@
+.content{margin:0 1em;padding:0;display:grid;grid-template-rows:100px auto;}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Layout.razor.scss b/Engine/Launcher/UI/Components/Layout.razor.scss
new file mode 100644
index 0000000..6a00208
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Layout.razor.scss
@@ -0,0 +1,6 @@
+.content {
+ margin: 0 1em;
+ padding: 0;
+ display: grid;
+ grid-template-rows: 100px auto;
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor
new file mode 100644
index 0000000..fe8bc2b
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor
@@ -0,0 +1,14 @@
+
+
+@code {
+ /// Gets or set the icon for the sidebar item.
+ [Parameter]
+ public SvgIcon Icon { get; set; }
+
+ /// Gets or sets the event executed when the sidebar item is clicked.
+ [Parameter]
+ public EventCallback OnClick { get; set; }
+
+ private async Task OnItemClicked()
+ => await OnClick.InvokeAsync();
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.css b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.css
new file mode 100644
index 0000000..63ddf80
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.css
@@ -0,0 +1,5 @@
+.sidebar-item {
+ cursor: pointer;
+ list-style: none;
+ padding: 0.5rem 1em;
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css
new file mode 100644
index 0000000..38c8234
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.min.css
@@ -0,0 +1 @@
+.sidebar-item{cursor:pointer;list-style:none;padding:.5rem 1em;}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.scss b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.scss
new file mode 100644
index 0000000..73c8089
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/SideBarItem.razor.scss
@@ -0,0 +1,5 @@
+.sidebar-item {
+ cursor: pointer;
+ list-style: none;
+ padding: 0.5rem 1em;
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/Sidebar.razor b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor
new file mode 100644
index 0000000..bc3ca2c
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor
@@ -0,0 +1,11 @@
+
+
+@code {
+ /// Gets or sets the sidebar items to be rendered within the sidebar.
+ [Parameter, EditorRequired]
+ public RenderFragment ChildContent { get; set; }
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.css b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.css
new file mode 100644
index 0000000..8d28b22
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.css
@@ -0,0 +1,6 @@
+.sidebar {
+ border-right: 1px solid black;
+}
+.sidebar ul {
+ padding: 0;
+}
diff --git a/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.min.css b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.min.css
new file mode 100644
index 0000000..d701475
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.min.css
@@ -0,0 +1 @@
+.sidebar{border-right:1px solid #000;}.sidebar ul{padding:0;}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.scss b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.scss
new file mode 100644
index 0000000..6a71104
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Navigation/Sidebar.razor.scss
@@ -0,0 +1,7 @@
+.sidebar {
+ border-right: 1px solid black;
+
+ ul {
+ padding: 0;
+ }
+}
diff --git a/Engine/Launcher/UI/Components/Notification/Notification.razor b/Engine/Launcher/UI/Components/Notification/Notification.razor
new file mode 100644
index 0000000..a46aee5
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Notification/Notification.razor
@@ -0,0 +1,30 @@
+@inherits DialogBase
+
+@inject INotificationService NotificationService
+
+
+
+
+ @foreach (var notification in NotificationService.Notifications)
+ {
+
+ @notification.Key @(notification.Value > 1 ? $"({notification.Value})" : "")
+
+
+ }
+
+
+
+
+@code {
+ /// Gets or sets the type of the notification.
+ [Parameter]
+ public NotificationType Type { get; set; } = NotificationType.Warning;
+
+ ///
+ protected override void OnInitialized()
+ {
+ NotificationService.SetInvokeAsync(InvokeAsync);
+ NotificationService.SetOnNotificationsChanged(StateHasChanged);
+ }
+}
diff --git a/Engine/Launcher/UI/Components/Notification/Notification.razor.css b/Engine/Launcher/UI/Components/Notification/Notification.razor.css
new file mode 100644
index 0000000..e12e310
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Notification/Notification.razor.css
@@ -0,0 +1,18 @@
+.notification-container {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ pointer-events: none;
+}
+
+.notification {
+ position: relative;
+ display: flex;
+ top: 50px;
+ flex-direction: column;
+ align-items: end;
+}
+
+.notification-content {
+ pointer-events: auto;
+}
diff --git a/Engine/Launcher/UI/Components/Notification/Notification.razor.min.css b/Engine/Launcher/UI/Components/Notification/Notification.razor.min.css
new file mode 100644
index 0000000..c478afd
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Notification/Notification.razor.min.css
@@ -0,0 +1 @@
+.notification-container{width:100%;height:100%;position:absolute;pointer-events:none;}.notification{position:relative;display:flex;top:50px;flex-direction:column;align-items:end;}.notification-content{pointer-events:auto;}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Notification/Notification.razor.scss b/Engine/Launcher/UI/Components/Notification/Notification.razor.scss
new file mode 100644
index 0000000..f44c2d1
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Notification/Notification.razor.scss
@@ -0,0 +1,18 @@
+.notification-container {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ pointer-events: none;
+}
+
+.notification {
+ position: relative;
+ display: flex;
+ top: 50px;
+ flex-direction: column;
+ align-items: end;
+}
+
+.notification-content {
+ pointer-events: auto;
+}
\ No newline at end of file
diff --git a/Engine/Launcher/UI/Components/Notification/NotificationType.cs b/Engine/Launcher/UI/Components/Notification/NotificationType.cs
new file mode 100644
index 0000000..a5fd255
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Notification/NotificationType.cs
@@ -0,0 +1,32 @@
+namespace Launcher.UI.Components.Notification
+{
+ ///
+ /// Represents the type of notification.
+ ///
+ public enum NotificationType
+ {
+ /// Primary notification type.
+ Primary,
+
+ /// Secondary notification type.
+ Secondary,
+
+ /// Success notification type.
+ Success,
+
+ /// Danger notification type.
+ Danger,
+
+ /// Warning notification type.
+ Warning,
+
+ /// Info notification type.
+ Info,
+
+ /// Light notification type.
+ Light,
+
+ /// Dark notification type.
+ Dark,
+ }
+}
diff --git a/Launcher/UI/Components/Dialogs/NotificationType.cs b/Engine/Launcher/UI/Components/Notification/NotificationTypeExtensions.cs
similarity index 59%
rename from Launcher/UI/Components/Dialogs/NotificationType.cs
rename to Engine/Launcher/UI/Components/Notification/NotificationTypeExtensions.cs
index 9f34c0a..7762cf4 100644
--- a/Launcher/UI/Components/Dialogs/NotificationType.cs
+++ b/Engine/Launcher/UI/Components/Notification/NotificationTypeExtensions.cs
@@ -1,35 +1,5 @@
-namespace Launcher.UI.Components
+namespace Launcher.UI.Components.Notification
{
- ///
- /// Represents the type of notification.
- ///
- public enum NotificationType
- {
- /// Primary notification type.
- Primary,
-
- /// Secondary notification type.
- Secondary,
-
- /// Success notification type.
- Success,
-
- /// Danger notification type.
- Danger,
-
- /// Warning notification type.
- Warning,
-
- /// Info notification type.
- Info,
-
- /// Light notification type.
- Light,
-
- /// Dark notification type.
- Dark,
- }
-
///
/// Extension methods for .
///
diff --git a/Engine/Launcher/UI/Components/Searchbox.razor b/Engine/Launcher/UI/Components/Searchbox.razor
new file mode 100644
index 0000000..2417a3e
--- /dev/null
+++ b/Engine/Launcher/UI/Components/Searchbox.razor
@@ -0,0 +1,35 @@
+
+
+
+
+
+@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; }
+
+ 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/Engine/Launcher/UI/Components/Searchbox.razor.css b/Engine/Launcher/UI/Components/Searchbox.razor.css
new file mode 100644
index 0000000..9f2bc34
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Searchbox.razor.min.css b/Engine/Launcher/UI/Components/Searchbox.razor.min.css
new file mode 100644
index 0000000..2641fc8
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Components/Searchbox.razor.scss b/Engine/Launcher/UI/Components/Searchbox.razor.scss
new file mode 100644
index 0000000..4dd8607
--- /dev/null
+++ b/Engine/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/Engine/Launcher/UI/Home.razor b/Engine/Launcher/UI/Home.razor
new file mode 100644
index 0000000..0eae1e4
--- /dev/null
+++ b/Engine/Launcher/UI/Home.razor
@@ -0,0 +1,41 @@
+@page "/"
+
+
+
+
+
+
+
+
+
+
+
+
+ @context.Name
+ @context.Path
+ |
+ @context.LastModified |
+
+
+
+ |
+
+
+
+
+
+ @{
+ var project = (Project)context;
+
+
+
+
+ }
+
\ No newline at end of file
diff --git a/Launcher/UI/Home.razor.cs b/Engine/Launcher/UI/Home.razor.cs
similarity index 61%
rename from Launcher/UI/Home.razor.cs
rename to Engine/Launcher/UI/Home.razor.cs
index 71c52fb..5fe6573 100644
--- a/Launcher/UI/Home.razor.cs
+++ b/Engine/Launcher/UI/Home.razor.cs
@@ -1,11 +1,17 @@
using Launcher.Services;
+using Launcher.UI.Components;
using Microsoft.AspNetCore.Components;
+
+using SharpEngine.Shared.Dto;
+using System.Diagnostics;
using System.Text.Json;
-using System.Text.Json.Serialization;
+
+using FilterMode = SharpEngine.Shared.Enums.FilterMode;
namespace Launcher.UI
{
/// Represents the home page of the launcher.
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S6966:Awaitable method should be used", Justification = "")]
public partial class Home : ComponentBase
{
[Inject]
@@ -17,12 +23,26 @@ public partial class Home : ComponentBase
private bool _showConfirmDelete;
private bool _showCreateProjectDialog;
- private List Projects { get; set; } = [];
+ private ContextMenu? _contextMenu;
+
+ 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);
@@ -32,17 +52,35 @@ protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
- // TODO: Get current launcher version
- // TODO: Check if GitHub has a new version released.
+ // TODO: #96 Get current launcher version
+ // TODO: #96 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;
@@ -58,7 +96,7 @@ private void OnDeleteProjectConfirm()
if (project is not null)
{
Projects.Remove(project);
- // TODO: Delete project files
+ // TODO: #26 Delete project files
}
else
_notificationService.Show($"Unable to delete project '{project?.Name}'.", false, _selectedProject, Projects);
@@ -87,7 +125,7 @@ private async Task OpenExistingFileAsync()
if (pickedFile == null)
return;
- if (pickedFile.FileName.EndsWith(".sharpproject") == false)
+ if (!pickedFile.FileName.EndsWith(".sharpproject"))
{
_notificationService.Show("Invalid file type selected. Please select a .sharpproject file.");
return;
@@ -113,5 +151,28 @@ private void SetProjects(Project project)
string json = JsonSerializer.Serialize(Projects);
File.WriteAllText(_projectsFilePath, json);
}
+
+ // If the user sets the project path as an executable command, this will execute that command.
+ // In most cases that should be fine as the developer has the launcher installed on their machine, however this still should be resolved soon.
+ 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);
+ }
+
+ private void OnFiltersChanged(Dictionary filters)
+ {
+ _filteredProjects = [.. Projects.FilterBy(filters)];
+ StateHasChanged();
+ }
}
}
diff --git a/Launcher/UI/Home.razor.css b/Engine/Launcher/UI/Home.razor.css
similarity index 98%
rename from Launcher/UI/Home.razor.css
rename to Engine/Launcher/UI/Home.razor.css
index ffb5a06..dfc3ab4 100644
--- a/Launcher/UI/Home.razor.css
+++ b/Engine/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/Engine/Launcher/UI/Home.razor.min.css
similarity index 50%
rename from Launcher/UI/Home.razor.min.css
rename to Engine/Launcher/UI/Home.razor.min.css
index 384ee6f..cb60f75 100644
--- a/Launcher/UI/Home.razor.min.css
+++ b/Engine/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/Engine/Launcher/UI/Home.razor.scss
similarity index 97%
rename from Launcher/UI/Home.razor.scss
rename to Engine/Launcher/UI/Home.razor.scss
index be215ee..bbdf065 100644
--- a/Launcher/UI/Home.razor.scss
+++ b/Engine/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/Routes.razor b/Engine/Launcher/UI/Routes.razor
similarity index 100%
rename from Launcher/UI/Routes.razor
rename to Engine/Launcher/UI/Routes.razor
diff --git a/Engine/Launcher/UI/_Imports.razor b/Engine/Launcher/UI/_Imports.razor
new file mode 100644
index 0000000..ea37d20
--- /dev/null
+++ b/Engine/Launcher/UI/_Imports.razor
@@ -0,0 +1,25 @@
+@using System.Net.Http
+@using System.Net.Http.Json
+@using System.Diagnostics
+
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+
+@using Launcher
+@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/Engine/Launcher/compilerconfig.json b/Engine/Launcher/compilerconfig.json
new file mode 100644
index 0000000..fa73a0f
--- /dev/null
+++ b/Engine/Launcher/compilerconfig.json
@@ -0,0 +1,62 @@
+[
+ {
+ "outputFile": "UI/Home.css",
+ "inputFile": "UI/Home.scss"
+ },
+ {
+ "outputFile": "UI/Home.razor.css",
+ "inputFile": "UI/Home.razor.scss"
+ },
+ {
+ "outputFile": "wwwroot/css/additional.razor.css",
+ "inputFile": "wwwroot/css/additional.razor.scss"
+ },
+ {
+ "outputFile": "wwwroot/css/additional.css",
+ "inputFile": "wwwroot/css/additional.scss"
+ },
+ {
+ "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"
+ },
+ {
+ "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/Launcher/compilerconfig.json.defaults b/Engine/Launcher/compilerconfig.json.defaults
similarity index 100%
rename from Launcher/compilerconfig.json.defaults
rename to Engine/Launcher/compilerconfig.json.defaults
diff --git a/Launcher/wwwroot/css/additional.css b/Engine/Launcher/wwwroot/css/additional.css
similarity index 100%
rename from Launcher/wwwroot/css/additional.css
rename to Engine/Launcher/wwwroot/css/additional.css
diff --git a/Launcher/wwwroot/css/additional.min.css b/Engine/Launcher/wwwroot/css/additional.min.css
similarity index 100%
rename from Launcher/wwwroot/css/additional.min.css
rename to Engine/Launcher/wwwroot/css/additional.min.css
diff --git a/Launcher/wwwroot/css/additional.scss b/Engine/Launcher/wwwroot/css/additional.scss
similarity index 100%
rename from Launcher/wwwroot/css/additional.scss
rename to Engine/Launcher/wwwroot/css/additional.scss
diff --git a/Launcher/wwwroot/css/app.css b/Engine/Launcher/wwwroot/css/app.css
similarity index 97%
rename from Launcher/wwwroot/css/app.css
rename to Engine/Launcher/wwwroot/css/app.css
index aad5a54..2196041 100644
--- a/Launcher/wwwroot/css/app.css
+++ b/Engine/Launcher/wwwroot/css/app.css
@@ -100,5 +100,11 @@ 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;
+}
+
+p {
+ margin: 0;
}
\ No newline at end of file
diff --git a/Launcher/wwwroot/css/bootstrap/bootstrap.min.css b/Engine/Launcher/wwwroot/css/bootstrap/bootstrap.min.css
similarity index 100%
rename from Launcher/wwwroot/css/bootstrap/bootstrap.min.css
rename to Engine/Launcher/wwwroot/css/bootstrap/bootstrap.min.css
diff --git a/Launcher/wwwroot/css/bootstrap/bootstrap.min.css.map b/Engine/Launcher/wwwroot/css/bootstrap/bootstrap.min.css.map
similarity index 100%
rename from Launcher/wwwroot/css/bootstrap/bootstrap.min.css.map
rename to Engine/Launcher/wwwroot/css/bootstrap/bootstrap.min.css.map
diff --git a/Launcher/wwwroot/favicon.png b/Engine/Launcher/wwwroot/favicon.png
similarity index 100%
rename from Launcher/wwwroot/favicon.png
rename to Engine/Launcher/wwwroot/favicon.png
diff --git a/Launcher/wwwroot/index.html b/Engine/Launcher/wwwroot/index.html
similarity index 100%
rename from Launcher/wwwroot/index.html
rename to Engine/Launcher/wwwroot/index.html
diff --git a/Engine/ObjLoader/DataStore.cs b/Engine/ObjLoader/DataStore.cs
new file mode 100644
index 0000000..5ee2b5c
--- /dev/null
+++ b/Engine/ObjLoader/DataStore.cs
@@ -0,0 +1,52 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.Loader.Data.Elements;
+
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ObjLoader
+{
+ public class DataStore : IGroupDataStore, IFaceGroup
+ {
+ private Group _currentGroup;
+
+ private readonly List _groups = [];
+ private readonly List _materials = [];
+
+ public List Vertices { get; } = [];
+
+ public List Textures { get; } = [];
+
+ public List Normals { get; } = [];
+
+ public List Materials { get; } = [];
+
+ public List Groups { get; } = [];
+
+ public DataStore()
+ {
+ PushGroup("default");
+ }
+
+ public void AddFace(Face face)
+ {
+ _currentGroup.AddFace(face);
+ }
+
+ public void PushGroup(string groupName)
+ {
+ _currentGroup = new Group(groupName);
+ _groups.Add(_currentGroup);
+ }
+
+ ///
+ public void SetMaterial(string materialName)
+ {
+ var material = _materials.SingleOrDefault(x => x.Name.EqualsOrdinalIgnoreCase(materialName));
+ _currentGroup.Material = material;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/LoaderBase.cs b/Engine/ObjLoader/Loaders/LoaderBase.cs
new file mode 100644
index 0000000..0de39a2
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/LoaderBase.cs
@@ -0,0 +1,38 @@
+using System.IO;
+
+namespace ObjLoader.Loader.Loaders
+{
+ public abstract class LoaderBase
+ {
+ private StreamReader _lineStreamReader;
+
+ protected void StartLoad(Stream lineStream)
+ {
+ _lineStreamReader = new StreamReader(lineStream);
+
+ while (!_lineStreamReader.EndOfStream)
+ {
+ ParseLine();
+ }
+ }
+
+ private void ParseLine()
+ {
+ var currentLine = _lineStreamReader.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(currentLine))
+ return;
+
+ if (currentLine[0] == '#')
+ return;
+
+ var fields = currentLine.Trim().Split(null, 2);
+ var keyword = fields[0].Trim();
+ var data = fields[1].Trim();
+
+ ParseLine(keyword, data);
+ }
+
+ protected abstract void ParseLine(string keyword, string data);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs b/Engine/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs
new file mode 100644
index 0000000..4417065
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs
@@ -0,0 +1,7 @@
+namespace ObjLoader.Loaders.MaterialLoader
+{
+ public interface IMaterialLibraryLoaderFacade
+ {
+ void Load(string materialFileName);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs b/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs
new file mode 100644
index 0000000..90ebb1b
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs
@@ -0,0 +1,107 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.Loader.Loaders;
+using SharpEngine.Core.Components.Properties;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Numerics;
+
+namespace ObjLoader.Loaders.MaterialLoader
+{
+ // https://paulbourke.net/dataformats/mtl/
+
+ public class MaterialLibraryLoader : LoaderBase
+ {
+ private string _objPath;
+ private readonly DataStore _dataStore;
+ private readonly Dictionary> _parseActionDictionary = [];
+ private readonly List _unrecognizedLines = [];
+
+ private Material CurrentMaterial { get; set; }
+
+ // TODO: #2 See Model class. This support both materials and textures
+
+ public MaterialLibraryLoader(string objPath, DataStore dataStore)
+ {
+ _objPath = objPath;
+ _dataStore = dataStore;
+
+ AddParseAction("newmtl", PushMaterial);
+ AddParseAction("Ka", d => CurrentMaterial.AmbientColor = ParseVec3(d));
+ AddParseAction("Kd", d => CurrentMaterial.DiffuseColor = ParseVec3(d));
+ AddParseAction("Ks", d => CurrentMaterial.SpecularColor = ParseVec3(d));
+ AddParseAction("Ns", d => CurrentMaterial.SpecularCoefficient = d.ParseInvariantFloat());
+
+ AddParseAction("d", d => CurrentMaterial.Transparency = d.ParseInvariantFloat());
+ AddParseAction("Tr", d => CurrentMaterial.Transparency = d.ParseInvariantFloat());
+
+ AddParseAction("illum", i => CurrentMaterial.IlluminationModel = i.ParseInvariantInt());
+
+ AddParseAction("map_Ka", m => CurrentMaterial.AmbientTextureMap = m);
+ AddParseAction("map_Kd", m => CurrentMaterial.DiffuseTextureMap = m);
+
+ AddParseAction("map_Ks", m => CurrentMaterial.SpecularTextureMap = m);
+ AddParseAction("map_Ns", m => CurrentMaterial.SpecularHighlightTextureMap = m);
+
+ AddParseAction("map_d", m => CurrentMaterial.AlphaTextureMap = m);
+
+ AddParseAction("map_bump", m => CurrentMaterial.BumpMap = m);
+ AddParseAction("bump", m => CurrentMaterial.BumpMap = m);
+
+ AddParseAction("disp", m => CurrentMaterial.DisplacementMap = m);
+
+ AddParseAction("decal", m => CurrentMaterial.StencilDecalMap = m);
+ }
+
+ private void AddParseAction(string key, Action action) => _parseActionDictionary.Add(key.ToLowerInvariant(), action);
+
+ ///
+ protected override void ParseLine(string keyword, string data)
+ {
+ var parseAction = GetKeywordAction(keyword);
+
+ if (parseAction == null)
+ {
+ _unrecognizedLines.Add(keyword + " " + data);
+ return;
+ }
+
+ parseAction(data);
+ }
+
+ private Action GetKeywordAction(string keyword)
+ {
+ _parseActionDictionary.TryGetValue(keyword.ToLowerInvariant(), out var action);
+
+ return action;
+ }
+
+ private void PushMaterial(string materialName)
+ {
+ CurrentMaterial = new Material(materialName);
+ _dataStore.Materials.Add(CurrentMaterial);
+ }
+
+ private static Vector3 ParseVec3(string data)
+ {
+ string[] parts = data.Split(' ');
+
+ float x = parts[0].ParseInvariantFloat();
+ float y = parts[1].ParseInvariantFloat();
+ float z = parts[2].ParseInvariantFloat();
+
+ return new Vector3(x, y, z);
+ }
+
+ public void Load(Stream lineStream) => StartLoad(lineStream);
+
+ ///
+ public Stream Open(string materialFilePath)
+ {
+ var dir = Path.GetDirectoryName(_objPath);
+ var path = Path.Combine(dir, materialFilePath);
+ return File.Open(path, FileMode.Open, FileAccess.Read);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs b/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs
new file mode 100644
index 0000000..c6f62af
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs
@@ -0,0 +1,34 @@
+using SharpEngine.Shared;
+using System.IO;
+
+namespace ObjLoader.Loaders.MaterialLoader
+{
+ public class MaterialLibraryLoaderFacade : IMaterialLibraryLoaderFacade
+ {
+ private readonly MaterialLibraryLoader _loader;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// Provides the functionality to load material libraries.
+ public MaterialLibraryLoaderFacade(MaterialLibraryLoader loader)
+ {
+ _loader = loader;
+ }
+
+ ///
+ public void Load(string materialFileName)
+ {
+ if (!File.Exists(materialFileName))
+ {
+ Debug.Log.Warning("Material file '{MaterialFileName}' doesn't exist.", materialFileName);
+ return;
+ }
+
+ using var stream = _loader.Open(materialFileName);
+
+ if (stream != null)
+ _loader.Load(stream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
new file mode 100644
index 0000000..798bc3c
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
@@ -0,0 +1,64 @@
+using ObjLoader.Loader.Loaders;
+using ObjLoader.TypeParsers;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using Silk.NET.Core.Contexts;
+using Silk.NET.OpenGL;
+using System.Collections.Generic;
+using System.IO;
+using Tutorial;
+
+namespace ObjLoader.Loaders.ObjLoader
+{
+ public class ObjLoader : LoaderBase
+ {
+ private readonly string _path;
+ private readonly DataStore _dataStore;
+ private readonly List _typeParsers = [];
+ private readonly List _unrecognizedLines = [];
+
+ public ObjLoader(string path, DataStore dataStore)
+ {
+ _path = path;
+ _dataStore = dataStore;
+ }
+
+ public ObjLoader SetupTypeParsers(params ITypeParser[] parsers)
+ {
+ foreach (var parser in parsers)
+ _typeParsers.Add(parser);
+
+ return this;
+ }
+
+ ///
+ protected override void ParseLine(string keyword, string data)
+ {
+ foreach (var typeParser in _typeParsers)
+ if (typeParser.CanParse(keyword))
+ {
+ typeParser.Parse(data);
+ return;
+ }
+
+ _unrecognizedLines.Add(keyword + " " + data);
+ }
+
+ public List Load(GL gl)
+ {
+ var fileStream = new FileStream(_path, FileMode.Open, FileAccess.Read);
+ StartLoad(fileStream);
+
+ return
+ [
+ new(gl)
+ {
+ Vertices2 = _dataStore.Vertices,
+ TextureCoordinates2 = _dataStore.Textures,
+ Normals2 = _dataStore.Normals,
+ Groups = _dataStore.Groups,
+ Materials = _dataStore.Materials
+ }
+ ];
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
new file mode 100644
index 0000000..408855c
--- /dev/null
+++ b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
@@ -0,0 +1,65 @@
+using ObjLoader.Loader.TypeParsers;
+using ObjLoader.Loaders.MaterialLoader;
+
+using SharpEngine.Core.Entities.Properties.Meshes;
+using Silk.NET.OpenGL;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Tutorial;
+
+namespace ObjLoader.Loaders.ObjLoader
+{
+ ///
+ /// Handles loading 3D models.
+ ///
+ public static class ObjLoaderFactory
+ {
+ const string FbxExtension = ".fbx";
+ const string ObjExtension = ".obj";
+
+ ///
+ /// Loads a mesh based on the file extension of the provided path.
+ ///
+ /// The OpenGL context where the model should be bound.
+ /// Specifies the file path of the mesh to be loaded, which determines the loading method based on its extension.
+ /// The model loaded from the file.
+ /// Thrown when the file extension of the provided path is not recognized as a supported mesh format.
+ public static Model_Old Load(GL gl, string path)
+ => Path.GetExtension(path) switch
+ {
+ FbxExtension => LoadFbx("", path),
+ ObjExtension => LoadObj(gl, path),
+ _ => throw new NotSupportedException($"The file extension {Path.GetExtension(path)} is not a supported mesh file.")
+ };
+
+ // TODO: #3 Load fbx mesh from file
+ private static Model_Old LoadFbx(string identifier, string meshFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static Model_Old LoadObj(GL gl, string path)
+ {
+ return new Model_Old(gl, path);
+
+ // TOOD: Use the ObjLoader to load the model instead of the external library.
+
+ /*var dataStore = new DataStore();
+
+ var faceParser = new FaceParser(dataStore);
+ var groupParser = new GroupParser(dataStore);
+ var normalParser = new NormalParser(dataStore);
+ var textureParser = new TextureParser(dataStore);
+ var vertexParser = new VertexParser(dataStore);
+
+ var materialLibraryLoader = new MaterialLibraryLoader(path, dataStore);
+ var materialLibraryParser = new MaterialLibraryParser(materialLibraryLoader);
+ var useMaterialParser = new UseMaterialParser(dataStore);
+
+ var loader = new ObjLoader(path, dataStore).SetupTypeParsers(faceParser, groupParser, normalParser, textureParser, vertexParser, materialLibraryParser, useMaterialParser);
+ return loader.Load(gl);
+ */
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj b/Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj
new file mode 100644
index 0000000..44cd0f4
--- /dev/null
+++ b/Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/FaceParser.cs b/Engine/ObjLoader/TypeParsers/FaceParser.cs
new file mode 100644
index 0000000..c7328f7
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/FaceParser.cs
@@ -0,0 +1,59 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.Loader.Data.Elements;
+using ObjLoader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using System;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class FaceParser : TypeParserBase, ITypeParser
+ {
+ private readonly IFaceGroup _faceGroup;
+
+ public FaceParser(IFaceGroup faceGroup)
+ {
+ _faceGroup = faceGroup;
+ }
+
+ ///
+ protected override string Keyword => "f";
+
+ ///
+ public override void Parse(string line)
+ {
+ string[] vertices = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+
+ var face = new Face();
+
+ foreach (string vertexString in vertices)
+ {
+ var faceVertex = ParseFaceVertex(vertexString);
+ face.AddVertex(faceVertex);
+ }
+
+ _faceGroup.AddFace(face);
+ }
+
+ private static FaceVertex ParseFaceVertex(string vertexString)
+ {
+ string[] fields = vertexString.Split('/', StringSplitOptions.None);
+
+ int vertexIndex = fields[0].ParseInvariantInt();
+ var faceVertex = new FaceVertex(vertexIndex, 0, 0);
+
+ if (fields.Length > 1)
+ {
+ int textureIndex = fields[1].Length == 0 ? 0 : fields[1].ParseInvariantInt();
+ faceVertex.TextureIndex = textureIndex;
+ }
+
+ if (fields.Length > 2)
+ {
+ int normalIndex = fields.Length > 2 && fields[2].Length == 0 ? 0 : fields[2].ParseInvariantInt();
+ faceVertex.NormalIndex = normalIndex;
+ }
+
+ return faceVertex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/GroupParser.cs b/Engine/ObjLoader/TypeParsers/GroupParser.cs
new file mode 100644
index 0000000..b8d1512
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/GroupParser.cs
@@ -0,0 +1,21 @@
+using ObjLoader.TypeParsers;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class GroupParser : TypeParserBase, ITypeParser
+ {
+ private readonly DataStore _dataStore;
+
+ public GroupParser(DataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "g";
+
+ ///
+ public override void Parse(string line)
+ => _dataStore.PushGroup(line);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/ITypeParser.cs b/Engine/ObjLoader/TypeParsers/ITypeParser.cs
new file mode 100644
index 0000000..9cb2d7e
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/ITypeParser.cs
@@ -0,0 +1,8 @@
+namespace ObjLoader.TypeParsers
+{
+ public interface ITypeParser
+ {
+ bool CanParse(string keyword);
+ void Parse(string line);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/MaterialLibraryParser.cs b/Engine/ObjLoader/TypeParsers/MaterialLibraryParser.cs
new file mode 100644
index 0000000..7b7c2b4
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/MaterialLibraryParser.cs
@@ -0,0 +1,22 @@
+using ObjLoader.Loader.Loaders;
+using ObjLoader.Loaders.MaterialLoader;
+using ObjLoader.TypeParsers;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class MaterialLibraryParser : TypeParserBase, ITypeParser
+ {
+ private readonly IMaterialLibraryLoaderFacade _libraryLoaderFacade;
+
+ public MaterialLibraryParser(MaterialLibraryLoader loader)
+ {
+ _libraryLoaderFacade = new MaterialLibraryLoaderFacade(loader);
+ }
+
+ ///
+ protected override string Keyword => "mtllib";
+
+ ///
+ public override void Parse(string line) => _libraryLoaderFacade.Load(line);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/NormalParser.cs b/Engine/ObjLoader/TypeParsers/NormalParser.cs
new file mode 100644
index 0000000..49af71a
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/NormalParser.cs
@@ -0,0 +1,32 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.TypeParsers;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class NormalParser : TypeParserBase, ITypeParser
+ {
+ private readonly DataStore _dataStore;
+
+ public NormalParser(DataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "vn";
+
+ ///
+ public override void Parse(string line)
+ {
+ string[] parts = line.Split(' ');
+
+ float x = parts[0].ParseInvariantFloat();
+ float y = parts[1].ParseInvariantFloat();
+ float z = parts[2].ParseInvariantFloat();
+
+ var normal = new Normal(x, y, z);
+ _dataStore.Normals.Add(normal);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/TextureParser.cs b/Engine/ObjLoader/TypeParsers/TextureParser.cs
new file mode 100644
index 0000000..0c596ba
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/TextureParser.cs
@@ -0,0 +1,31 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.TypeParsers;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class TextureParser : TypeParserBase, ITypeParser
+ {
+ private readonly DataStore _dataStore;
+
+ public TextureParser(DataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "vt";
+
+ ///
+ public override void Parse(string line)
+ {
+ string[] parts = line.Split(' ');
+
+ float x = parts[0].ParseInvariantFloat();
+ float y = parts[1].ParseInvariantFloat();
+
+ var texture = new TextureCoordinate(x, y);
+ _dataStore.Textures.Add(texture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/TypeParserBase.cs b/Engine/ObjLoader/TypeParsers/TypeParserBase.cs
new file mode 100644
index 0000000..7880db8
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/TypeParserBase.cs
@@ -0,0 +1,14 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.TypeParsers;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public abstract class TypeParserBase : ITypeParser
+ {
+ protected abstract string Keyword { get; }
+
+ public bool CanParse(string keyword) => keyword.EqualsOrdinalIgnoreCase(Keyword);
+
+ public abstract void Parse(string line);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/UseMaterialParser.cs b/Engine/ObjLoader/TypeParsers/UseMaterialParser.cs
new file mode 100644
index 0000000..72f33d8
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/UseMaterialParser.cs
@@ -0,0 +1,20 @@
+using ObjLoader.TypeParsers;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class UseMaterialParser : TypeParserBase, ITypeParser
+ {
+ private readonly DataStore _dataStore;
+
+ public UseMaterialParser(DataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "usemtl";
+
+ ///
+ public override void Parse(string line) => _dataStore.SetMaterial(line);
+ }
+}
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/VertexParser.cs b/Engine/ObjLoader/TypeParsers/VertexParser.cs
new file mode 100644
index 0000000..d6b4fcf
--- /dev/null
+++ b/Engine/ObjLoader/TypeParsers/VertexParser.cs
@@ -0,0 +1,37 @@
+using ObjLoader.Loader.Common;
+using ObjLoader.TypeParsers;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+using System;
+using Tutorial;
+
+namespace ObjLoader.Loader.TypeParsers
+{
+ public class VertexParser : TypeParserBase, ITypeParser
+ {
+ private readonly DataStore _dataStore;
+
+ public VertexParser(DataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "v";
+
+ ///
+ public override void Parse(string line)
+ {
+ string[] parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+
+ float x = parts[0].ParseInvariantFloat();
+ float y = parts[1].ParseInvariantFloat();
+ float z = parts[2].ParseInvariantFloat();
+
+ var vertex = new Vertex()
+ {
+ Position = new System.Numerics.Vector3(x, y, z),
+ };
+ _dataStore.Vertices.Add(vertex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Resources/Screenshots/editor.png b/Engine/Resources/Screenshots/editor.png
similarity index 100%
rename from Resources/Screenshots/editor.png
rename to Engine/Resources/Screenshots/editor.png
diff --git a/Resources/Screenshots/launcher.png b/Engine/Resources/Screenshots/launcher.png
similarity index 100%
rename from Resources/Screenshots/launcher.png
rename to Engine/Resources/Screenshots/launcher.png
diff --git a/Resources/icon.ico b/Engine/Resources/icon.ico
similarity index 100%
rename from Resources/icon.ico
rename to Engine/Resources/icon.ico
diff --git a/Launcher/Resources/AppIcon/icon.png b/Engine/Resources/icon.png
similarity index 100%
rename from Launcher/Resources/AppIcon/icon.png
rename to Engine/Resources/icon.png
diff --git a/Resources/icon.svg b/Engine/Resources/icon.svg
similarity index 100%
rename from Resources/icon.svg
rename to Engine/Resources/icon.svg
diff --git a/Resources/icon.xcf b/Engine/Resources/icon.xcf
similarity index 100%
rename from Resources/icon.xcf
rename to Engine/Resources/icon.xcf
diff --git a/Engine/SharpEngine.Core.Components/IComponent.cs b/Engine/SharpEngine.Core.Components/IComponent.cs
new file mode 100644
index 0000000..fdeaa85
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/IComponent.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpEngine.Core.Components;
+
+// TODO: #53 Entity Component System
+
+internal interface IComponent
+{
+}
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs
new file mode 100644
index 0000000..e6c0922
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs
@@ -0,0 +1,9 @@
+using ObjLoader.Loader.Data.Elements;
+
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface IFaceGroup
+ {
+ void AddFace(Face face);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IGroupDataStore.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IGroupDataStore.cs
new file mode 100644
index 0000000..3fe9d69
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IGroupDataStore.cs
@@ -0,0 +1,7 @@
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface IGroupDataStore
+ {
+ void PushGroup(string groupName);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IMaterialLibrary.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IMaterialLibrary.cs
new file mode 100644
index 0000000..5652449
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IMaterialLibrary.cs
@@ -0,0 +1,9 @@
+using SharpEngine.Core.Components.Properties;
+
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface IMaterialLibrary
+ {
+ void Push(Material material);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/INormalDataStore.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/INormalDataStore.cs
new file mode 100644
index 0000000..10ae87d
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/INormalDataStore.cs
@@ -0,0 +1,9 @@
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface INormalDataStore
+ {
+ void AddNormal(Normal normal);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/ITextureDataStore.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/ITextureDataStore.cs
new file mode 100644
index 0000000..a1591ff
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/ITextureDataStore.cs
@@ -0,0 +1,9 @@
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface ITextureDataStore
+ {
+ void AddTexture(TextureCoordinate texture);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IVertexDataStore.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IVertexDataStore.cs
new file mode 100644
index 0000000..6667f06
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IVertexDataStore.cs
@@ -0,0 +1,9 @@
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore
+{
+ public interface IVertexDataStore
+ {
+ void AddVertex(Vertex vertex);
+ }
+}
\ No newline at end of file
diff --git a/Core/Entities/Properties/BoundingBox.cs b/Engine/SharpEngine.Core.Components/Properties/BoundingBox.cs
similarity index 84%
rename from Core/Entities/Properties/BoundingBox.cs
rename to Engine/SharpEngine.Core.Components/Properties/BoundingBox.cs
index 1119c28..e774eaa 100644
--- a/Core/Entities/Properties/BoundingBox.cs
+++ b/Engine/SharpEngine.Core.Components/Properties/BoundingBox.cs
@@ -28,14 +28,15 @@ public BoundingBox(Vector3 min, Vector3 max)
Max = max;
}
+ // TODO: #5 This should be calculated by the mesh.
///
/// 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);
- return new BoundingBox(min, max);
+ return new BoundingBox((Vector3)min, (Vector3)max);
}
}
diff --git a/Engine/SharpEngine.Core.Components/Properties/ITransform.cs b/Engine/SharpEngine.Core.Components/Properties/ITransform.cs
new file mode 100644
index 0000000..a3b5dbd
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/ITransform.cs
@@ -0,0 +1,23 @@
+using SharpEngine.Core.Numerics;
+using System.Numerics;
+
+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/Engine/SharpEngine.Core.Components/Properties/Material.cs b/Engine/SharpEngine.Core.Components/Properties/Material.cs
new file mode 100644
index 0000000..07c21be
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Material.cs
@@ -0,0 +1,127 @@
+using Silk.NET.OpenGL;
+using System.Numerics;
+using Shader = SharpEngine.Core.Shaders.Shader;
+using Texture = SharpEngine.Core.Components.Properties.Textures.Texture;
+
+namespace SharpEngine.Core.Components.Properties;
+
+///
+/// Represents the material rendered onto a game object.
+///
+public class Material
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The diffuse map texture of the material.
+ /// The specular map texture of the material. Defaults to the diffuse map if not provided.
+ public Material(Texture diffuseMap, Texture? specularMap = null)
+ {
+ DiffuseMap = diffuseMap;
+ SpecularMap = specularMap ?? diffuseMap;
+
+ Specular = new(0.5f, 0.5f, 0.5f);
+ Shininess = 32.0f;
+ }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The name assigned to the new material.
+ public Material(string materialName)
+ {
+ Name = materialName;
+ }
+
+ /// Gets or sets the name of the material.
+ public string Name { get; set; }
+
+ /// Gets or sets the diffuse map texture.
+ public Texture DiffuseMap { get; set; }
+
+ /// Gets or sets the path to the diffuse texture map.
+ public string DiffuseTextureMap { get; set; }
+
+ /// Gets or sets the specular map texture.
+ public Texture SpecularMap { get; set; }
+
+ /// Gets or sets the path to the specular texture map.
+ public string SpecularTextureMap { get; set; }
+
+ /// Gets a value indicating whether the material uses a specular map.
+ public bool UseSpecularMap => SpecularMap.Handle != DiffuseMap.Handle;
+
+ /// The texture unit for the diffuse map.
+ public const int DIFFUSE_UNIT = 0;
+
+ /// The texture unit for the specular map.
+ public const int SPECULAR_UNIT = 1;
+
+ /// Gets or sets the specular color of the material.
+ public Vector3 Specular { get; set; }
+
+ /// Gets or sets the shininess of the material.
+ public float Shininess { get; set; }
+
+ /// Gets or sets the ambient color of the material.
+ public Vector3 AmbientColor { get; set; }
+
+ /// Gets or sets the diffuse color of the material.
+ public Vector3 DiffuseColor { get; set; }
+
+ /// Gets or sets the specular color of the material.
+ public Vector3 SpecularColor { get; set; }
+
+ /// Gets or sets the specular coefficient of the material.
+ public float SpecularCoefficient { get; set; }
+
+ /// Gets or sets the transparency of the material.
+ public float Transparency { get; set; }
+
+ /// Gets or sets the illumination model of the material.
+ public int IlluminationModel { get; set; }
+
+ /// Gets or sets the path to the ambient texture map.
+ public string AmbientTextureMap { get; set; }
+
+ /// Gets or sets the path to the specular highlight texture map.
+ public string SpecularHighlightTextureMap { get; set; }
+
+ /// Gets or sets the path to the bump map.
+ public string BumpMap { get; set; }
+
+ /// Gets or sets the path to the displacement map.
+ public string DisplacementMap { get; set; }
+
+ /// Gets or sets the path to the stencil decal map.
+ public string StencilDecalMap { get; set; }
+
+ /// Gets or sets the path to the alpha texture map.
+ public string AlphaTextureMap { get; set; }
+
+ ///
+ /// Sets the uniform values for the material in the specified shader.
+ ///
+ /// The shader to set the uniform values in.
+ public void SetUniformValues(Shader shader)
+ {
+ // TODO: Get all shader uniforms and set their values automatically
+
+ DiffuseMap.Use(TextureUnit.Texture0);
+ shader.SetInt("material.diffuse", DIFFUSE_UNIT);
+
+ if (UseSpecularMap)
+ {
+ SpecularMap.Use(TextureUnit.Texture1);
+ shader.SetInt("material.specular", SPECULAR_UNIT);
+ shader.SetVector3("material.specular", Specular);
+ shader.SetFloat("material.shininess", Shininess);
+ }
+ else
+ {
+ shader.SetInt("material.specular", 0);
+ shader.SetVector3("material.specular", Vector3.Zero);
+ shader.SetFloat("material.shininess", 0);
+ }
+ }
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs
new file mode 100644
index 0000000..1279b1d
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs
@@ -0,0 +1,60 @@
+using Silk.NET.OpenGL;
+using System.Runtime.InteropServices;
+
+namespace SharpEngine.Core.Components.Properties.Meshes.MeshData
+{
+ ///
+ /// Represents a buffer object in OpenGL that manages memory allocation and data transfer for a specific data type.
+ ///
+ /// Specifies the type of data stored in the buffer, which must be an unmanaged type for proper memory handling.
+ public class BufferObject : IDisposable where TDataType : unmanaged
+ {
+ private readonly uint _handle;
+ private readonly BufferTargetARB _bufferType;
+ private readonly GL _gl;
+ private bool disposedValue;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The OpenGL context where the memory should be allocated.
+ /// The data buffer.
+ /// The type of buffer target.
+ public BufferObject(GL gl, Span data, BufferTargetARB bufferType)
+ {
+ _gl = gl;
+ _bufferType = bufferType;
+
+ _handle = _gl.GenBuffer();
+ Bind();
+
+ var size = (nuint)(data.Length * Marshal.SizeOf());
+ _gl.BufferData(bufferType, size, data, BufferUsageARB.StaticDraw);
+ }
+
+ ///
+ /// Binds the buffer object.
+ ///
+ public void Bind() => _gl.BindBuffer(_bufferType, _handle);
+
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposedValue)
+ return;
+
+ if (disposing)
+ _gl.DeleteBuffer(_handle);
+
+ disposedValue = true;
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs
new file mode 100644
index 0000000..e6f66fc
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs
@@ -0,0 +1,192 @@
+using ObjLoader.Loader.Data.Elements;
+using SharpEngine.Core.Attributes;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+using Silk.NET.OpenGL;
+using Tutorial;
+
+using Texture2 = SharpEngine.Core.Components.Properties.Textures.Texture;
+
+namespace SharpEngine.Core.Entities.Properties.Meshes;
+
+///
+/// Represents a game object mesh, which is a collection of vertices, normals, texture coordinates, and indices
+/// used to define the shape and appearance of a 3D object.
+///
+public class Mesh : IDisposable
+{
+ private bool disposedValue;
+
+ ///
+ /// Gets or sets an identifying name for the mesh.
+ ///
+ public string Name { get; set; } = "New Mesh";
+
+ ///
+ /// Gets or sets the mesh vertices as an array of floats.
+ ///
+ [Inspector(DisplayInInspector = false)]
+ public float[] Vertices { get; set; } = [];
+
+ ///
+ /// Gets or sets the mesh vertices as a list of objects.
+ ///
+ public List Vertices2 { get; set; } = [];
+
+ ///
+ /// Gets or sets the mesh normals as an array of floats.
+ ///
+ [Inspector(DisplayInInspector = false)]
+ public float[] Normals { get; set; } = [];
+
+ ///
+ /// Gets or sets the mesh normals as a list of objects.
+ ///
+ public List Normals2 { get; set; } = [];
+
+ ///
+ /// Gets or sets the mesh texture UV coordinates as an array of floats.
+ ///
+ [Inspector(DisplayInInspector = false)]
+ public float[] TextureCoordinates { get; set; } = [];
+
+ ///
+ /// Gets or sets the mesh texture UV coordinates as a list of objects.
+ ///
+ public List TextureCoordinates2 { get; set; } = [];
+
+ ///
+ /// Gets or sets the indices of the mesh as an array of unsigned integers.
+ ///
+ [Inspector(DisplayInInspector = false)]
+ public uint[] Indices { get; set; } = [];
+
+ ///
+ /// Gets or sets the groups of faces in the mesh.
+ ///
+ public List Groups { get; set; } = [];
+
+ ///
+ /// Gets or sets the materials used by the mesh.
+ ///
+ public List Materials { get; set; } = [];
+
+ ///
+ /// Gets or sets the textures used by the mesh.
+ ///
+ public IReadOnlyList Textures { get; set; }
+
+ ///
+ /// Gets or sets the legacy textures used by the mesh.
+ ///
+ public IReadOnlyList Textures_Old { get; set; }
+
+ ///
+ /// Gets or sets the Vertex Array Object (VAO) for the mesh.
+ ///
+ public VertexArrayObject VAO { get; set; }
+
+ ///
+ /// Gets or sets the Vertex Buffer Object (VBO) for the mesh.
+ ///
+ public BufferObject VBO { get; set; }
+
+ ///
+ /// Gets or sets the Element Buffer Object (EBO) for the mesh.
+ ///
+ public BufferObject EBO { get; set; }
+
+ ///
+ /// Gets the OpenGL context associated with the mesh.
+ ///
+ public GL GL { get; }
+
+ ///
+ /// Initializes a new instance of the class with the specified OpenGL context, vertices, indices, and textures.
+ ///
+ /// The OpenGL context.
+ /// The vertices of the mesh.
+ /// The indices of the mesh.
+ /// The textures of the mesh.
+ public Mesh(GL gl, float[] vertices, uint[] indices, List textures)
+ {
+ GL = gl;
+ Vertices = vertices;
+ Indices = indices;
+ Textures = textures;
+ SetupMesh();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified OpenGL context, vertices, indices, and legacy textures.
+ ///
+ /// The OpenGL context.
+ /// The vertices of the mesh.
+ /// The indices of the mesh.
+ /// The legacy textures of the mesh.
+ public Mesh(GL gl, float[] vertices, uint[] indices, List textures)
+ {
+ GL = gl;
+ Vertices = vertices;
+ Indices = indices;
+ Textures_Old = textures;
+ SetupMesh();
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified OpenGL context.
+ ///
+ /// The OpenGL context.
+ public Mesh(GL gl)
+ {
+ GL = gl;
+ SetupMesh();
+ }
+
+ ///
+ /// Allocates the required memory for the mesh and sets up the Vertex Array Object (VAO), Vertex Buffer Object (VBO), and Element Buffer Object (EBO).
+ ///
+ public void SetupMesh()
+ {
+ EBO = new BufferObject(GL, Indices, BufferTargetARB.ElementArrayBuffer);
+ VBO = new BufferObject(GL, Vertices, BufferTargetARB.ArrayBuffer);
+ VAO = new VertexArrayObject(GL, VBO, EBO);
+
+ VAO.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 8, 0);
+ VAO.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, 8, 3);
+ VAO.VertexAttributePointer(2, 2, VertexAttribPointerType.Float, 8, 6);
+
+ // VAO.VertexAttributePointer(0, VertexData.VerticesSize, VertexAttribPointerType.Float, VertexData.Stride, VertexData.VerticesOffset);
+ // VAO.VertexAttributePointer(1, VertexData.NormalsSize, VertexAttribPointerType.Float, VertexData.Stride, VertexData.NormalsOffset);
+ // VAO.VertexAttributePointer(2, VertexData.TexCoordsSize, VertexAttribPointerType.Float, VertexData.Stride, VertexData.TexCoordsOffset);
+ }
+
+ ///
+ /// Binds the mesh Vertex Array Object (VAO) to the current OpenGL context.
+ ///
+ public void Bind()
+ => VAO.Bind();
+
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposedValue)
+ return;
+
+ if (disposing)
+ {
+ VAO.Dispose();
+ VBO.Dispose();
+ EBO.Dispose();
+ }
+
+ disposedValue = true;
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs
new file mode 100644
index 0000000..e4dfdb5
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs
@@ -0,0 +1,27 @@
+namespace ObjLoader.Loader.Data.Elements
+{
+ public class Face
+ {
+ public readonly List _vertices = [];
+
+ public void AddVertex(FaceVertex vertex) => _vertices.Add(vertex);
+
+ public FaceVertex this[int i] => _vertices[i];
+
+ public int Count => _vertices.Count;
+ }
+
+ public struct FaceVertex
+ {
+ public FaceVertex(int vertexIndex, int textureIndex, int normalIndex) : this()
+ {
+ VertexIndex = vertexIndex;
+ TextureIndex = textureIndex;
+ NormalIndex = normalIndex;
+ }
+
+ public int VertexIndex { get; set; }
+ public int TextureIndex { get; set; }
+ public int NormalIndex { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs
new file mode 100644
index 0000000..1cd52c9
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs
@@ -0,0 +1,22 @@
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties;
+
+namespace ObjLoader.Loader.Data.Elements
+{
+ public class Group : IFaceGroup
+ {
+ private readonly List _faces = [];
+
+ public Group(string name)
+ {
+ Name = name;
+ }
+
+ public string Name { get; private set; }
+ public Material Material { get; set; }
+
+ public IList Faces => _faces;
+
+ public void AddFace(Face face) => _faces.Add(face);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs
new file mode 100644
index 0000000..5e40c14
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs
@@ -0,0 +1,16 @@
+namespace SharpEngine.Core.Components.Properties.Meshes.MeshData
+{
+ public struct Normal
+ {
+ public Normal(float x, float y, float z) : this()
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ public float X { get; private set; }
+ public float Y { get; private set; }
+ public float Z { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs
new file mode 100644
index 0000000..5bd29ce
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs
@@ -0,0 +1,14 @@
+namespace SharpEngine.Core.Components.Properties.Meshes.MeshData
+{
+ public struct TextureCoordinate
+ {
+ public TextureCoordinate(float x, float y) : this()
+ {
+ X = x;
+ Y = y;
+ }
+
+ public float X { get; private set; }
+ public float Y { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs
new file mode 100644
index 0000000..5eb1fda
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Numerics;
+
+namespace SharpEngine.Core.Components.Properties.Meshes.MeshData
+{
+ public struct Vertex
+ {
+ public Vector3 Position;
+ public Vector3 Normal;
+ public Vector3 Tangent;
+ public Vector2 TexCoords;
+ public Vector3 Bitangent;
+
+ // TODO: #65 Skeletal mesh
+ public const int MAX_BONE_INFLUENCE = 4;
+ public int[] BoneIds;
+ public float[] Weights;
+ }
+}
diff --git a/Core/Entities/Properties/Meshes/MeshExtensions.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs
similarity index 100%
rename from Core/Entities/Properties/Meshes/MeshExtensions.cs
rename to Engine/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs
new file mode 100644
index 0000000..513cf4e
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs
@@ -0,0 +1,104 @@
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+using Silk.NET.Assimp;
+using Silk.NET.OpenGL;
+using System.Numerics;
+using System.Linq;
+using Mesh = SharpEngine.Core.Entities.Properties.Meshes.Mesh;
+using Texture = SharpEngine.Core.Components.Properties.Textures.Texture;
+
+namespace Tutorial
+{
+ public class Model : IDisposable
+ {
+ public readonly string Path;
+ public List Meshes { get; set; } = new List();
+
+ private readonly GL _gl;
+
+ public Model(GL gl, string path)
+ {
+ _gl = gl;
+ Path = path;
+ }
+
+ public Model(GL gl, string path, List meshes)
+ {
+ _gl = gl;
+ Path = path;
+ Meshes = meshes;
+
+ foreach (var mesh in Meshes)
+ ProcessMesh(mesh);
+ }
+
+ public void Dispose()
+ {
+ foreach (var mesh in Meshes)
+ {
+ mesh.Dispose();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ public Mesh ProcessMesh(Mesh mesh)
+ {
+ // data to fill
+ for (int i = 0; i < mesh.Vertices2.Count; i++)
+ {
+ var vertex = mesh.Vertices2[i];
+ vertex.BoneIds = new int[Vertex.MAX_BONE_INFLUENCE];
+ vertex.Weights = new float[Vertex.MAX_BONE_INFLUENCE];
+ }
+
+ List indices = new List();
+ foreach (var group in mesh.Groups)
+ {
+ foreach (var face in group.Faces)
+ {
+ indices.AddRange(face._vertices.Select(v => (uint)v.VertexIndex));
+ }
+ }
+
+ List textures = new List();
+ foreach (var material in mesh.Materials)
+ {
+ if (material.DiffuseMap != null)
+ textures.Add(material.DiffuseMap);
+
+ if (material.UseSpecularMap && material.SpecularMap != null)
+ textures.Add(material.SpecularMap);
+ }
+
+ // return a mesh object updated with the extracted mesh data
+ mesh.Vertices = BuildVertices(mesh.Vertices2);
+ mesh.Indices = BuildIndices(indices);
+ mesh.Textures = textures;
+
+ return mesh;
+ }
+
+ private float[] BuildVertices(List vertexCollection)
+ {
+ var vertices = new List();
+
+ foreach (var vertex in vertexCollection)
+ {
+ vertices.Add(vertex.Position.X);
+ vertices.Add(vertex.Position.Y);
+ vertices.Add(vertex.Position.Z);
+
+ // vertices.Add(vertex.Normal.X);
+ // vertices.Add(vertex.Normal.Y);
+
+ vertices.Add(vertex.TexCoords.X);
+ vertices.Add(vertex.TexCoords.Y);
+ }
+
+ return vertices.ToArray();
+ }
+
+ private uint[] BuildIndices(List indices)
+ => indices.ToArray();
+ }
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/Model_Old.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/Model_Old.cs
new file mode 100644
index 0000000..0f477aa
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/Model_Old.cs
@@ -0,0 +1,214 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using Silk.NET.Assimp;
+using Silk.NET.OpenGL;
+using System.Numerics;
+using AssimpMesh = Silk.NET.Assimp.Mesh;
+using Mesh = SharpEngine.Core.Entities.Properties.Meshes.Mesh;
+using Texture = SharpEngine.Core.Components.Properties.Textures.Texture;
+
+namespace Tutorial
+{
+ [Obsolete("This should be removed and the ObjLoader should be used instead.")]
+ public class Model_Old : IDisposable
+ {
+ public Model_Old(GL gl, string path)
+ {
+ _assimp = Assimp.GetApi();
+ _gl = gl;
+
+ if (!string.IsNullOrWhiteSpace(path))
+ LoadModel(path);
+ }
+
+ public Model_Old(GL gl, Mesh mesh)
+ {
+ var result = new Mesh(gl, mesh.GetVertices(), mesh.Indices, mesh.Textures.ToList());
+ Meshes.Add(result);
+ }
+
+ private readonly GL _gl;
+ private Assimp _assimp;
+ private List _texturesLoaded = [];
+ public string Directory { get; protected set; } = string.Empty;
+ public List Meshes { get; protected set; } = [];
+
+ private unsafe void LoadModel(string path)
+ {
+ var scene = _assimp.ImportFile(path, (uint)PostProcessSteps.Triangulate);
+
+ if (scene == null || scene->MFlags == Assimp.SceneFlagsIncomplete || scene->MRootNode == null)
+ {
+ string error = _assimp.GetErrorStringS();
+ throw new Exception(error);
+ }
+
+ Directory = path;
+
+ ProcessNode(scene->MRootNode, scene);
+ }
+
+ private unsafe void ProcessNode(Node* node, Scene* scene)
+ {
+ for (int i = 0; i < node->MNumMeshes; i++)
+ {
+ var mesh = scene->MMeshes[node->MMeshes[i]];
+ Meshes.Add(ProcessMesh(mesh, scene));
+
+ }
+
+ for (int i = 0; i < node->MNumChildren; i++)
+ {
+ ProcessNode(node->MChildren[i], scene);
+ }
+ }
+
+ private unsafe Mesh ProcessMesh(AssimpMesh* mesh, Scene* scene)
+ {
+ // data to fill
+ List vertices = [];
+ List indices = [];
+ List textures = [];
+
+ // walk through each of the mesh's vertices
+ for (uint i = 0; i < mesh->MNumVertices; i++)
+ {
+ var vertex = new Vertex
+ {
+ BoneIds = new int[Vertex.MAX_BONE_INFLUENCE],
+ Weights = new float[Vertex.MAX_BONE_INFLUENCE],
+
+ Position = mesh->MVertices[i]
+ };
+
+ // normals
+ if (mesh->MNormals != null)
+ vertex.Normal = mesh->MNormals[i];
+ // tangent
+ if (mesh->MTangents != null)
+ vertex.Tangent = mesh->MTangents[i];
+ // bitangent
+ if (mesh->MBitangents != null)
+ vertex.Bitangent = mesh->MBitangents[i];
+
+ // texture coordinates
+ if (mesh->MTextureCoords[0] != null) // does the mesh contain texture coordinates?
+ {
+ // a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
+ // use models where a vertex can have multiple texture coordinates so we always take the first set (0).
+ var texcoord3 = mesh->MTextureCoords[0][i];
+ vertex.TexCoords = new Vector2(texcoord3.X, texcoord3.Y);
+ }
+
+ vertices.Add(vertex);
+ }
+
+ // now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
+ for (uint i = 0; i < mesh->MNumFaces; i++)
+ {
+ var face = mesh->MFaces[i];
+ // retrieve all indices of the face and store them in the indices vector
+ for (uint j = 0; j < face.MNumIndices; j++)
+ indices.Add(face.MIndices[j]);
+ }
+
+ // process materials
+ var material = scene->MMaterials[mesh->MMaterialIndex];
+ // we assume a convention for sampler names in the shaders. Each diffuse texture should be named
+ // as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER.
+ // Same applies to other texture as the following list summarizes:
+ // diffuse: texture_diffuseN
+ // specular: texture_specularN
+ // normal: texture_normalN
+
+ // 1. diffuse maps
+ var diffuseMaps = LoadMaterialTextures(material, TextureType.Diffuse);
+ if (diffuseMaps.Any())
+ textures.AddRange(diffuseMaps);
+
+ // 2. specular maps
+ var specularMaps = LoadMaterialTextures(material, TextureType.Specular);
+ if (specularMaps.Any())
+ textures.AddRange(specularMaps);
+
+ // 3. normal maps
+ var normalMaps = LoadMaterialTextures(material, TextureType.Height);
+ if (normalMaps.Any())
+ textures.AddRange(normalMaps);
+
+ // 4. height maps
+ var heightMaps = LoadMaterialTextures(material, TextureType.Ambient);
+ if (heightMaps.Any())
+ textures.AddRange(heightMaps);
+
+ // return a mesh object created from the extracted mesh data
+ var result = new Mesh(_gl, BuildVertices(vertices), BuildIndices(indices), textures);
+ return result;
+ }
+
+ private unsafe List LoadMaterialTextures(Material* mat, TextureType type)
+ {
+ uint textureCount = _assimp.GetMaterialTextureCount(mat, type);
+ List textures = [];
+ for (uint i = 0; i < textureCount; i++)
+ {
+ AssimpString path;
+ _assimp.GetMaterialTexture(mat, type, i, &path, null, null, null, null, null, null);
+ bool skip = false;
+ for (int j = 0; j < _texturesLoaded.Count; j++)
+ {
+ if (_texturesLoaded[j].Path == path)
+ {
+ textures.Add(_texturesLoaded[j]);
+ skip = true;
+ break;
+ }
+ }
+ if (!skip)
+ {
+ var texture = new Texture(_gl, Directory, type);
+ textures.Add(texture);
+ _texturesLoaded.Add(texture);
+ }
+ }
+ return textures;
+ }
+
+ private static float[] BuildVertices(List vertexCollection)
+ {
+ var vertices = new List();
+
+ foreach (var vertex in vertexCollection)
+ {
+ vertices.Add(vertex.Position.X);
+ vertices.Add(vertex.Position.Y);
+ vertices.Add(vertex.Position.Z);
+
+ vertices.Add(vertex.Normal.X);
+ vertices.Add(vertex.Normal.Y);
+ vertices.Add(vertex.Normal.Z);
+
+ vertices.Add(vertex.TexCoords.X);
+ vertices.Add(vertex.TexCoords.Y);
+ }
+
+ return vertices.ToArray();
+ }
+
+ private static uint[] BuildIndices(List indices) => indices.ToArray();
+
+ public void Dispose()
+ {
+ foreach (var mesh in Meshes)
+ {
+ mesh.Dispose();
+ }
+
+ _texturesLoaded = [];
+ GC.SuppressFinalize(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs
new file mode 100644
index 0000000..e1e45de
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/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/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs
new file mode 100644
index 0000000..4ba4ad0
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs
@@ -0,0 +1,79 @@
+using Silk.NET.OpenGL;
+using System.Runtime.InteropServices;
+
+namespace SharpEngine.Core.Components.Properties.Meshes.MeshData
+{
+ ///
+ /// Represents a vertex array object for managing vertex and index data in OpenGL.
+ ///
+ /// Specifies the type of vertex data used in the vertex array.
+ /// Specifies the type of index data used for indexing vertices in the vertex array.
+ public class VertexArrayObject : IDisposable
+ where TVertexType : unmanaged
+ where TIndexType : unmanaged
+ {
+ private readonly uint _handle;
+ private readonly GL _gl;
+ private bool disposedValue;
+
+ ///
+ /// Initializes an instance of .
+ ///
+ /// Represents the OpenGL context used for generating and managing the vertex array.
+ /// Represents the buffer object containing vertex data that will be bound to the vertex array.
+ /// Represents the buffer object containing index data that will be bound to the vertex array.
+ public VertexArrayObject(GL gl, BufferObject vbo, BufferObject ebo)
+ {
+ _gl = gl;
+
+ _handle = _gl.GenVertexArray();
+ Bind();
+ vbo.Bind();
+ ebo.Bind();
+ }
+
+ ///
+ /// Configures the vertex attribute pointer for rendering.
+ ///
+ /// Specifies the index of the vertex attribute to be configured.
+ /// Indicates the number of components per vertex attribute.
+ /// Defines the data type of each component in the vertex attribute.
+ /// Determines the size of each vertex in bytes.
+ /// Sets the offset in bytes from the start of the vertex data.
+ public void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, uint vertexSize, int offSet)
+ {
+ var size = Marshal.SizeOf();
+ var stride = (uint)(vertexSize * size);
+ var pointer = (nint)offSet * size;
+
+ _gl.VertexAttribPointer(index, count, type, false, stride, pointer);
+ _gl.EnableVertexAttribArray(index);
+ }
+
+ ///
+ /// Binds the vertex array object (VAO) to the current OpenGL context.
+ ///
+ public void Bind()
+ => _gl.BindVertexArray(_handle);
+
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposedValue)
+ return;
+
+ if (disposing)
+ _gl.DeleteVertexArray(_handle);
+
+ disposedValue = true;
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Core/Entities/Properties/VertexData.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexData.cs
similarity index 78%
rename from Core/Entities/Properties/VertexData.cs
rename to Engine/SharpEngine.Core.Components/Properties/Meshes/VertexData.cs
index c25e5d4..328d38d 100644
--- a/Core/Entities/Properties/VertexData.cs
+++ b/Engine/SharpEngine.Core.Components/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
+public class 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.
@@ -25,9 +25,4 @@ public struct VertexData
/// The byte offset to the texture coordinates in the vertex data object.
public const int TexCoordsOffset = (VerticesSize + NormalsSize) * sizeof(float);
-
- ///
- /// Initializes a new instance of .
- ///
- public VertexData() { }
}
diff --git a/Core/Entities/Properties/Quaternion.cs b/Engine/SharpEngine.Core.Components/Properties/Quaternion.cs
similarity index 100%
rename from Core/Entities/Properties/Quaternion.cs
rename to Engine/SharpEngine.Core.Components/Properties/Quaternion.cs
diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs b/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
new file mode 100644
index 0000000..13aea94
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
@@ -0,0 +1,83 @@
+using SharpEngine.Shared;
+using Silk.NET.OpenGL;
+using System.Diagnostics.CodeAnalysis;
+
+namespace SharpEngine.Core.Shaders;
+
+///
+/// Represents a shader program.
+///
+public partial class Shader : IDisposable
+{
+ /// Gets the handle to the shader program.
+ public uint Handle { get; private set; }
+
+ /// Gets or sets the identifying name of the shader.
+ public string Name { get; set; }
+
+ /// Gets or sets the path to the vertex shader file.
+ public string VertPath { get; set; }
+
+ /// Gets or sets the path to the fragment shader file.
+ public string FragPath { get; set; }
+
+ private Dictionary _uniformLocations = [];
+ private bool disposedValue;
+ private readonly GL _gl;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ ///
+ /// Shaders are written in GLSL, which is a language very similar to C in its semantics.
+ /// The GLSL source is compiled *at runtime*, so it can optimize itself for the graphics card it's currently being used on.
+ ///
+ /// The OpenGL instance where this shader is used.
+ /// The vertex shader full path.
+ /// The fragment shader full path.
+ /// The identifier name of the shader.
+ public Shader(GL gl, string vertPath, string fragPath, string name)
+ {
+ Name = name;
+ _gl = gl;
+
+ // There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
+ // The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
+ // The vertex shader won't be too important here, but they'll be more important later.
+ // 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"))
+ Debug.Log.Warning("Vertex shaders should have the file extension '.vert' for easier manageability.");
+
+ if (!fragPath.EndsWith(".frag"))
+ Debug.Log.Warning("Fragment shaders should have the file extension '.frag' for easier manageability.");
+
+ VertPath = vertPath;
+ FragPath = fragPath;
+ }
+
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposedValue)
+ return;
+
+ if (disposing)
+ {
+ _gl.DeleteProgram(Handle);
+ Handle = 0;
+ _uniformLocations.Clear();
+ }
+
+ disposedValue = true;
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Core/Shaders/ShaderAttributes.cs b/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs
similarity index 83%
rename from Core/Shaders/ShaderAttributes.cs
rename to Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs
index f099087..29c14ac 100644
--- a/Core/Shaders/ShaderAttributes.cs
+++ b/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs
@@ -16,5 +16,9 @@ public static class ShaderAttributes
public const int AttributeLocationNotFound = -1;
/// Represents the model matrix attribute.
- public const string Model = "model";
+ public const string Model = "uModel";
+
+ public const string View = "uView";
+
+ public const string Projection = "uProjection";
}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs b/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
new file mode 100644
index 0000000..830a2aa
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
@@ -0,0 +1,261 @@
+using SharpEngine.Shared;
+using Silk.NET.OpenGL;
+using System.Numerics;
+using System.Text.RegularExpressions;
+
+namespace SharpEngine.Core.Shaders;
+
+///
+/// Represents a shader program in the engine.
+///
+public partial class Shader
+{
+ ///
+ /// Initializes and compiles vertex and fragment shaders into a shader program.
+ ///
+ /// The current instance after initialization.
+ public Shader Initialize()
+ {
+ // Load and compile shader
+ if (!LoadShader(ShaderType.VertexShader, VertPath, out uint vertexShader))
+ {
+ Debug.Log.Error("Unable to load vertex shader.");
+ return this;
+ }
+
+ if (!LoadShader(ShaderType.FragmentShader, FragPath, out uint fragmentShader))
+ {
+ Debug.Log.Error("Unable to load fragment shader.");
+ return this;
+ }
+
+ // These two shaders must then be merged into a shader program, which can then be used by OpenGL.
+ // To do this, create a program...
+
+ Handle = _gl.CreateProgram();
+
+ // Attach both shaders...
+ _gl.AttachShader(Handle, vertexShader);
+ _gl.AttachShader(Handle, fragmentShader);
+
+ // And then link them together.
+ bool shaderLinked = LinkProgram(Handle);
+
+ // When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
+ // Detach them, and then delete them.
+ _gl.DetachShader(Handle, vertexShader);
+ _gl.DetachShader(Handle, fragmentShader);
+ _gl.DeleteShader(fragmentShader);
+ _gl.DeleteShader(vertexShader);
+
+ if (!shaderLinked)
+ {
+ Debug.Log.Information("Unable to link shader program.");
+ return this;
+ }
+
+ // 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.
+ SetUniformLocations();
+
+ return this;
+ }
+
+ private bool LoadShader(ShaderType shaderType, string shaderPath, out uint shader)
+ {
+ if (!File.Exists(shaderPath))
+ {
+ Debug.Log.Information("Shader file not found: {Path}", shaderPath);
+
+ shader = 0;
+ return false;
+ }
+
+ string shaderSource = File.ReadAllText(shaderPath);
+ shaderSource = ProcessIncludes(shaderSource, Path.GetDirectoryName(shaderPath)!);
+
+ // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
+ shader = _gl.CreateShader(shaderType);
+ _gl.ShaderSource(shader, shaderSource);
+
+ if (!CompileShader(shader))
+ {
+ Debug.Log.Information("Unable to load {Type} shader from '{Path}'.", shaderType, shaderPath);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void SetUniformLocations()
+ {
+ // First, we have to get the number of active uniforms in the shader.
+ _gl.GetProgram(Handle, GLEnum.ActiveUniforms, out var numberOfUniforms);
+
+ Dictionary uniformLocations = [];
+
+ // Loop over all the uniforms,
+ for (uint i = 0; i < numberOfUniforms; i++)
+ {
+ // get the name of this uniform,
+ var key = _gl.GetActiveUniform(Handle, i, out _, out _);
+
+ // get the location,
+ var location = _gl.GetUniformLocation(Handle, key);
+
+ // and then add it to the dictionary.
+ uniformLocations.Add(key, location);
+ }
+
+ _uniformLocations = uniformLocations;
+ }
+
+ private static string ProcessIncludes(string shaderCode, string directory)
+ {
+ const string includePattern = @"#include\s+""(.+?)""";
+ return Regex.Replace(shaderCode, includePattern, match =>
+ {
+ string includePath = Path.Combine(directory, match.Groups[1].Value);
+ string includeCode = File.ReadAllText(includePath);
+ return ProcessIncludes(includeCode, Path.GetDirectoryName(includePath)!);
+ }, RegexOptions.NonBacktracking);
+ }
+
+ private bool CompileShader(uint shader)
+ {
+ // Try to compile the shader
+ _gl.CompileShader(shader);
+
+ // Check for compilation errors
+ _gl.GetShader(shader, GLEnum.CompileStatus, out var statusCode);
+ if (statusCode != (int)GLEnum.True)
+ {
+ // We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
+ var infoLog = _gl.GetShaderInfoLog(shader);
+ Debug.Log.Error("Error occurred whilst compiling Shader({Shader}).\n\n{Log}", shader, infoLog);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool LinkProgram(uint program)
+ {
+ _gl.LinkProgram(program);
+ _gl.GetProgram(program, GLEnum.LinkStatus, out var statusCode);
+
+ if (statusCode != (int)GLEnum.True)
+ {
+ string infoLog = _gl.GetProgramInfoLog(program);
+ Debug.Log.Error("Error occurred whilst linking Program({Program}): {Info}", program, infoLog);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Enables the shader program.
+ ///
+ public void Use()
+ => _gl.UseProgram(Handle);
+
+ ///
+ /// Checks if the shader attribute exists within the current shader.
+ ///
+ /// The name of the attribute that's being looked for.
+ /// Outputs the location of the attribute in the shader if found; otherwise -1.
+ /// If the attribute exists, ; otherwise, .
+ public bool TryGetAttribLocation(string attribName, out int location)
+ {
+ location = _gl.GetAttribLocation(Handle, attribName);
+ if (location == ShaderAttributes.AttributeLocationNotFound)
+ {
+ Debug.Log.Warning("Attribute '{Attribute}' not found in shader program.", attribName);
+ return false;
+ }
+
+ return true;
+ }
+
+ // Uniform setters
+ // Uniforms are variables that can be set by user code, instead of reading them from the VBO.
+ // You use VBOs for vertex-related data, and uniforms for almost everything else.
+
+ // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
+ // 1. Bind the program you want to set the uniform on
+ // 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 uniformName, T data, Action setter)
+ {
+ if (!_uniformLocations.TryGetValue(uniformName, out int location))
+ {
+ Debug.Log.Information("Uniform '{UniformName}' not found in shader '{ShaderName}'.", uniformName, Name);
+ return false;
+ }
+
+ // TODO: #95 The _gl.UseProgram should be not be called here. Rather the renderer should call it once before rendering.
+ _gl.UseProgram(Handle);
+ setter(location, data);
+
+ return true;
+ }
+
+ ///
+ /// Set a uniform int on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetInt(string name, int data)
+ => TrySetUniform(name, data, _gl.Uniform1);
+
+ ///
+ /// Set a uniform float on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetFloat(string name, float data)
+ => TrySetUniform(name, data, _gl.Uniform1);
+
+ ///
+ /// Set a uniform Matrix4 on this shader
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ /// Determines whether or not the matrix should be transposed. Defaults to .
+ ///
+ ///
+ /// The matrix is transposed before being sent to the shader unless is set to .
+ ///
+ ///
+ public bool SetMatrix4(string name, Matrix4x4 data, bool transpose = true)
+ => TrySetUniform(name, data, (uniform, d) => _gl.UniformMatrix4(uniform, transpose, d.ToSpan()));
+
+ ///
+ /// Set a uniform Vector2 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector2(string name, Vector2 data)
+ => TrySetUniform(name, data, _gl.Uniform2);
+
+ ///
+ /// Set a uniform Vector3 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector3(string name, Vector3 data)
+ => TrySetUniform(name, data, _gl.Uniform3);
+
+ ///
+ /// Set a uniform Vector3 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector4(string name, Vector4 data)
+ => TrySetUniform(name, data, _gl.Uniform4);
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs b/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs
new file mode 100644
index 0000000..d271f1e
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs
@@ -0,0 +1,31 @@
+using Silk.NET.Assimp;
+using Silk.NET.OpenGL;
+
+namespace SharpEngine.Core.Components.Properties.Textures;
+
+///
+/// Represents a texture program.
+///
+public partial class Texture : IDisposable
+{
+ /// The OpenGL handle for the texture.
+ public readonly uint Handle;
+ public readonly TextureType Type;
+ public readonly string Path;
+
+ private readonly GL _gl;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public Texture(GL gl, string path, TextureType type = TextureType.Diffuse)
+ {
+ _gl = gl;
+ Handle = _gl.GenTexture();
+
+ Path = path;
+ Type = type;
+
+ Initialize();
+ }
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs b/Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs
new file mode 100644
index 0000000..a060f8c
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs
@@ -0,0 +1,63 @@
+using StbImageSharp;
+using Silk.NET.OpenGL;
+
+namespace SharpEngine.Core.Components.Properties.Textures;
+
+public partial class Texture
+{
+ public void Initialize()
+ {
+ // Bind the handle
+ Use();
+
+ // OpenGL has its texture origin in the lower left corner instead of the top left corner,
+ // so we tell StbImageSharp to flip the image when loading.
+ StbImage.stbi_set_flip_vertically_on_load(1);
+
+ // Here we open a stream to the file and pass it to StbImageSharp to load.
+ using (Stream stream = File.OpenRead(Path))
+ {
+ var image = ImageResult.FromStream(stream, ColorComponents.RedGreenBlueAlpha);
+
+ // Generate a texture
+ _gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)image.Width, (uint)image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, image.Data.AsSpan());
+ }
+
+ // Set texture parameters
+ SetParameters();
+
+ // Generate mipmaps
+ _gl.GenerateMipmap(GLEnum.Texture2D);
+ }
+
+ public void SetParameters()
+ {
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GLEnum.ClampToEdge);
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GLEnum.ClampToEdge);
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)GLEnum.LinearMipmapLinear);
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)GLEnum.Linear);
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0);
+ _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8);
+ _gl.GenerateMipmap(TextureTarget.Texture2D);
+ }
+
+ // Multiple textures can be bound, if your shader needs more than just one.
+ // If you want to do that, use GL.ActiveTexture to set which slot GL.BindTexture binds to.
+ // The OpenGL standard requires that there be at least 16, but there can be more depending on your graphics card.
+
+ ///
+ /// Activate the texture.
+ ///
+ /// The bound texture location.
+ public void Use(TextureUnit unit = TextureUnit.Texture0)
+ {
+ _gl.ActiveTexture(unit);
+ _gl.BindTexture(TextureTarget.Texture2D, Handle);
+ }
+
+ public void Dispose()
+ {
+ _gl.DeleteTexture(Handle);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Core/Entities/Properties/Transform.cs b/Engine/SharpEngine.Core.Components/Properties/Transform.cs
similarity index 58%
rename from Core/Entities/Properties/Transform.cs
rename to Engine/SharpEngine.Core.Components/Properties/Transform.cs
index 50b8bea..3f2f075 100644
--- a/Core/Entities/Properties/Transform.cs
+++ b/Engine/SharpEngine.Core.Components/Properties/Transform.cs
@@ -1,12 +1,22 @@
-using System.Numerics;
+using SharpEngine.Core.Numerics;
+using Matrix4x4 = System.Numerics.Matrix4x4;
namespace SharpEngine.Core.Entities.Properties;
///
/// Represents a game object transformation in 3D space.
///
-public class Transform
+public class Transform : ITransform
{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public Transform() { }
+ public Transform(Vector3 position)
+ {
+ Position = position;
+ }
+
///
/// Gets or sets the position of the game object.
///
@@ -25,7 +35,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/Engine/SharpEngine.Core.Components/Properties/Transform2D.cs
similarity index 59%
rename from Core/Entities/Properties/Transform2D.cs
rename to Engine/SharpEngine.Core.Components/Properties/Transform2D.cs
index cbcbbee..8a44d56 100644
--- a/Core/Entities/Properties/Transform2D.cs
+++ b/Engine/SharpEngine.Core.Components/Properties/Transform2D.cs
@@ -1,16 +1,17 @@
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
+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.
@@ -20,12 +21,14 @@ public class Transform2D
///
/// Gets or sets the rotation of the game object.
///
- public float Rotation { get; set; }
+ 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) *
- 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/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj b/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
new file mode 100644
index 0000000..bec75b6
--- /dev/null
+++ b/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+ $(GenerateNuGetPackages)
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Engine/SharpEngine.Core.Numerics/Float.cs b/Engine/SharpEngine.Core.Numerics/Float.cs
new file mode 100644
index 0000000..333f856
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/Float.cs
@@ -0,0 +1,9 @@
+namespace SharpEngine.Core.Numerics;
+
+public static class Float
+{
+ ///
+ /// Represents a constant value for tolerance set to 1e-6. Used for precision in calculations.
+ ///
+ public const float Tolerance = 1e-6f;
+}
diff --git a/Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs b/Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs
new file mode 100644
index 0000000..601d499
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs
@@ -0,0 +1,15 @@
+// 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)
+ => (((h1 << 5) + h1) ^ h2);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Numerics/IVector.cs b/Engine/SharpEngine.Core.Numerics/IVector.cs
new file mode 100644
index 0000000..a620e8d
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/IVector.cs
@@ -0,0 +1,15 @@
+namespace SharpEngine.Core.Numerics;
+
+///
+/// An abstraction of the different vector types.
+///
+public interface IVector
+{
+ ///
+ float X { get; set; }
+
+ ///
+ float Y { get; set; }
+
+ public const float Epsilon = 1e-5f;
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs b/Engine/SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs
new file mode 100644
index 0000000..0f6846a
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs
@@ -0,0 +1,14 @@
+// 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
+
+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/Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj b/Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj
new file mode 100644
index 0000000..3082977
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+ $(GenerateNuGetPackages)
+
+
+
+
diff --git a/Engine/SharpEngine.Core.Numerics/Vector2.cs b/Engine/SharpEngine.Core.Numerics/Vector2.cs
new file mode 100644
index 0000000..0e94630
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/Vector2.cs
@@ -0,0 +1,419 @@
+// 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.Globalization;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Matrix3x2 = System.Numerics.Matrix3x2;
+using Matrix4x4 = System.Numerics.Matrix4x4;
+using Quaternion = System.Numerics.Quaternion;
+using Vector = System.Numerics.Vector;
+
+namespace SharpEngine.Core.Numerics
+{
+ ///
+ /// A structure encapsulating two single precision floating point values and provides hardware accelerated methods.
+ ///
+ public partial struct Vector2 : 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(1.0f, 1.0f);
+
+ /// Returns the vector (1,0).
+ public static Vector2 UnitX => new(1.0f, 0.0f);
+
+ /// Returns the vector (0,1).
+ public static Vector2 UnitY => new(0.0f, 1.0f);
+
+ #endregion Public Static Properties
+
+ #region Public instance methods
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ /// The hash code.
+ public override readonly int GetHashCode()
+ => HashCodeHelper.CombineHashCodes(X.GetHashCode(), Y.GetHashCode());
+
+ ///
+ /// 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 not Vector2 vec2)
+ return false;
+
+ return Equals(vec2);
+ }
+
+ ///
+ /// Returns a String representing this Vector2 instance.
+ ///
+ /// The string representation.
+ public override readonly string ToString() => 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 readonly string ToString(string format) => 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 readonly string ToString(string? format, IFormatProvider? formatProvider)
+ {
+ var sb = new StringBuilder();
+ string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;
+ sb.Append('<');
+ sb.Append(X.ToString(format, formatProvider));
+ sb.Append(separator);
+ sb.Append(' ');
+ sb.Append(Y.ToString(format, formatProvider));
+ sb.Append('>');
+ return sb.ToString();
+ }
+
+ ///
+ /// Returns the length of the vector.
+ ///
+ /// The vector's length.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly 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 readonly 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)
+ {
+ var 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)
+ {
+ var 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) => 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) => 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) => 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, float right) => 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(float left, Vector2 right) => 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) => 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, float divisor) => left / divisor;
+
+ ///
+ /// Negates a given vector.
+ ///
+ /// The source vector.
+ /// The negated vector.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector2 Negate(Vector2 value) => -value;
+
+ #endregion Public operator methods
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs b/Engine/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs
new file mode 100644
index 0000000..093518f
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs
@@ -0,0 +1,272 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+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 : IVector
+ {
+ ///
+ public float X { get; set; }
+
+ ///
+ public float Y { get; set; }
+
+ #region Constructors
+
+ ///
+ /// Constructs a vector whose elements are all the single specified value.
+ ///
+ /// The element to fill the vector with.
+ [JitIntrinsic]
+ public Vector2(float value) : this(value, value) { }
+
+ ///
+ /// Constructs a vector with the given individual elements.
+ ///
+ /// The X component.
+ /// The Y component.
+ [JitIntrinsic]
+ public Vector2(float x, float 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 readonly void CopyTo(float[] 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 readonly void CopyTo(float[] array, int index)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ ArgumentNullException.ThrowIfNull(array);
+
+ if (index < 0 || index >= array.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ if ((array.Length - index) < 2)
+ throw new ArgumentException("The given index is outside the supported range.", 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 approximately equal to this instance; False otherwise.
+ [JitIntrinsic]
+ public readonly bool Equals(Vector2 other)
+ => MathF.Abs(X - other.X) < Float.Tolerance && MathF.Abs(Y - other.Y) < Float.Tolerance;
+
+ #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)
+ => (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)
+ {
+ var x = (value1.X < value2.X) ? value1.X : value2.X;
+ var y = (value1.Y < value2.Y) ? value1.Y : value2.Y;
+
+ return new Vector2(x, 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)
+ {
+ var x = (value1.X > value2.X) ? value1.X : value2.X;
+ var y = (value1.Y > value2.Y) ? value1.Y : value2.Y;
+
+ return new Vector2(x, 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)
+ => new(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)
+ => new(MathF.Sqrt(value.X), 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) => new(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) => new(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) => new(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 *(float left, Vector2 right) => 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, float right) => 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) => new(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) => 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) => 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) => !(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);
+
+ #endregion Public Static Operators
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Numerics/Vector3.cs b/Engine/SharpEngine.Core.Numerics/Vector3.cs
new file mode 100644
index 0000000..da6f282
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/Vector3.cs
@@ -0,0 +1,427 @@
+// 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.Globalization;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Matrix4x4 = System.Numerics.Matrix4x4;
+using Quaternion = System.Numerics.Quaternion;
+using Vector = System.Numerics.Vector;
+
+namespace SharpEngine.Core.Numerics
+{
+ ///
+ /// A structure encapsulating three single precision floating point values and provides hardware accelerated methods.
+ ///
+ public partial struct Vector3 : IEquatable, IFormattable
+ {
+ #region Public Static Properties
+
+ /// Returns the vector (0,0,0).
+ public static Vector3 Zero => new();
+
+ /// Returns the vector (1,1,1).
+ public static Vector3 One => new(1.0f, 1.0f, 1.0f);
+
+ /// Returns the vector (1,0,0).
+ public static Vector3 UnitX => new(1.0f, 0.0f, 0.0f);
+
+ /// Returns the vector (0,1,0).
+ public static Vector3 UnitY => new(0.0f, 1.0f, 0.0f);
+
+ /// Returns the vector (0,0,1).
+ public static Vector3 UnitZ => new(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 readonly int GetHashCode()
+ {
+ int hash = X.GetHashCode();
+ hash = HashCodeHelper.CombineHashCodes(hash, Y.GetHashCode());
+ hash = HashCodeHelper.CombineHashCodes(hash, 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 readonly bool Equals(object? obj)
+ {
+ if (obj is not Vector3 vec3)
+ return false;
+
+ return Equals(vec3);
+ }
+
+ ///
+ /// Returns a String representing this Vector3 instance.
+ ///
+ /// The string representation.
+ public override readonly string ToString() => 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 readonly string ToString(string format) => 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 readonly string ToString(string? format, IFormatProvider? formatProvider)
+ {
+ var sb = new StringBuilder();
+ string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator;
+ sb.Append('<');
+ sb.Append(X.ToString(format, formatProvider));
+ sb.Append(separator);
+ sb.Append(' ');
+ sb.Append(Y.ToString(format, formatProvider));
+ sb.Append(separator);
+ sb.Append(' ');
+ sb.Append(Z.ToString(format, formatProvider));
+ sb.Append('>');
+ return sb.ToString();
+ }
+
+ ///
+ /// Returns the length of the vector.
+ ///
+ /// The vector's length.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly float Length()
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ float ls = Vector3.Dot(this, this);
+ return System.MathF.Sqrt(ls);
+ }
+ else
+ {
+ float ls = (X * X) + (Y * Y) + (Z * Z);
+ return System.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 readonly 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)
+ {
+ var difference = value1 - value2;
+ float ls = Vector3.Dot(difference, difference);
+ return System.MathF.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 System.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(Vector3 value1, Vector3 value2)
+ {
+ if (Vector.IsHardwareAccelerated)
+ {
+ var difference = value1 - value2;
+ return Vector3.Dot(difference, difference);
+ }
+
+ 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)
+ => new((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 (Vector.IsHardwareAccelerated)
+ {
+ float dot = Vector3.Dot(vector, normal);
+ var 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)
+ {
+ var firstInfluence = value1 * (1f - amount);
+ var secondInfluence = value2 * amount;
+ return firstInfluence + secondInfluence;
+ }
+
+ 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)
+ => new((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)
+ => new((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) => 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) => 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) => 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, float right) => 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(float left, Vector3 right) => 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) => 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, float divisor) => left / divisor;
+
+ ///
+ /// Negates a given vector.
+ ///
+ /// The source vector.
+ /// The negated vector.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector3 Negate(Vector3 value) => -value;
+
+ #endregion Public operator methods
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs b/Engine/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs
new file mode 100644
index 0000000..e747bab
--- /dev/null
+++ b/Engine/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs
@@ -0,0 +1,308 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+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 : IVector
+ {
+ ///
+ public float X { get; set; }
+
+ ///
+ public float Y { get; set; }
+
+ ///
+ public float Z { get; set; }
+
+ #region Constructors
+
+ ///
+ /// Constructs a vector whose elements are all the single specified value.
+ ///
+ /// The element to fill the vector with.
+ [JitIntrinsic]
+ public Vector3(float 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(float x, float y, float 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 readonly void CopyTo(float[] 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 readonly void CopyTo(float[] array, int index)
+ {
+ // Match the JIT's exception type here. For perf, a NullReference is thrown instead of an ArgumentNull.
+ ArgumentNullException.ThrowIfNull(array);
+
+ 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 approximately equal to this instance; False otherwise.
+ [JitIntrinsic]
+ public readonly bool Equals(Vector3 other)
+ => MathF.Abs(X - other.X) < Float.Tolerance &&
+ MathF.Abs(Y - other.Y) < Float.Tolerance &&
+ MathF.Abs(Z - other.Z) < Float.Tolerance;
+
+ #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)
+ => (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)
+ => new((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)
+ => new((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)
+ => new(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)
+ => new(MathF.Sqrt(value.X),
+ MathF.Sqrt(value.Y),
+ 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)
+ => new(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)
+ => new(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)
+ => new(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, float right) => 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 *(float left, Vector3 right) => 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)
+ => new(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) => 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)
+ => Math.Abs(left.X - right.X) < Float.Tolerance &&
+ Math.Abs(left.Y - right.Y) < Float.Tolerance &&
+ Math.Abs(left.Z - right.Z) < Float.Tolerance;
+
+ ///
+ /// 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)
+ => !(left == right);
+
+ ///
+ /// 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);
+
+ #endregion Public Static Operators
+ }
+}
\ No newline at end of file
diff --git a/Core/Interfaces/IGame.cs b/Engine/SharpEngine.Core/Abstractions/IGame.cs
similarity index 100%
rename from Core/Interfaces/IGame.cs
rename to Engine/SharpEngine.Core/Abstractions/IGame.cs
diff --git a/Engine/SharpEngine.Core/Abstractions/IRenderable.cs b/Engine/SharpEngine.Core/Abstractions/IRenderable.cs
new file mode 100644
index 0000000..e248e60
--- /dev/null
+++ b/Engine/SharpEngine.Core/Abstractions/IRenderable.cs
@@ -0,0 +1,14 @@
+using SharpEngine.Core.Entities.Properties.Meshes;
+
+namespace SharpEngine.Core.Interfaces;
+
+// TODO: #55 Make an abstract class and move Render method here?
+
+///
+/// Contains definitions for rendering objects.
+///
+public interface IRenderable
+{
+
+
+}
diff --git a/Core/Interfaces/ISettings.cs b/Engine/SharpEngine.Core/Abstractions/ISettings.cs
similarity index 69%
rename from Core/Interfaces/ISettings.cs
rename to Engine/SharpEngine.Core/Abstractions/ISettings.cs
index e2ee475..23888d2 100644
--- a/Core/Interfaces/ISettings.cs
+++ b/Engine/SharpEngine.Core/Abstractions/ISettings.cs
@@ -1,4 +1,5 @@
using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
using Silk.NET.Windowing;
namespace SharpEngine.Core.Interfaces;
@@ -20,6 +21,12 @@ 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; }
}
///
@@ -33,4 +40,10 @@ public class DefaultSettings : ISettings
///
public RenderFlags RendererFlags { get; set; } = RenderFlags.All;
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Engine/SharpEngine.Core/Audio/Audio.cs b/Engine/SharpEngine.Core/Audio/Audio.cs
new file mode 100644
index 0000000..7d9d171
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/Audio.cs
@@ -0,0 +1,28 @@
+using System;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Handles Audio related operations.
+///
+public static class Audio
+{
+ ///
+ /// Plays an audio file if it is in the WAV format.
+ ///
+ /// Specifies the location of the audio file to be played.
+ /// Thrown when the file format is not supported, such as when it is not a WAV file.
+ public static void Play(string filePath)
+ {
+ switch (Path.GetExtension(filePath))
+ {
+ case ".wav":
+ new WavPlayer().Play(filePath);
+ break;
+
+ default:
+ throw new NotSupportedException("Unsupported file format.");
+ }
+ }
+}
diff --git a/Engine/SharpEngine.Core/Audio/AudioBuffer.cs b/Engine/SharpEngine.Core/Audio/AudioBuffer.cs
new file mode 100644
index 0000000..506f426
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioBuffer.cs
@@ -0,0 +1,43 @@
+using Silk.NET.OpenAL;
+using System;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents an audio buffer with a unique identifier.
+///
+public class AudioBuffer
+{
+ /// Gets the identifier to the audio buffer.
+ public uint BufferId => _buffer;
+
+ private readonly uint _buffer;
+ private readonly AL _al;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The OpenAL API context.
+ public AudioBuffer(AL al)
+ {
+ _al = al;
+ _buffer = _al.GenBuffer();
+ }
+
+ ///
+ /// Loads the given into the buffer.
+ ///
+ /// The data to be loaded into the buffer.
+ /// Details about the data.
+ public void LoadData(ReadOnlySpan data, WavData wavData)
+ {
+ unsafe
+ {
+ fixed (byte* pData = data)
+ {
+ _al.BufferData(_buffer, wavData.Format, pData, data.Length, wavData.SampleRate);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Audio/AudioDevice.cs b/Engine/SharpEngine.Core/Audio/AudioDevice.cs
new file mode 100644
index 0000000..aa0560d
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioDevice.cs
@@ -0,0 +1,46 @@
+using Silk.NET.OpenAL;
+using System;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Manages an audio device and its context for audio operations.
+///
+public unsafe class AudioDevice : IDisposable
+{
+ /// Gets the OpenAL context.
+ public AL Al { get; }
+
+ private readonly ALContext _alc;
+ private readonly Device* _device;
+ private readonly Context* _context;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// Thrown when unable to create the audio device.
+ public AudioDevice()
+ {
+ _alc = ALContext.GetApi();
+ Al = AL.GetApi();
+ _device = _alc.OpenDevice("");
+
+ if (_device == null)
+ throw new InvalidOperationException("Could not create device.");
+
+ _context = _alc.CreateContext(_device, null);
+ _alc.MakeContextCurrent(_context);
+ Al.GetError();
+ }
+
+ ///
+ public void Dispose()
+ {
+ _alc.DestroyContext(_context);
+ _alc.CloseDevice(_device);
+ Al.Dispose();
+ _alc.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Engine/SharpEngine.Core/Audio/AudioFileExtensions.cs b/Engine/SharpEngine.Core/Audio/AudioFileExtensions.cs
new file mode 100644
index 0000000..93160e2
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioFileExtensions.cs
@@ -0,0 +1,16 @@
+namespace SharpEngine.Core.Audio;
+
+///
+/// Contains all the supported audio file extensions.
+///
+public static class AudioFileExtensions
+{
+ /// The file extension of the Waveform audio file.
+ public const string WavExtension = ".wav";
+
+ /// The file extension of the MPEG-1 Audio Layer III or MPEG-2 Audio Layer III (MP3) file.
+ public const string Mp3Extension = ".mp3";
+
+ /// The file extension of the MPEG-4 (MP4) file.
+ public const string Mp4Extension = ".mp4";
+}
diff --git a/Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs b/Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs
new file mode 100644
index 0000000..5778672
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs
@@ -0,0 +1,67 @@
+using SharpEngine.Core.Scenes;
+
+using System;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a base class for all audio players.
+///
+public abstract class AudioPlayerBase : SceneNode
+{
+ /// Gets the extension accepted by the audio player.
+ protected abstract string FileExtension { get; }
+
+ /// Gets or sets the properties for the player.
+ public AudioProperties AudioProperties { get; set; }
+
+ /// Contains the audio data of the media file.
+ protected readonly WavData Data;
+
+ /// Represents a container for the .
+ protected readonly AudioBuffer AudioBuffer;
+
+ /// The source of the audio.
+ protected readonly AudioSource AudioSource;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ protected AudioPlayerBase()
+ {
+ Data = new WavData();
+
+ var audioDevice = new AudioDevice();
+ AudioBuffer = new AudioBuffer(audioDevice.Al);
+ AudioSource = new AudioSource(audioDevice.Al);
+
+ AudioProperties = new AudioProperties(audioDevice.Al, AudioSource);
+ }
+
+ ///
+ /// Plays a media file specified by the given path.
+ ///
+ /// The location of the media file to be played.
+ public abstract void Play(string filePath);
+
+ ///
+ /// Validates the specified file by checking its extension and existence.
+ ///
+ /// The path to the file that needs to be validated for its type and existence.
+ /// Thrown when the file does not have the allowed extension.
+ /// Thrown when the specified file does not exist in the file system.
+ protected virtual void ValidateFile(string filePath)
+ {
+ if (Path.GetExtension(filePath) != FileExtension)
+ throw new ArgumentException($"Given file is not a {FileExtension} file", nameof(filePath));
+
+ if (!File.Exists(filePath))
+ throw new FileNotFoundException("Given file does not exist", filePath);
+ }
+
+ ///
+ /// Stops the audio playback of the associated AudioSource.
+ ///
+ public virtual void Stop() => AudioSource.Stop();
+}
diff --git a/Engine/SharpEngine.Core/Audio/AudioProperties.cs b/Engine/SharpEngine.Core/Audio/AudioProperties.cs
new file mode 100644
index 0000000..8a57383
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioProperties.cs
@@ -0,0 +1,36 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Contains properties used to control how the audio should be played.
+///
+public class AudioProperties
+{
+ private readonly AL _al;
+ private readonly uint _audioSourceId;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The context where the audio should be played.
+ /// The source at which the audio should be played.
+ public AudioProperties(AL al, AudioSource audioSource)
+ {
+ _al = al;
+ _audioSourceId = audioSource.Get();
+ }
+
+ /// Gets or sets whether the audio should be looping.
+ public bool IsLooping
+ {
+ get => _isLooping;
+ set
+ {
+ _isLooping = value;
+ _al.SetSourceProperty(_audioSourceId, SourceBoolean.Looping, value);
+ }
+ }
+
+ private bool _isLooping;
+}
diff --git a/Engine/SharpEngine.Core/Audio/AudioSource.cs b/Engine/SharpEngine.Core/Audio/AudioSource.cs
new file mode 100644
index 0000000..9e59179
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/AudioSource.cs
@@ -0,0 +1,47 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a source for audio
+///
+public class AudioSource
+{
+ private readonly uint _source;
+ private readonly AL _al;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// An accessor to the OpenAL context.
+ public AudioSource(AL al)
+ {
+ _al = al;
+ _source = _al.GenSource();
+ }
+
+ ///
+ /// Gets the source identifier.
+ ///
+ /// The identifier to the source.
+ public uint Get() => _source;
+
+ ///
+ /// Plays the given audio from the .
+ ///
+ /// A buffer containing the audio data.
+ public void Play(AudioBuffer buffer)
+ {
+ _al.SetSourceProperty(_source, SourceInteger.Buffer, buffer.BufferId);
+ _al.SourcePlay(_source);
+ }
+
+ ///
+ /// Stops playing the audio.
+ ///
+ public void Stop()
+ {
+ _al.SourceStop(_source);
+ _al.DeleteSource(_source);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Audio/WavData.cs b/Engine/SharpEngine.Core/Audio/WavData.cs
new file mode 100644
index 0000000..80ac937
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/WavData.cs
@@ -0,0 +1,22 @@
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents audio data in WAV format.
+///
+public class WavData
+{
+ /// Gets or sets the number of channels used in the data.
+ public short NumChannels { get; set; }
+
+ /// Gets or sets the sample rate of the data.
+ public int SampleRate { get; set; }
+
+ /// Gets or sets the number of bits per sample.
+ public short BitsPerSample { get; set; }
+
+ /// Gets or sets the format of the buffer.
+ /// Defines how data is organized in the buffer.
+ public BufferFormat Format { get; set; }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Audio/WavPlayer.cs b/Engine/SharpEngine.Core/Audio/WavPlayer.cs
new file mode 100644
index 0000000..a686bea
--- /dev/null
+++ b/Engine/SharpEngine.Core/Audio/WavPlayer.cs
@@ -0,0 +1,116 @@
+using SharpEngine.Shared;
+using Silk.NET.OpenAL;
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+
+namespace SharpEngine.Core.Audio;
+
+///
+/// Represents a player for .wav files.
+///
+public class WavPlayer : AudioPlayerBase
+{
+ ///
+ protected override string FileExtension => ".wav";
+
+ private static class WavConstants
+ {
+ public static ReadOnlySpan RiffHeader => "RIFF"u8;
+ public static ReadOnlySpan WaveHeader => "WAVE"u8;
+ public static ReadOnlySpan FmtHeader => "fmt "u8;
+ public static ReadOnlySpan DataHeader => "data"u8;
+
+ public const int ChunkSizeLength = 4;
+ public const int HeaderLength = 4;
+ public const int FormatChunkSkipLength = 6;
+ public const int AudioFormatLength = 2;
+ public const int NumChannelsLength = 2;
+ public const int SampleRateLength = 4;
+ public const int BitsPerSampleLength = 2;
+ }
+
+ ///
+ public override void Play(string filePath)
+ {
+ ValidateFile(filePath);
+
+ ReadOnlySpan file = File.ReadAllBytes(filePath);
+ int index = 0;
+
+ if (!CheckHeader(file, ref index, WavConstants.RiffHeader))
+ {
+ Debug.Log.Warning("Given file is not in RIFF format");
+ return;
+ }
+
+ index += WavConstants.ChunkSizeLength; // Skip chunk size
+
+ if (!CheckHeader(file, ref index, WavConstants.WaveHeader))
+ {
+ Debug.Log.Warning("Given file is not in WAVE format");
+ return;
+ }
+
+ while (index + WavConstants.HeaderLength <= file.Length)
+ {
+ var identifier = file.Slice(index, WavConstants.HeaderLength);
+ index += WavConstants.HeaderLength;
+
+ var size = BinaryPrimitives.ReadInt32LittleEndian(file.Slice(index, WavConstants.ChunkSizeLength));
+ index += WavConstants.ChunkSizeLength;
+
+ var data = file.Slice(index, size);
+ switch (identifier)
+ {
+ case var id when id.SequenceEqual(WavConstants.FmtHeader):
+ ParseFormat(data, Data);
+ break;
+
+ case var id when id.SequenceEqual(WavConstants.DataHeader):
+ AudioBuffer.LoadData(data, Data);
+ break;
+ }
+
+ index += size;
+ }
+
+ AudioSource.Play(AudioBuffer);
+ }
+
+ private static bool CheckHeader(ReadOnlySpan file, ref int index, ReadOnlySpan header)
+ {
+ if (!file.Slice(index, header.Length).SequenceEqual(header))
+ return false;
+
+ index += header.Length;
+ return true;
+ }
+
+ private static void ParseFormat(ReadOnlySpan formatChunk, WavData wavData)
+ {
+ int index = 0;
+ var audioFormat = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.AudioFormatLength));
+ index += WavConstants.AudioFormatLength;
+
+ if (audioFormat != 1)
+ throw new InvalidOperationException($"Unknown Audio Format with ID {audioFormat}");
+
+ wavData.NumChannels = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.NumChannelsLength));
+ index += WavConstants.NumChannelsLength;
+
+ wavData.SampleRate = BinaryPrimitives.ReadInt32LittleEndian(formatChunk.Slice(index, WavConstants.SampleRateLength));
+ index += WavConstants.SampleRateLength;
+
+ index += WavConstants.FormatChunkSkipLength; // Skip byteRate and blockAlign
+ wavData.BitsPerSample = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.BitsPerSampleLength));
+
+ wavData.Format = wavData.NumChannels switch
+ {
+ 1 => wavData.BitsPerSample == 8 ? BufferFormat.Mono8 : BufferFormat.Mono16,
+ 2 => wavData.BitsPerSample == 8 ? BufferFormat.Stereo8 : BufferFormat.Stereo16,
+ _ => throw new InvalidOperationException($"Can't play audio with {wavData.NumChannels} channels")
+ };
+ }
+}
diff --git a/Engine/SharpEngine.Core/Entities/BezierCurve.cs b/Engine/SharpEngine.Core/Entities/BezierCurve.cs
new file mode 100644
index 0000000..cd2547c
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/BezierCurve.cs
@@ -0,0 +1,65 @@
+using Silk.NET.Maths;
+using System;
+using System.Collections.Generic;
+
+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;
+
+ /// 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;
+
+ 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);
+ }
+
+ ///
+ /// 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++)
+ {
+ 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/Engine/SharpEngine.Core/Entities/GameObject.cs b/Engine/SharpEngine.Core/Entities/GameObject.cs
new file mode 100644
index 0000000..160896a
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/GameObject.cs
@@ -0,0 +1,114 @@
+using SharpEngine.Core.Attributes;
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Entities.Views;
+using SharpEngine.Core.Interfaces;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Scenes;
+using SharpEngine.Core.Shaders;
+using SharpEngine.Core.Windowing;
+using Shader = SharpEngine.Core.Shaders.Shader;
+
+using Silk.NET.OpenGL;
+using Tutorial;
+using System.Threading.Tasks;
+
+namespace SharpEngine.Core.Entities;
+
+///
+/// Represents a game object in the scene.
+///
+public class GameObject : EmptyNode, IRenderable
+{
+ ///
+ /// Initializes a new instance of the .
+ ///
+ public GameObject() : base(string.Empty)
+ {
+ BoundingBox = BoundingBox.CalculateBoundingBox(Transform);
+ Shader = ShaderService.Instance.LoadShader(_Resources.Default.VertexShader, _Resources.Default.FragmentShader, "lighting");
+ }
+
+ public GameObject(Model_Old model) : base(string.Empty)
+ {
+ Model = model;
+ BoundingBox = BoundingBox.CalculateBoundingBox(Transform);
+ Shader = ShaderService.Instance.LoadShader(_Resources.Default.VertexShader, _Resources.Default.FragmentShader, "lighting");
+ }
+
+ ///
+ /// 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.
+ /// The file path of the vertex shader.
+ /// The file path of the fragment shader.
+ public GameObject(Shader shader, Model_Old model) : base(string.Empty)
+ {
+ Model = model;
+ BoundingBox = BoundingBox.CalculateBoundingBox(Transform);
+ Shader = shader;
+ }
+
+ public Shader Shader { get; set; }
+
+ ///
+ /// Gets or sets the mesh of the game object.
+ ///
+ public Model_Old Model { get; set; }
+
+ ///
+ /// Gets or sets the transform of the game object.
+ ///
+ public override Transform Transform
+ {
+ get => _transform;
+ set
+ {
+ _transform = value;
+ BoundingBox = BoundingBox.CalculateBoundingBox(_transform);
+ }
+ }
+
+ private Transform _transform = new();
+
+ ///
+ /// Gets the bounding box of the game object.
+ ///
+ [Inspector(DisplayInInspector = false)]
+ public BoundingBox BoundingBox { get; set; }
+
+ protected virtual void SetShaderUniforms(CameraView camera)
+ {
+ Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix);
+
+ // TODO: One of these could be calculated once and "reloaded" if it changes.
+ Shader.SetMatrix4(ShaderAttributes.View, camera.GetViewMatrix(), true);
+ Shader.SetMatrix4(ShaderAttributes.Projection, camera.GetProjectionMatrix(), true);
+ }
+
+ ///
+ public override Task Render(CameraView camera, Window window)
+ {
+ // TODO: This needs to removed later once fixed.
+ if (Model is null || Model.Meshes is null)
+ return Task.CompletedTask;
+
+ foreach (var mesh in Model.Meshes)
+ {
+ mesh.Bind();
+
+ foreach (var texture in mesh.Textures)
+ texture.Use();
+
+ Shader.Use();
+ SetShaderUniforms(camera);
+
+ foreach (var material in mesh.Materials)
+ material.SetUniformValues(Shader);
+
+ Window.GL.DrawArrays(PrimitiveType.Triangles, 0, (uint)mesh.Vertices.Length);
+ }
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/Core/Entities/Lights/DirectionalLight.cs b/Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs
similarity index 58%
rename from Core/Entities/Lights/DirectionalLight.cs
rename to Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs
index 4fcb63b..c7c0701 100644
--- a/Core/Entities/Lights/DirectionalLight.cs
+++ b/Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs
@@ -1,6 +1,9 @@
-using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core._Resources;
+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;
@@ -24,16 +27,16 @@ public DirectionalLight()
Diffuse = new Vector3(0.4f, 0.4f, 0.4f);
Specular = new Vector3(0.5f, 0.5f, 0.5f);
- Material.Shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/lighting.frag"), "lighting");
+ Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.FragmentShader, "lighting");
}
///
- public override Task Render()
+ public Task Render(CameraView camera, Window window)
{
- Material.Shader.SetVector3("dirLight.direction", Direction);
- Material.Shader.SetVector3("dirLight.ambient", Ambient);
- Material.Shader.SetVector3("dirLight.diffuse", Diffuse);
- Material.Shader.SetVector3("dirLight.specular", Specular);
+ Shader.SetVector3("dirLight.direction", Direction);
+ Shader.SetVector3("dirLight.ambient", Ambient);
+ Shader.SetVector3("dirLight.diffuse", Diffuse);
+ Shader.SetVector3("dirLight.specular", Specular);
return Task.CompletedTask;
}
diff --git a/Core/Entities/Lights/Light.cs b/Engine/SharpEngine.Core/Entities/Lights/Light.cs
similarity index 100%
rename from Core/Entities/Lights/Light.cs
rename to Engine/SharpEngine.Core/Entities/Lights/Light.cs
diff --git a/Core/Entities/Lights/PointLight.cs b/Engine/SharpEngine.Core/Entities/Lights/PointLight.cs
similarity index 54%
rename from Core/Entities/Lights/PointLight.cs
rename to Engine/SharpEngine.Core/Entities/Lights/PointLight.cs
index 858dbf8..0522f97 100644
--- a/Core/Entities/Lights/PointLight.cs
+++ b/Engine/SharpEngine.Core/Entities/Lights/PointLight.cs
@@ -1,5 +1,8 @@
-using SharpEngine.Core.Extensions;
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Entities.Views;
+using SharpEngine.Core.Extensions;
using SharpEngine.Core.Shaders;
+using SharpEngine.Core.Windowing;
using System.Numerics;
using System.Threading.Tasks;
@@ -18,14 +21,13 @@ public class PointLight : Light
public PointLight(Vector3 position, int index)
{
Transform.Scale = new(0.2f, 0.2f, 0.2f);
- Transform.Position = position;
+ Transform.Position = (Numerics.Vector3)position;
Constant = 1.0f;
Linear = 0.09f;
Quadratic = 0.032f;
_index = index;
- Material.Shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/lighting.frag"), "lighting");
LampShader = new LampShader();
}
@@ -49,21 +51,23 @@ public PointLight(Vector3 position, int index)
///
public float Quadratic { get; set; }
- ///
- public override Task Render()
+ protected override void SetShaderUniforms(CameraView camera)
{
- Material.Shader.SetVector3($"pointLights[{_index}].position", Transform.Position);
- Material.Shader.SetVector3($"pointLights[{_index}].ambient", Ambient);
- Material.Shader.SetVector3($"pointLights[{_index}].diffuse", Diffuse);
- Material.Shader.SetVector3($"pointLights[{_index}].specular", Specular);
- Material.Shader.SetFloat($"pointLights[{_index}].constant", Constant);
- Material.Shader.SetFloat($"pointLights[{_index}].linear", Linear);
- Material.Shader.SetFloat($"pointLights[{_index}].quadratic", Quadratic);
+ Shader.SetVector3($"pointLights[{_index}].position", (Vector3)Transform.Position);
+ Shader.SetVector3($"pointLights[{_index}].ambient", Ambient);
+ Shader.SetVector3($"pointLights[{_index}].diffuse", Diffuse);
+ Shader.SetVector3($"pointLights[{_index}].specular", Specular);
+ Shader.SetFloat($"pointLights[{_index}].constant", Constant);
+ Shader.SetFloat($"pointLights[{_index}].linear", Linear);
+ Shader.SetFloat($"pointLights[{_index}].quadratic", Quadratic);
- var lampMatrix = Matrix4x4.CreateScale(Transform.Scale);
- lampMatrix *= Matrix4x4.CreateTranslation(Transform.Position);
+ base.SetShaderUniforms(camera);
+ }
- LampShader.Shader?.SetMatrix4(ShaderAttributes.Model, lampMatrix);
+ ///
+ public Task Render(CameraView camera, Window window)
+ {
+ SetShaderUniforms(camera);
return Task.CompletedTask;
}
diff --git a/Core/Entities/Lights/SpotLight.cs b/Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs
similarity index 57%
rename from Core/Entities/Lights/SpotLight.cs
rename to Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs
index 502952d..59ad0fe 100644
--- a/Core/Entities/Lights/SpotLight.cs
+++ b/Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs
@@ -1,5 +1,8 @@
-using SharpEngine.Core.Extensions;
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Entities.Views;
+using SharpEngine.Core.Extensions;
using SharpEngine.Core.Shaders;
+using SharpEngine.Core.Windowing;
using System.Numerics;
using System.Threading.Tasks;
@@ -11,7 +14,7 @@ namespace SharpEngine.Core.Entities.Lights;
public class SpotLight : Light
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of .
///
public SpotLight()
{
@@ -22,7 +25,7 @@ public SpotLight()
Linear = 0.09f;
Quadratic = 0.032f;
- Material.Shader = ShaderService.Instance.LoadShader(PathExtensions.GetAssemblyPath("Shaders/shader.vert"), PathExtensions.GetAssemblyPath("Shaders/lighting.frag"), "lighting");
+ Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.FragmentShader, "lighting");
}
///
@@ -55,20 +58,27 @@ public SpotLight()
///
public float Quadratic { get; set; }
- ///
- public override Task Render()
+ protected override void SetShaderUniforms(CameraView camera)
{
- Material.Shader.SetVector3("spotLight.position", Transform.Position);
- Material.Shader.SetVector3("spotLight.direction", Direction);
- Material.Shader.SetVector3("spotLight.ambient", Ambient);
- Material.Shader.SetVector3("spotLight.diffuse", Diffuse);
- Material.Shader.SetVector3("spotLight.specular", Specular);
- Material.Shader.SetFloat("spotLight.constant", Constant);
- Material.Shader.SetFloat("spotLight.linear", Linear);
- Material.Shader.SetFloat("spotLight.quadratic", Quadratic);
- Material.Shader.SetFloat("spotLight.cutOff", CutOff);
- Material.Shader.SetFloat("spotLight.outerCutOff", OuterCutOff);
+ Shader.SetVector3("spotLight.position", (Vector3)Transform.Position);
+ Shader.SetVector3("spotLight.direction", Direction);
+ Shader.SetVector3("spotLight.ambient", Ambient);
+ Shader.SetVector3("spotLight.diffuse", Diffuse);
+ Shader.SetVector3("spotLight.specular", Specular);
+ Shader.SetFloat("spotLight.constant", Constant);
+ Shader.SetFloat("spotLight.linear", Linear);
+ Shader.SetFloat("spotLight.quadratic", Quadratic);
+ Shader.SetFloat("spotLight.cutOff", CutOff);
+ Shader.SetFloat("spotLight.outerCutOff", OuterCutOff);
+
+ base.SetShaderUniforms(camera);
+ }
+ ///
+ public override Task Render(CameraView camera, Window window)
+ {
+ SetShaderUniforms(camera);
+
return Task.CompletedTask;
}
}
diff --git a/Core/Entities/Properties/Meshes/MeshService.cs b/Engine/SharpEngine.Core/Entities/MeshService.cs
similarity index 69%
rename from Core/Entities/Properties/Meshes/MeshService.cs
rename to Engine/SharpEngine.Core/Entities/MeshService.cs
index 67e5300..99a60a2 100644
--- a/Core/Entities/Properties/Meshes/MeshService.cs
+++ b/Engine/SharpEngine.Core/Entities/MeshService.cs
@@ -1,5 +1,4 @@
-using Silk.NET.OpenGL;
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace SharpEngine.Core.Entities.Properties.Meshes;
@@ -26,15 +25,7 @@ public Mesh LoadMesh(string identifier, Mesh mesh)
if (Meshes.TryGetValue(identifier, out var cachedMesh))
return cachedMesh;
- var meshData = mesh.GetVertices();
-
- var vertexBufferObject = Window.GL.GenBuffer();
- Window.GL.BindBuffer(GLEnum.ArrayBuffer, vertexBufferObject);
- Window.GL.BufferData(GLEnum.ArrayBuffer, (uint)meshData.Length * sizeof(float), meshData, GLEnum.StaticDraw);
-
Meshes.Add(identifier, mesh);
return mesh;
}
-
- // TODO: #3 & #2 Load mesh from file
}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs b/Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs
new file mode 100644
index 0000000..b0dded0
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs
@@ -0,0 +1,24 @@
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Scenes;
+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.
+/// 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; }
+
+ /// Gets or sets the dimensions of the content area.
+ public required IVector ContentAreaSize { get; set; }
+
+ ///
+ ///
+ public override T[][] GetValues() => throw new NotImplementedException();
+}
diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs b/Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs
new file mode 100644
index 0000000..452cc3e
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs
@@ -0,0 +1,88 @@
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Scenes;
+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 where TItem : SceneNode, new()
+{
+ /// 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 TItem 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;
+ }
+ }
+
+ ///
+ public override void AddItem(TItem item)
+ {
+ base.AddItem(item);
+
+ var offset = -100; // TODO: #26 Fix this, this is just a temporary solution.
+
+ if (Items.Count % Columns != 0)
+ offset = offset + (Items.Count * 30);
+
+ // item.Transform.Position.X = offset;
+ }
+
+ ///
+ /// 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 TItem[][] GetValues()
+ {
+ var values = new TItem[Rows][];
+ for (uint i = 0; i < Rows; i++)
+ {
+ values[i] = new TItem[Columns];
+ for (uint j = 0; j < Columns; j++)
+ {
+ var item = this[i, j];
+ values[i][j] = item;
+ }
+ }
+
+ return values;
+ }
+}
diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs b/Engine/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs
new file mode 100644
index 0000000..bb65554
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs
@@ -0,0 +1,44 @@
+using SharpEngine.Core.Scenes;
+using System.Collections.Generic;
+
+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);
+
+ foreach (var node in nodes)
+ AddItem((TItem)node);
+
+ return this;
+ }
+
+ ///
+ /// Adds and item to the container.
+ ///
+ /// The item to be added.
+ public virtual void AddItem(TItem item)
+ {
+ Items.Add(item);
+ }
+
+ ///
+ /// Gets the items in the container.
+ ///
+ /// The items as a 2D array representation.
+ public abstract TItem[][] GetValues();
+}
diff --git a/Engine/SharpEngine.Core/Entities/UI/UIElement.cs b/Engine/SharpEngine.Core/Entities/UI/UIElement.cs
new file mode 100644
index 0000000..4230410
--- /dev/null
+++ b/Engine/SharpEngine.Core/Entities/UI/UIElement.cs
@@ -0,0 +1,108 @@
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Entities.Properties.Meshes;
+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.Numerics;
+using System.Threading.Tasks;
+using Vector2 = SharpEngine.Core.Numerics.Vector2;
+
+namespace SharpEngine.Core.Entities.UI;
+
+///
+/// Represents a User Interface entity.
+///
+public class UIElement : EmptyNode, IRenderable
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public UIElement() : this("UIElement") { }
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The name of the UI element.
+ public UIElement(string name) : base(name)
+ {
+ // TODO: #5 Support custom meshes?
+ Mesh = MeshService.Instance.LoadMesh(nameof(Primitives.Plane), Primitives.Plane.Mesh);
+
+ Initialize();
+ }
+
+ private readonly UIShader _uiShader = new();
+
+ /// Gets or sets the width of the ui element.
+ public float Width { get; set; } = 10;
+
+ /// Gets or sets the height of the ui element.
+ public float Height { get; set; } = 10;
+
+ /// Gets or sets the mesh of the UI element.
+ public Mesh Mesh { get; set; }
+
+ ///
+ public uint VAO { get; set; }
+
+ ///
+ public void Initialize()
+ {
+ VAO = Window.GL.GenVertexArray();
+ Bind();
+
+ InitializeBuffers(Mesh);
+
+ _uiShader.Shader?.Use();
+ _uiShader.SetAttributes();
+ }
+
+ ///
+ public void Bind()
+ {
+ Window.GL.BindVertexArray(VAO);
+ }
+
+ ///
+ public void InitializeBuffers(Mesh mesh, bool useMeshVertices = false)
+ {
+ 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);
+ }
+
+ Matrix4x4 OrthoMatrix = Matrix4x4.CreateOrthographicOffCenter(-1, 1, -1, 1, -1, 1);
+
+ ///
+ /// Render the UI element.
+ ///
+ public override Task Render(CameraView camera, Window window)
+ {
+ _uiShader.Shader.Use();
+ Bind();
+
+ // TODO: #75 These should come from somewhere else.
+ 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));
+ _uiShader.Shader.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix);
+ _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;
+ }
+}
diff --git a/Core/Entities/Views/CameraView.cs b/Engine/SharpEngine.Core/Entities/Views/CameraView.cs
similarity index 91%
rename from Core/Entities/Views/CameraView.cs
rename to Engine/SharpEngine.Core/Entities/Views/CameraView.cs
index f3eb785..d98a3ff 100644
--- a/Core/Entities/Views/CameraView.cs
+++ b/Engine/SharpEngine.Core/Entities/Views/CameraView.cs
@@ -1,3 +1,4 @@
+using SharpEngine.Core.Components.Properties;
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Shaders;
using System.Numerics;
@@ -24,20 +25,6 @@ public CameraView(Vector3 position, IViewSettings settings) : base(settings)
AspectRatio = settings.WindowOptions.Size.X / (float)settings.WindowOptions.Size.Y;
}
- // Those vectors are directions pointing outwards from the camera to define how it rotated.
- private Vector3 _front = -Vector3.UnitZ;
- private Vector3 _up = Vector3.UnitY;
- private Vector3 _right = Vector3.UnitX;
-
- /// Gets the front vector of the camera.
- public Vector3 Front => _front;
-
- /// Gets the up vector of the camera.
- public Vector3 Up => _up;
-
- /// Gets the right vector of the camera.
- public Vector3 Right => _right;
-
// Rotation around the X axis (radians)
private float _pitch;
@@ -147,7 +134,7 @@ public override void UpdateMousePosition(Vector2 mousePosition)
lastPos = new Vector2(mousePosition.X, mousePosition.Y);
firstMove = false;
}
- else // TODO: Split so that only the camera view will rotate
+ else // TODO: #48 Split so that only the camera view will rotate
{
var deltaX = mousePosition.X - lastPos.X;
var deltaY = mousePosition.Y - lastPos.Y;
@@ -222,8 +209,8 @@ public Plane[] GetFrustumPlanes()
/// The shader to set the uniforms on.
public override void SetShaderUniforms(Shader shader)
{
- shader.SetMatrix4("view", GetViewMatrix());
- shader.SetMatrix4("projection", GetProjectionMatrix());
+ shader.SetMatrix4(ShaderAttributes.View, GetViewMatrix());
+ shader.SetMatrix4(ShaderAttributes.Projection, GetProjectionMatrix());
shader.SetVector3("viewPos", Position);
}
}
\ No newline at end of file
diff --git a/Core/Entities/Views/Settings/DefaultViewSettings.cs b/Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs
similarity index 77%
rename from Core/Entities/Views/Settings/DefaultViewSettings.cs
rename to Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs
index 5a777e1..cef7852 100644
--- a/Core/Entities/Views/Settings/DefaultViewSettings.cs
+++ b/Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs
@@ -1,5 +1,5 @@
using SharpEngine.Core.Renderers;
-
+using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.Windowing;
@@ -19,7 +19,7 @@ public DefaultViewSettings()
}
///
- /// Convenience wrapper around creating a new WindowProperties with sensible defaults.
+ /// Convenience wrapper around creating a new WindowProperties with sensible defaults.
///
public static ViewSettings Default { get; private set; }
@@ -41,4 +41,10 @@ public DefaultViewSettings()
Title = "SharpEngine",
Size = new Vector2D(1280, 720),
};
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Core/Entities/Views/Settings/IViewSettings.cs b/Engine/SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs
similarity index 100%
rename from Core/Entities/Views/Settings/IViewSettings.cs
rename to Engine/SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs
diff --git a/Core/Entities/Views/Settings/ViewSettings.cs b/Engine/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs
similarity index 82%
rename from Core/Entities/Views/Settings/ViewSettings.cs
rename to Engine/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs
index a28ffe3..09c65cb 100644
--- a/Core/Entities/Views/Settings/ViewSettings.cs
+++ b/Engine/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs
@@ -1,6 +1,5 @@
-using SharpEngine.Core.Interfaces;
-using SharpEngine.Core.Renderers;
-using Silk.NET.Maths;
+using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
using Silk.NET.Windowing;
namespace SharpEngine.Core.Entities.Views.Settings;
@@ -47,4 +46,10 @@ public ViewSettings()
///
public WindowOptions WindowOptions { get; set; }
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
}
diff --git a/Core/Entities/Views/View.cs b/Engine/SharpEngine.Core/Entities/Views/View.cs
similarity index 55%
rename from Core/Entities/Views/View.cs
rename to Engine/SharpEngine.Core/Entities/Views/View.cs
index a6f5bf6..02aecba 100644
--- a/Core/Entities/Views/View.cs
+++ b/Engine/SharpEngine.Core/Entities/Views/View.cs
@@ -1,5 +1,9 @@
-using SharpEngine.Core.Entities.Views.Settings;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Core.Entities.Views.Settings;
+using SharpEngine.Core.Scenes;
using SharpEngine.Core.Shaders;
+using System;
+using System.Collections.Generic;
using System.Numerics;
namespace SharpEngine.Core.Entities.Views;
@@ -18,15 +22,28 @@ public View(IViewSettings settings)
Settings = settings;
}
+ protected Vector3 _front = -Vector3.UnitZ;
+ protected Vector3 _up = Vector3.UnitY;
+ protected Vector3 _right = Vector3.UnitX;
+
+ /// Gets the front vector of the camera.
+ public Vector3 Front => _front;
+
+ /// Gets the up vector of the camera.
+ public Vector3 Up => _up;
+
+ /// 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;
@@ -65,4 +82,18 @@ public virtual void UpdateMousePosition(Vector2 mousePosition)
firstMove = false;
}
}
+
+ ///
+ /// Checks whether a game object is in view of the camera.
+ ///
+ ///
+ /// Uses a step based ray marching algorithm to check if any object is in view of the camera.
+ ///
+ /// The scene where the check should be applied to.
+ /// The types that are allowed to be hit.
+ /// The first object that the ray intersected.
+ /// The position where the object was hit.
+ /// if any object was hit; otherwise, .
+ public bool IsInView(Scene scene, out GameObject? intersectingObject, out Vector3 hitPosition, params Type[] allowedTypes)
+ => new Ray(Position, Front).IsGameObjectInView(scene, out intersectingObject, out hitPosition, allowedTypes);
}
\ No newline at end of file
diff --git a/Core/Enums/MouseWheelScrollDirection.cs b/Engine/SharpEngine.Core/Enums/MouseWheelScrollDirection.cs
similarity index 100%
rename from Core/Enums/MouseWheelScrollDirection.cs
rename to Engine/SharpEngine.Core/Enums/MouseWheelScrollDirection.cs
diff --git a/Core/Extensions/CollectionExtensions.cs b/Engine/SharpEngine.Core/Extensions/CollectionExtensions.cs
similarity index 97%
rename from Core/Extensions/CollectionExtensions.cs
rename to Engine/SharpEngine.Core/Extensions/CollectionExtensions.cs
index aab5eb9..fa8686d 100644
--- a/Core/Extensions/CollectionExtensions.cs
+++ b/Engine/SharpEngine.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/Extensions/PathExtensions.cs b/Engine/SharpEngine.Core/Extensions/PathExtensions.cs
similarity index 100%
rename from Core/Extensions/PathExtensions.cs
rename to Engine/SharpEngine.Core/Extensions/PathExtensions.cs
diff --git a/Engine/SharpEngine.Core/Primitives/Cube.cs b/Engine/SharpEngine.Core/Primitives/Cube.cs
new file mode 100644
index 0000000..16be872
--- /dev/null
+++ b/Engine/SharpEngine.Core/Primitives/Cube.cs
@@ -0,0 +1,203 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Textures;
+using SharpEngine.Core.Windowing;
+using Tutorial;
+
+namespace SharpEngine.Core.Primitives;
+
+///
+/// Used to create a primitive cube object.
+///
+public static class Cube
+{
+ static Cube()
+ {
+ if (_loaded)
+ return;
+
+ var defaultTexture = TextureService.Instance.LoadTexture(Default.DebugTexture);
+
+ var mesh = new Mesh(Window.GL)
+ {
+ Vertices = [.. Vertices],
+ Normals = [.. Normals],
+ TextureCoordinates = [.. TextureCoordinates],
+ Indices = [.. Indices],
+ Textures = [defaultTexture],
+ // Materials = [MaterialService.Instance.LoadMaterial(Default.DebugMaterial)],
+ Materials = [new(defaultTexture)]
+ };
+
+ Mesh = MeshService.Instance.LoadMesh(nameof(Cube), mesh);
+ Model = new(Window.GL, Mesh);
+
+ _loaded = true;
+ }
+
+ private static bool _loaded;
+
+ public static Model_Old Model;
+
+ /// The cube mesh.
+ public static readonly Mesh Mesh;
+
+ public static float[] Vertices =
+ [
+ -0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f,
+ -0.5f, -0.5f, -0.5f,
+
+ -0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ -0.5f, -0.5f, 0.5f,
+
+ -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, -0.5f,
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+
+ -0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, -0.5f, -0.5f,
+
+ -0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, -0.5f,
+ ];
+ public static float[] Normals =
+ [
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+ 0.0f, 0.0f, -1.0f,
+
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f,
+
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+ -1.0f, 0.0f, 0.0f,
+
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f,
+
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f,
+
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ ];
+ public static float[] TextureCoordinates =
+ [
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 1.0f,
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f,
+
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f
+ ];
+ public static uint[] 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/Engine/SharpEngine.Core/Primitives/Plane.cs
similarity index 52%
rename from Core/Primitives/Plane.cs
rename to Engine/SharpEngine.Core/Primitives/Plane.cs
index 1468423..ba046a5 100644
--- a/Core/Primitives/Plane.cs
+++ b/Engine/SharpEngine.Core/Primitives/Plane.cs
@@ -1,4 +1,5 @@
using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Windowing;
namespace SharpEngine.Core.Primitives;
@@ -8,28 +9,33 @@ namespace SharpEngine.Core.Primitives;
public static class Plane
{
/// The plane mesh.
- public static Mesh Mesh { get; } = new()
+ public static Mesh Mesh { get; } = new(Window.GL)
{
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/Primitives/PrimitiveFactory.cs b/Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs
similarity index 63%
rename from Core/Primitives/PrimitiveFactory.cs
rename to Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs
index 38ec50e..633580d 100644
--- a/Core/Primitives/PrimitiveFactory.cs
+++ b/Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs
@@ -1,8 +1,11 @@
using SharpEngine.Core.Entities;
using SharpEngine.Core.Entities.Properties;
-using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Shaders;
using System;
+using System.Collections.Generic;
using System.Numerics;
+using Tutorial;
namespace SharpEngine.Core.Primitives;
@@ -11,17 +14,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.
@@ -33,15 +32,21 @@ 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)
+ {
+ // var material = diffuseMapFile, specularMapFile,
+
+ Model_Old model = primitiveType switch
+ {
+ PrimitiveType.Cube => Cube.Model,
+ // PrimitiveType.Plane => [Plane.Mesh],
+ _ => throw new InvalidOperationException($"A primitive of type {primitiveType} does not exist.")
+ };
+
+ var shader = ShaderService.Instance.LoadShader(vertShaderFile ?? _Resources.Default.VertexShader, fragShaderFile ?? _Resources.Default.FragmentShader, "lighting");
+ return new GameObject(shader, model)
{
- Transform = new Transform { Position = position },
- Mesh = primitiveType switch
- {
- PrimitiveType.Cube => [Cube.Mesh],
- PrimitiveType.Plane => [Plane.Mesh],
- _ => throw new InvalidOperationException($"A primitive of type {primitiveType} does not exist.")
- }
+ Transform = new Transform((Numerics.Vector3)position),
};
+ }
}
diff --git a/Core/Primitives/PrimitiveType.cs b/Engine/SharpEngine.Core/Primitives/PrimitiveType.cs
similarity index 100%
rename from Core/Primitives/PrimitiveType.cs
rename to Engine/SharpEngine.Core/Primitives/PrimitiveType.cs
diff --git a/Core/Ray.cs b/Engine/SharpEngine.Core/Ray.cs
similarity index 89%
rename from Core/Ray.cs
rename to Engine/SharpEngine.Core/Ray.cs
index 68f1dee..942346b 100644
--- a/Core/Ray.cs
+++ b/Engine/SharpEngine.Core/Ray.cs
@@ -13,14 +13,10 @@ namespace SharpEngine.Core;
///
public class Ray
{
- ///
- /// Gets or sets the ray origin.
- ///
+ /// Gets or sets the ray origin.
public Vector3 Origin { get; set; }
- ///
- /// Gets or sets the ray direction.
- ///
+ /// Gets or sets the ray direction.
public Vector3 Direction { get; set; }
///
@@ -35,7 +31,7 @@ public Ray(Vector3 origin, Vector3 direction)
}
///
- /// Checks if the ray intersects with any objects in the scene.
+ /// Checks if the ray intersects with any objects in the scene.
///
/// The scene to check for intersections.
/// The intersecting object; if the ray did not intersect an object.
@@ -47,9 +43,9 @@ 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.Any())
+ if (allowedTypes.Length != 0)
sceneNodes = [.. sceneNodes.Where(go => allowedTypes.Any(type => type.IsInstanceOfType(go)))];
for (float t = 0; t < maxDistance; t += stepSize)
@@ -70,7 +66,7 @@ public bool IsGameObjectInView(Scene scene, out GameObject? intersectingObject,
}
///
- /// Determines if a point is inside a given object.
+ /// Determines if a point is inside a given object.
///
/// The point to check.
/// The object to check against.
@@ -86,7 +82,7 @@ public static bool IsPointInsideObject(Vector3 point, GameObject obj)
}
///
- /// Gets the normal of the closest face of an object to a given point.
+ /// Gets the normal of the closest face of an object to a given point.
///
/// The point to check.
/// The object to check against.
diff --git a/Core/Renderers/RenderFlags.cs b/Engine/SharpEngine.Core/Renderers/RenderFlags.cs
similarity index 63%
rename from Core/Renderers/RenderFlags.cs
rename to Engine/SharpEngine.Core/Renderers/RenderFlags.cs
index 68d5137..005999b 100644
--- a/Core/Renderers/RenderFlags.cs
+++ b/Engine/SharpEngine.Core/Renderers/RenderFlags.cs
@@ -14,13 +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 components will be rendered.
+ Text = 4,
+
+ /// Enables all renderers.
+ All = 7,
}
diff --git a/Core/Renderers/Renderer.cs b/Engine/SharpEngine.Core/Renderers/Renderer.cs
similarity index 72%
rename from Core/Renderers/Renderer.cs
rename to Engine/SharpEngine.Core/Renderers/Renderer.cs
index 9532509..91f4417 100644
--- a/Core/Renderers/Renderer.cs
+++ b/Engine/SharpEngine.Core/Renderers/Renderer.cs
@@ -4,7 +4,8 @@
using SharpEngine.Core.Interfaces;
using SharpEngine.Core.Scenes;
using SharpEngine.Core.Shaders;
-
+using SharpEngine.Core.Windowing;
+using SharpEngine.Shared;
using Silk.NET.OpenGL;
using System;
@@ -23,9 +24,14 @@ public class Renderer : RendererBase
private readonly CameraView _camera;
private readonly Scene _scene;
+ private readonly Window _window;
+
+ // TODO: #7 Property for specific type of objects
+ // No heavy iteration reads for filtering,
+ // instead use a notification system from the scene that an item has been removed / added?
// Read only once, load into OpenGL buffer once.
- // TODO: Multiple meshes
+ // TODO: #5 Multiple meshes
///
public override RenderFlags RenderFlag => RenderFlags.Renderer3D;
@@ -34,13 +40,16 @@ 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, 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: #5 These should be refactored out. The minimum build shouldn't need to use these.
_lightingShader = new LightingShader();
_lampShader = new LampShader();
}
@@ -48,10 +57,18 @@ public Renderer(CameraView camera, ISettings settings, Scene scene) : base(setti
///
public override Task Render()
{
+ // TODO: Make toggling these shaders into a hotkey.
+ // return Task.CompletedTask;
+
try
{
Window.GL.Enable(EnableCap.DepthTest);
+ // Enable image transparency.
+ // TODO: #62 Needs testing.
+ Window.GL.Enable(EnableCap.Blend);
+ Window.GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
+
_camera.SetShaderUniforms(_lightingShader.Shader!);
Window.GL.BindVertexArray(_lightingShader.Vao);
@@ -64,7 +81,7 @@ public override Task Render()
}
catch (Exception ex)
{
- Console.WriteLine(ex);
+ Debug.Log.Error(ex, "{Message}", ex.Message);
return Task.FromException(ex);
}
}
@@ -74,13 +91,14 @@ private Task RenderGameObject(SceneNode node)
if (node is not GameObject gameObject)
return Task.CompletedTask;
- // TODO: Fix culling for blocks that are partially in view
+ // TODO: #7 Fix culling for blocks that are partially in view
// Perform frustum culling
if (!IsInViewFrustum(gameObject.BoundingBox, _camera))
return Task.CompletedTask;
- // TODO: Skip blocks that are behind others relative to the camera
- return gameObject.Render();
+ // TODO: #7 Skip blocks that are behind others relative to the camera
+ return gameObject.Render(_camera, _window);
+ // gameObject.Render(_camera, _window);
}
private static bool IsInViewFrustum(BoundingBox boundingBox, CameraView camera)
diff --git a/Core/Renderers/RendererBase.cs b/Engine/SharpEngine.Core/Renderers/RendererBase.cs
similarity index 97%
rename from Core/Renderers/RendererBase.cs
rename to Engine/SharpEngine.Core/Renderers/RendererBase.cs
index c36bd03..1491fab 100644
--- a/Core/Renderers/RendererBase.cs
+++ b/Engine/SharpEngine.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/Engine/SharpEngine.Core/Renderers/TextRenderer.cs b/Engine/SharpEngine.Core/Renderers/TextRenderer.cs
new file mode 100644
index 0000000..5787fc5
--- /dev/null
+++ b/Engine/SharpEngine.Core/Renderers/TextRenderer.cs
@@ -0,0 +1,22 @@
+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, Window window, ISettings settings, Scene scene) : base(settings)
+ {
+ }
+
+ public override RenderFlags RenderFlag => RenderFlags.Text;
+
+ ///
+ public override Task Render()
+ {
+ return Task.CompletedTask;
+ }
+}
diff --git a/Core/Renderers/UIRenderer.cs b/Engine/SharpEngine.Core/Renderers/UIRenderer.cs
similarity index 51%
rename from Core/Renderers/UIRenderer.cs
rename to Engine/SharpEngine.Core/Renderers/UIRenderer.cs
index 11e2165..54ee5da 100644
--- a/Core/Renderers/UIRenderer.cs
+++ b/Engine/SharpEngine.Core/Renderers/UIRenderer.cs
@@ -1,8 +1,10 @@
-using SharpEngine.Core.Entities;
+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 SharpEngine.Shared;
using Silk.NET.OpenGL;
using System;
@@ -16,7 +18,8 @@ namespace SharpEngine.Core.Renderers;
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;
@@ -24,51 +27,34 @@ public class UIRenderer : RendererBase
///
/// Initializes a new instance of .
///
- public UIRenderer(CameraView _, ISettings settings, Scene scene) : base(settings)
+ public UIRenderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings)
{
_scene = scene;
- _uiShader = new UIShader();
- }
-
- ///
- 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();
+ _camera = camera;
+ _window = window;
}
///
public override Task Render()
{
+ // TODO: Make toggling these shaders into a hotkey.
+ // return Task.CompletedTask;
+
try
{
- Window.GL.Disable(EnableCap.DepthTest);
+ 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 uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera, _window));
- var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render());
return Task.WhenAll(uiElementRenderTasks);
}
catch (Exception ex)
{
- Console.WriteLine(ex);
+ Debug.Log.Error(ex, "{Message}", ex.Message);
return Task.FromException(ex);
}
}
diff --git a/Core/Scenes/Scene.cs b/Engine/SharpEngine.Core/Scenes/Scene.cs
similarity index 87%
rename from Core/Scenes/Scene.cs
rename to Engine/SharpEngine.Core/Scenes/Scene.cs
index 0370af4..e53d524 100644
--- a/Core/Scenes/Scene.cs
+++ b/Engine/SharpEngine.Core/Scenes/Scene.cs
@@ -1,5 +1,7 @@
using SharpEngine.Core.Entities;
-
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Entities.UI;
+using SharpEngine.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -41,13 +43,13 @@ 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; } = [];
/// 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.
@@ -59,7 +61,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);
}
@@ -104,26 +106,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)
+ // TODO: #94 Can we make this async?
+ 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;
}
///
@@ -180,7 +183,7 @@ public IEnumerable IterateAsync(List elements, FuncThe scene from the given file. Loads an empty scene if unable to load the scene.
public static Scene LoadScene(string sceneFile)
{
- Console.WriteLine(sceneFile);
+ Debug.Log.Debug(sceneFile);
var loadedScene = JsonSerializer.Deserialize(sceneFile);
diff --git a/Engine/SharpEngine.Core/Scenes/SceneNode.cs b/Engine/SharpEngine.Core/Scenes/SceneNode.cs
new file mode 100644
index 0000000..13b3526
--- /dev/null
+++ b/Engine/SharpEngine.Core/Scenes/SceneNode.cs
@@ -0,0 +1,110 @@
+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;
+
+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();
+}
+
+///
+/// Represents a node in the scene.
+///
+public abstract class SceneNode
+{
+ /// Gets a new empty node.
+ public static SceneNode Empty => new EmptyNode("Empty Node");
+
+ ///
+ /// Gets or sets the name of the node.
+ ///
+ public string Name { get; set; } = "New Object";
+
+ ///
+ /// Gets or sets the children of the node.
+ ///
+ public List Children { get; set; } = [];
+
+ ///
+ /// Initializes a new empty .
+ ///
+ public SceneNode() { }
+
+ ///
+ /// Initializes a new instance of the with the specified .
+ ///
+ /// The name of the new empty node.
+ 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);
+
+ ///
+ /// 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 TTransform : ITransform, new() where TVector : IVector, new()
+ {
+ 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.
+ public virtual SceneNode AddChild(params SceneNode[] nodes)
+ {
+ foreach (var node in nodes)
+ Children.Add(node);
+
+ return this;
+ }
+
+ ///
+ /// Removes a child node from this node.
+ ///
+ /// The node to be removed.
+ public void RemoveChild(SceneNode node)
+ {
+ Children.Remove(node);
+ }
+
+ ///
+ /// Renders the current object to the screen.
+ ///
+ /// A representing an asynchronous operation.
+ public virtual Task Render(CameraView camera, Window window) => Task.CompletedTask;
+}
diff --git a/Engine/SharpEngine.Core/Shaders/LampShader.cs b/Engine/SharpEngine.Core/Shaders/LampShader.cs
new file mode 100644
index 0000000..898d08b
--- /dev/null
+++ b/Engine/SharpEngine.Core/Shaders/LampShader.cs
@@ -0,0 +1,39 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Windowing;
+using Silk.NET.OpenGL;
+
+namespace SharpEngine.Core.Shaders;
+
+internal class LampShader : ShaderBase
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public LampShader()
+ {
+ Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.LightShader, "lamp").Initialize();
+
+ Vao = Window.GL.GenVertexArray();
+ Window.GL.BindVertexArray(Vao);
+
+ SetAttributes();
+ }
+
+ ///
+ public override bool SetAttributes()
+ {
+ if (!base.SetAttributes())
+ return false;
+
+ if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation))
+ return false;
+
+ var positionLocationUint = (uint)positionLocation;
+ Window.GL.EnableVertexAttribArray(positionLocationUint);
+ Window.GL.VertexAttribPointer(positionLocationUint, 3, VertexAttribPointerType.Float, false, VertexData.Stride, 0);
+
+ return true;
+ }
+}
diff --git a/Engine/SharpEngine.Core/Shaders/LightingShader.cs b/Engine/SharpEngine.Core/Shaders/LightingShader.cs
new file mode 100644
index 0000000..0893499
--- /dev/null
+++ b/Engine/SharpEngine.Core/Shaders/LightingShader.cs
@@ -0,0 +1,53 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Windowing;
+using Silk.NET.OpenGL;
+
+namespace SharpEngine.Core.Shaders;
+
+internal class LightingShader : ShaderBase
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public LightingShader()
+ {
+ Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.FragmentShader, "lighting").Initialize();
+
+ Vao = Window.GL.GenVertexArray();
+ Window.GL.BindVertexArray(Vao);
+
+ SetAttributes();
+ }
+
+ ///
+ public override bool SetAttributes()
+ {
+ if (!base.SetAttributes())
+ return false;
+
+ /*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/ShaderBase.cs b/Engine/SharpEngine.Core/Shaders/ShaderBase.cs
similarity index 80%
rename from Core/Shaders/ShaderBase.cs
rename to Engine/SharpEngine.Core/Shaders/ShaderBase.cs
index 2eac3ae..e7fd389 100644
--- a/Core/Shaders/ShaderBase.cs
+++ b/Engine/SharpEngine.Core/Shaders/ShaderBase.cs
@@ -1,4 +1,6 @@
-using System;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Shared;
+using System;
namespace SharpEngine.Core.Shaders;
@@ -23,7 +25,7 @@ public virtual bool SetAttributes()
{
if (Shader is null)
{
- Console.WriteLine("Unable to set shader attributes, shader not found.");
+ Debug.Log.Error("Unable to set shader attributes, shader not found.");
return false;
}
diff --git a/Core/Shaders/ShaderService.cs b/Engine/SharpEngine.Core/Shaders/ShaderService.cs
similarity index 86%
rename from Core/Shaders/ShaderService.cs
rename to Engine/SharpEngine.Core/Shaders/ShaderService.cs
index db685b3..ee8f516 100644
--- a/Core/Shaders/ShaderService.cs
+++ b/Engine/SharpEngine.Core/Shaders/ShaderService.cs
@@ -1,7 +1,9 @@
-using System;
-using System.IO;
+using System.IO;
using System.Collections.Generic;
+using SharpEngine.Core.Windowing;
+using SharpEngine.Shared;
+
namespace SharpEngine.Core.Shaders;
///
@@ -14,8 +16,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 +35,7 @@ private ShaderService() { }
public List GetAll()
{
HasShadersToLoad = false;
- return new(_shaderCache.Values);
+ return [.. _shaderCache.Values];
}
///
@@ -71,18 +72,18 @@ public Shader LoadShader(string vertPath, string fragPath, string name)
if (!File.Exists(vertPath))
{
- Console.WriteLine($"Vertex shader file not found: {vertPath}");
+ Debug.Log.Information("Vertex shader file not found: {VertPath}", vertPath);
throw new FileNotFoundException($"Vertex shader file not found: {vertPath}");
}
if (!File.Exists(fragPath))
{
- Console.WriteLine($"Fragment shader file not found: {fragPath}");
+ Debug.Log.Information("Fragment shader file not found: {FragPath}", fragPath);
throw new FileNotFoundException($"Fragment shader file not found: {fragPath}");
}
// Create a new shader instance and add it to the cache
- var shader = new Shader(vertPath, fragPath, name);
+ var shader = new Shader(Window.GL, vertPath, fragPath, name).Initialize();
_shaderCache[name] = shader;
HasShadersToLoad = true;
diff --git a/Engine/SharpEngine.Core/Shaders/UIShader.cs b/Engine/SharpEngine.Core/Shaders/UIShader.cs
new file mode 100644
index 0000000..37caf51
--- /dev/null
+++ b/Engine/SharpEngine.Core/Shaders/UIShader.cs
@@ -0,0 +1,48 @@
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Windowing;
+using SharpEngine.Core._Resources;
+
+using Silk.NET.OpenGL;
+
+namespace SharpEngine.Core.Shaders;
+
+internal class UIShader : ShaderBase
+{
+ ///
+ /// Initializes a new instance of .
+ ///
+ public UIShader()
+ {
+ Shader = new Shader(Window.GL, Default.UIVertexShader, Default.UIFragmentShader, nameof(UIShader)).Initialize();
+ }
+
+ ///
+ public override bool SetAttributes()
+ {
+ if (!base.SetAttributes())
+ return false;
+
+ 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/Engine/SharpEngine.Core/SharpEngine.Core.csproj b/Engine/SharpEngine.Core/SharpEngine.Core.csproj
new file mode 100644
index 0000000..4a179d4
--- /dev/null
+++ b/Engine/SharpEngine.Core/SharpEngine.Core.csproj
@@ -0,0 +1,62 @@
+
+
+
+ Everything needed to build games using a purely C# game engine.
+
+ AnyCPU;x64;x86
+ $(GenerateNuGetPackages)
+ https://github.com/Av3boy/SharpEngine
+ game-engine;
+ True
+
+
+ $(AssemblyName).NET
+
+
+ true
+
+ README.md
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+ True
+ \
+
+
+
+
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Textures/TextureService.cs b/Engine/SharpEngine.Core/Textures/TextureService.cs
new file mode 100644
index 0000000..4c40568
--- /dev/null
+++ b/Engine/SharpEngine.Core/Textures/TextureService.cs
@@ -0,0 +1,49 @@
+using System.IO;
+using System.Collections.Generic;
+
+using SharpEngine.Core.Windowing;
+using Texture = SharpEngine.Core.Components.Properties.Textures.Texture;
+using TextureType = Silk.NET.Assimp.TextureType;
+
+namespace SharpEngine.Core.Textures;
+
+///
+/// Represents a service for loading textures.
+///
+public class TextureService
+{
+ ///
+ /// Gets the singleton instance of .
+ ///
+ public static TextureService Instance { get; } = new TextureService();
+ private readonly Dictionary _textureCache = [];
+
+ // Private constructor to prevent instantiation
+ private TextureService() { }
+
+ ///
+ /// Loads a texture from the specified path.
+ ///
+ /// the full path to the texture.
+ /// The loaded texture program.
+ public Texture LoadTexture(string path, TextureType textureType = TextureType.Diffuse)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ throw new FileNotFoundException("No texture file provided.");
+
+ if (!File.Exists(path))
+ throw new FileNotFoundException($"Texture file not found from path '{path}'.");
+
+ // Check if the texture is already in the cache
+ if (_textureCache.TryGetValue(path, out var cachedTexture))
+ return cachedTexture;
+
+ // Generate handle
+ var texture = new Texture(Window.GL, path, textureType);
+
+ // Add it to the cache
+ _textureCache[path] = texture;
+
+ return texture;
+ }
+}
diff --git a/Engine/SharpEngine.Core/Windowing/Frame.cs b/Engine/SharpEngine.Core/Windowing/Frame.cs
new file mode 100644
index 0000000..9b43fbf
--- /dev/null
+++ b/Engine/SharpEngine.Core/Windowing/Frame.cs
@@ -0,0 +1,22 @@
+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/SilkWindow.cs b/Engine/SharpEngine.Core/Windowing/SilkWindow.cs
similarity index 64%
rename from Core/SilkWindow.cs
rename to Engine/SharpEngine.Core/Windowing/SilkWindow.cs
index 23989da..4301fa0 100644
--- a/Core/SilkWindow.cs
+++ b/Engine/SharpEngine.Core/Windowing/SilkWindow.cs
@@ -1,45 +1,69 @@
-using SharpEngine.Core.Enums;
using Silk.NET.Core;
using Silk.NET.Core.Contexts;
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.Windowing;
+using MouseButton = Silk.NET.Input.MouseButton;
+
using StbImageSharp;
+
using System;
using System.IO;
using System.Numerics;
-using MouseButton = Silk.NET.Input.MouseButton;
-namespace SharpEngine.Core;
+namespace SharpEngine.Core.Windowing;
///
-/// Represents a class abstraction for the interface.
+/// Represents an abstraction for the interface.
///
+///
+/// For window initialization, see .
+///
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 { 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
@@ -49,16 +73,28 @@ public string Title
}
///
- public WindowState WindowState { get; set; }
+ public WindowState WindowState
+ {
+ get => CurrentWindow.WindowState;
+ set => CurrentWindow.WindowState = value;
+ }
///
- 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;
@@ -79,25 +115,51 @@ public string Title
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 bool IsFocused { get; }
///
public VideoMode VideoMode => CurrentWindow.VideoMode;
@@ -154,6 +216,9 @@ public string Title
///
public event Action? Render;
+ /// Raised after the scene has been rendered.
+ public event Action? OnAfterRender;
+
/// An event executed when the window has loaded.
public event Action? OnLoaded;
@@ -173,12 +238,25 @@ public IWindow CurrentWindow
}
///
+ // [Obsolete("Use the 'Create' implementation instead.")]
public IWindow CreateWindow(WindowOptions opts)
{
+ if (CurrentWindow is not null)
+ throw new InvalidOperationException("Window already created for this instance.");
+
CurrentWindow = Silk.NET.Windowing.Window.Create(opts);
return CurrentWindow;
}
+ // public static T Create(WindowOptions opts) where T : SilkWindow, new()
+ // {
+ // var instance = new T();
+ // var window = instance.CreateWindow(opts);
+ //
+ // instance.CurrentWindow = window;
+ // return instance;
+ // }
+
///
protected virtual void Dispose(bool disposing) { }
@@ -205,16 +283,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();
@@ -259,16 +337,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(double deltaTime) { }
+ /// 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.
- protected virtual void PreRender(double deltaTime) { }
+ /// Contains information about the previous frame.
+ protected virtual void PreRender(Frame frame) { }
- // TODO: Scene unsaved changes warning.
///
/// Handles closing the application.
///
diff --git a/Core/Window.cs b/Engine/SharpEngine.Core/Windowing/Window.cs
similarity index 73%
rename from Core/Window.cs
rename to Engine/SharpEngine.Core/Windowing/Window.cs
index 29e9936..787d9e3 100644
--- a/Core/Window.cs
+++ b/Engine/SharpEngine.Core/Windowing/Window.cs
@@ -1,44 +1,49 @@
-using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Entities.Properties.Meshes;
using SharpEngine.Core.Entities.Views;
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;
-using Silk.NET.Core;
-using Silk.NET.GLFW;
+
using Silk.NET.Input;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ImGui;
using Silk.NET.Windowing;
-using StbImageSharp;
+using MouseButton = Silk.NET.Input.MouseButton;
+
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Numerics;
-using System.Runtime.InteropServices;
using System.Threading.Tasks;
-using MouseButton = Silk.NET.Input.MouseButton;
using Shader = SharpEngine.Core.Shaders.Shader;
+using Silk.NET.GLFW;
+using SharpEngine.Shared;
-namespace SharpEngine.Core;
+namespace SharpEngine.Core.Windowing;
///
/// Represents the game window.
///
public class Window : SilkWindow
{
- private readonly ISettings _settings;
- private readonly CameraView _camera;
-
private bool _initialized;
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.
public event Action? OnHandleMouse;
@@ -62,14 +67,14 @@ public class Window : SilkWindow
/// The OpenGL context.
public static GL GL;
- // TODO: Use this method.
+ // TODO: #93 Use this method.
///
/// Gets the current OpenGL context.
///
/// The OpenGL context for this window.
public static GL GetGL() => GL;
private static void SetGL(GL gl) => GL = gl;
-
+
///
/// Initializes a new instance of .
///
@@ -79,10 +84,9 @@ public class Window : SilkWindow
public Window(CameraView camera, Scene scene, IViewSettings settings)
{
Scene = scene;
- _settings = settings;
- _camera = camera;
-
- Initialize(settings.WindowOptions);
+ Settings = settings;
+ Camera = camera;
+ InitializeWindow();
}
///
@@ -93,17 +97,19 @@ public Window(CameraView camera, Scene scene, IViewSettings settings)
public Window(Scene scene, IViewSettings settings)
{
Scene = scene;
- _settings = settings;
- _camera = new(Vector3.One, new DefaultViewSettings());
+ Settings = settings;
+ Camera = new(Vector3.One, settings);
- Initialize(settings.WindowOptions);
+ InitializeWindow();
}
- private void Initialize(WindowOptions options)
+ private bool _windowInitialized;
+
+ private void InitializeWindow()
{
- CurrentWindow = CreateWindow(options);
- CurrentWindow.Update += OnUpdateFrame;
- CurrentWindow.Render += RenderFrame;
+ CurrentWindow = CreateWindow(Settings.WindowOptions);
+ CurrentWindow.Update += deltaTime => OnUpdateFrame(new Frame(deltaTime));
+ CurrentWindow.Render += deltaTime => RenderFrame(new Frame(deltaTime));
CurrentWindow.Resize += OnResize;
CurrentWindow.Load += OnLoad;
CurrentWindow.Closing += OnClosing;
@@ -112,6 +118,9 @@ private void Initialize(WindowOptions options)
///
public override void Run(Action onFrame)
{
+ if (!_windowInitialized)
+ throw new InvalidOperationException("Window not been initialized for this instance. Try calling 'window.Initialize()' first.");
+
try
{
base.Run(onFrame);
@@ -119,7 +128,7 @@ public override void Run(Action onFrame)
}
catch (Exception ex)
{
- Debug.LogInformation("Error running window: " + ex.Message, ex);
+ Debug.Log.Error(ex, "Error running window: {Message}", ex.Message);
}
}
@@ -140,11 +149,8 @@ public override void OnLoad()
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
- // TODO: Set cursor shape
- // CursorShape = CursorShape.Hand;
-
// Load all meshes from the mesh cache
- MeshService.Instance.LoadMesh("cube", Primitives.Cube.Mesh);
+ // MeshService.Instance.LoadMesh("cube", Primitives.Cube.Mesh);
// Using reflection, find all renderers that implement the RendererBase.
var rendererTypes = AppDomain.CurrentDomain.GetAssemblies()
@@ -154,8 +160,8 @@ public override void OnLoad()
foreach (var type in rendererTypes)
{
// 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 };
+ // TODO: #75 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, this, Settings, Scene };
var renderer = (RendererBase)Activator.CreateInstance(type, requiredArguments)!;
_renderers = _renderers.Append(renderer);
@@ -170,7 +176,7 @@ public override void OnLoad()
}
catch (Exception ex)
{
- Debug.LogInformation("Error loading window: " + ex.Message, ex);
+ Debug.Log.Information(ex, "Error loading window: {Message}", ex.Message);
}
base.OnLoad();
@@ -179,8 +185,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)
{
@@ -189,32 +195,29 @@ 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);
- ToggleWireFrame(_settings.UseWireFrame);
+ ToggleWireFrame(Settings.UseWireFrame);
UseShaders();
- var renderTasks = _renderers.Where(renderer => _settings.RendererFlags.HasFlag(renderer.RenderFlag))
+ var renderTasks = _renderers.Where(renderer => Settings.RendererFlags.HasFlag(renderer.RenderFlag))
.Select(renderer => renderer.Render())
.ToList();
Task.WaitAll([.. renderTasks]);
- // TODO: This call causes flickering in the new framework. Investigate why.
- // _window.SwapBuffers();
-
- AfterRender(deltaTime);
+ AfterRender(frame);
_imGuiController?.Render();
}
catch (Exception ex)
{
- Debug.LogInformation(ex.Message);
+ Debug.Log.Information(ex.Message);
}
}
@@ -236,25 +239,21 @@ private void UseShaders()
}
///
- protected void OnUpdateFrame(double deltaTime)
+ protected void OnUpdateFrame(Frame frame)
{
while (!_initialized)
{
// Wait for the window to be initialized.
}
- // TODO: GLFW_ISFOCUSED
- // if (!_window.IsFocused)
- // return;
+ if (Settings.PrintFrameRate)
+ Debug.Log.Information($"FPS: {frame.FrameRate}");
- if (_settings.PrintFrameRate)
- Console.WriteLine($"FPS: {1f / deltaTime}");
-
- // TODO: Handle multiple mice?
+ // TODO: #21 Handle multiple mice?
var mouse = Input?.Mice[0];
if (mouse is not null)
{
- _camera.UpdateMousePosition(mouse.Position);
+ Camera.UpdateMousePosition(mouse.Position);
OnHandleMouse?.Invoke(mouse);
}
@@ -264,11 +263,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
@@ -276,7 +275,7 @@ private void AssignInputEvents()
{
if (Input is null)
{
- Debug.LogInformation("Input is null. No input events will be assigned.");
+ Debug.Log.Information("Input is null. No input events will be assigned.");
return;
}
@@ -288,6 +287,9 @@ private void AssignInputEvents()
mouse.Scroll += OnMouseWheel;
mouse.Click += OnMouseClick;
mouse.MouseDown += OnMouseDown;
+
+ if (IsFocused)
+ mouse.Cursor.CursorMode = CursorMode.Raw;
}
}
@@ -309,14 +311,16 @@ protected void OnMouseWheel(IMouse mouse, ScrollWheel sw)
};
HandleMouseWheel?.Invoke(direction, sw);
- _camera.Fov -= sw.Y;
+ Camera.Fov -= sw.Y;
}
///
protected void OnResize(Vector2D size)
{
GL.Viewport(size);
- _camera.AspectRatio = size.X / size.Y;
+
+ if (size != Vector2D.Zero)
+ Camera.AspectRatio = (float)size.X / size.Y;
}
///
@@ -329,7 +333,7 @@ protected override void OnMouseDown(IMouse mouse, MouseButton button)
/// The contents of the new scene.
protected void SetScene(Scene scene)
{
- // TODO: Do we need to clear anything from e.g. the GPU when we change change the scene?
+ // TODO: #92 Do we need to clear anything from e.g. the GPU when we change change the scene?
Scene = scene;
}
}
diff --git a/Engine/SharpEngine.Core/_Resources/Default.cs b/Engine/SharpEngine.Core/_Resources/Default.cs
new file mode 100644
index 0000000..ef3e138
--- /dev/null
+++ b/Engine/SharpEngine.Core/_Resources/Default.cs
@@ -0,0 +1,23 @@
+using SharpEngine.Core.Extensions;
+
+namespace SharpEngine.Core._Resources;
+
+///
+/// Contains default resources used by the engine.
+///
+public static class Default
+{
+ /// Gets the path to the debug texture.
+ public static string DebugTexture => PathExtensions.GetAssemblyPath("_Resources\\Textures\\debug.JPG");
+
+ /// Gets the path to the default vertex shader.
+ public static string VertexShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\shader.vert");
+
+ /// Gets the path to the lighting fragment shader.
+ public static string FragmentShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\lighting.frag");
+
+ public static string LightShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\shader.frag");
+
+ public static string UIVertexShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\uiShader.vert");
+ public static string UIFragmentShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\uiShader.frag");
+}
diff --git a/Core/Shaders/Light/DirectionalLight.glsl b/Engine/SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl
similarity index 100%
rename from Core/Shaders/Light/DirectionalLight.glsl
rename to Engine/SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl
diff --git a/Core/Shaders/Light/PointLight.glsl b/Engine/SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl
similarity index 100%
rename from Core/Shaders/Light/PointLight.glsl
rename to Engine/SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl
diff --git a/Core/Shaders/Light/SpotLight.glsl b/Engine/SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl
similarity index 100%
rename from Core/Shaders/Light/SpotLight.glsl
rename to Engine/SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl
diff --git a/Core/Shaders/Material.glsl b/Engine/SharpEngine.Core/_Resources/Shaders/Material.glsl
similarity index 100%
rename from Core/Shaders/Material.glsl
rename to Engine/SharpEngine.Core/_Resources/Shaders/Material.glsl
diff --git a/Core/Shaders/lighting.frag b/Engine/SharpEngine.Core/_Resources/Shaders/lighting.frag
similarity index 84%
rename from Core/Shaders/lighting.frag
rename to Engine/SharpEngine.Core/_Resources/Shaders/lighting.frag
index 8e2354d..b725244 100644
--- a/Core/Shaders/lighting.frag
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/lighting.frag
@@ -1,3 +1,5 @@
+// Represents a basic forward rendered lighting shader.
+
#version 330 core
#include "Material.glsl"
@@ -23,15 +25,18 @@ uniform SpotLight spotLight;
void main()
{
+
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
-
+
vec3 result = CalcDirLight(dirLight, norm, viewDir);
-
+
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
-
+
result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
-
+
FragColor = vec4(result, 1.0);
+
+ // FragColor = vec4(1.0); // set all 4 vector values to 1.0
}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/shader.frag b/Engine/SharpEngine.Core/_Resources/Shaders/shader.frag
new file mode 100644
index 0000000..c35339c
--- /dev/null
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/shader.frag
@@ -0,0 +1,14 @@
+#version 330 core
+in vec2 fUv;
+
+
+uniform sampler2D uTexture0;
+
+out vec4 FragColor;
+
+void main()
+{
+ // FragColor = vec4(1.0); // set all 4 vector values to 1.0
+ FragColor = texture(uTexture0, fUv);
+
+}
\ No newline at end of file
diff --git a/Core/Shaders/shader.vert b/Engine/SharpEngine.Core/_Resources/Shaders/shader.vert
similarity index 50%
rename from Core/Shaders/shader.vert
rename to Engine/SharpEngine.Core/_Resources/Shaders/shader.vert
index af12da3..b2f7b4a 100644
--- a/Core/Shaders/shader.vert
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/shader.vert
@@ -3,9 +3,9 @@ layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
-uniform mat4 model;
-uniform mat4 view;
-uniform mat4 projection;
+uniform mat4 uModel;
+uniform mat4 uView;
+uniform mat4 uProjection;
out vec3 Normal;
out vec3 FragPos;
@@ -13,8 +13,8 @@ out vec2 TexCoords;
void main()
{
- gl_Position = vec4(aPos, 1.0) * model * view * projection;
- FragPos = vec3(vec4(aPos, 1.0) * model);
- Normal = aNormal * mat3(transpose(inverse(model)));
+ gl_Position = vec4(aPos, 1.0) * uModel * uView * uProjection;
+ FragPos = vec3(vec4(aPos, 1.0) * uModel);
+ Normal = aNormal * mat3(transpose(inverse(uModel)));
TexCoords = aTexCoords;
}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/shader2.vert b/Engine/SharpEngine.Core/_Resources/Shaders/shader2.vert
new file mode 100644
index 0000000..fc46c04
--- /dev/null
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/shader2.vert
@@ -0,0 +1,16 @@
+#version 330 core
+layout (location = 0) in vec3 vPos;
+layout (location = 1) in vec2 vUv;
+
+uniform mat4 uModel;
+uniform mat4 uView;
+uniform mat4 uProjection;
+
+out vec2 fUv;
+
+void main()
+{
+ //Multiplying our uniform with the vertex position, the multiplication order here does matter.
+ gl_Position = uProjection * uView * uModel * vec4(vPos, 1.0);
+ fUv = vUv;
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.frag b/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.frag
new file mode 100644
index 0000000..1cc0a9e
--- /dev/null
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.frag
@@ -0,0 +1,10 @@
+#version 330 core
+
+uniform sampler2D texture1;
+out vec4 FragColor;
+in vec2 TexCoords;
+
+void main()
+{
+ FragColor = texture(texture1, TexCoords);
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.vert b/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.vert
new file mode 100644
index 0000000..7787de5
--- /dev/null
+++ b/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.vert
@@ -0,0 +1,58 @@
+#version 330 core
+
+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;
+uniform mat4 orthoMatrix;
+
+uniform mat4 uModel;
+
+out vec3 Normal;
+out vec3 FragPos;
+out vec2 TexCoords;
+
+void main()
+{
+ float reciprScaleOnscreen = 0.005f;
+
+ // 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
+ );
+
+ // 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 * uModel;
+
+ // Apply the MVP matrix to the scaled vertex position with aspect ratio
+ gl_Position = mvp * vec4(
+ (scaledPos.x + (position.x * reciprScaleOnscreen)) * aspectRatio,
+ scaledPos.y + (position.y * reciprScaleOnscreen),
+ scaledPos.z,
+ 1
+ );
+
+ // Pass through other attributes
+ Normal = mat3(uModel) * aNormal; // Transform the normal
+ FragPos = vec3(uModel * vec4(aPos, 1.0)); // Transform the fragment position
+ TexCoords = aTexCoords;
+}
\ No newline at end of file
diff --git a/Core/Textures/DefaultTextures/debug.jpg b/Engine/SharpEngine.Core/_Resources/Textures/debug.jpg
similarity index 100%
rename from Core/Textures/DefaultTextures/debug.jpg
rename to Engine/SharpEngine.Core/_Resources/Textures/debug.jpg
diff --git a/Resources/icon.png b/Engine/SharpEngine.Core/_Resources/icon.png
similarity index 100%
rename from Resources/icon.png
rename to Engine/SharpEngine.Core/_Resources/icon.png
diff --git a/Engine/SharpEngine.Rest/Clients/AssetStoreClient.cs b/Engine/SharpEngine.Rest/Clients/AssetStoreClient.cs
new file mode 100644
index 0000000..e209d12
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Clients/AssetStoreClient.cs
@@ -0,0 +1,12 @@
+using SharpEngine.Rest.Urls;
+using SharpEngine.Shared.Dto.AssetStore;
+
+namespace SharpEngine.Rest.Clients;
+
+public class AssetStoreClient : RestClient, IAssetStoreClient
+{
+ public AssetStoreClient(HttpClient httpClient) : base(httpClient) { }
+
+ public async Task GetAssetAsync(Guid assetId)
+ => await GetAsync(AssetStore.GetById(assetId));
+}
diff --git a/Engine/SharpEngine.Rest/Clients/IAssetStoreClient.cs b/Engine/SharpEngine.Rest/Clients/IAssetStoreClient.cs
new file mode 100644
index 0000000..b99147a
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Clients/IAssetStoreClient.cs
@@ -0,0 +1,8 @@
+using SharpEngine.Shared.Dto.AssetStore;
+
+namespace SharpEngine.Rest.Clients;
+
+public interface IAssetStoreClient
+{
+ Task GetAssetAsync(Guid assetId);
+}
diff --git a/Engine/SharpEngine.Rest/Clients/IPortalClient.cs b/Engine/SharpEngine.Rest/Clients/IPortalClient.cs
new file mode 100644
index 0000000..55de6a5
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Clients/IPortalClient.cs
@@ -0,0 +1,5 @@
+namespace SharpEngine.Rest.Clients;
+
+internal interface IPortalClient
+{
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Rest/Clients/PortalClient.cs b/Engine/SharpEngine.Rest/Clients/PortalClient.cs
new file mode 100644
index 0000000..f8213f6
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Clients/PortalClient.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpEngine.Rest.Clients;
+
+internal class PortalClient : RestClient, IPortalClient
+{
+ public PortalClient(HttpClient httpClient) : base(httpClient) { }
+}
diff --git a/Engine/SharpEngine.Rest/Clients/RestClient.cs b/Engine/SharpEngine.Rest/Clients/RestClient.cs
new file mode 100644
index 0000000..3e96916
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Clients/RestClient.cs
@@ -0,0 +1,28 @@
+using SharpEngine.Rest.Urls;
+using System.Diagnostics;
+using System.Text.Json;
+
+namespace SharpEngine.Rest.Clients;
+
+public abstract class RestClient
+{
+ private readonly HttpClient _httpClient;
+ public RestClient(HttpClient httpClient)
+ {
+ _httpClient = httpClient;
+ _httpClient.BaseAddress = new Uri(AssetStore.BaseUrl);
+ }
+
+ public async Task GetAsync(string url)
+ {
+ var response = await _httpClient.GetAsync(url);
+ response.EnsureSuccessStatusCode();
+
+ var json = await response.Content.ReadAsStringAsync();
+
+ var result = JsonSerializer.Deserialize(json);
+ Debug.Assert(result is not null, "Deserialized result is null.");
+
+ return result;
+ }
+}
diff --git a/Engine/SharpEngine.Rest/SharpEngine.Rest.csproj b/Engine/SharpEngine.Rest/SharpEngine.Rest.csproj
new file mode 100644
index 0000000..d6ab7f9
--- /dev/null
+++ b/Engine/SharpEngine.Rest/SharpEngine.Rest.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Engine/SharpEngine.Rest/Urls/AssetStore.cs b/Engine/SharpEngine.Rest/Urls/AssetStore.cs
new file mode 100644
index 0000000..e369a96
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Urls/AssetStore.cs
@@ -0,0 +1,7 @@
+namespace SharpEngine.Rest.Urls;
+
+public static class AssetStore
+{
+ public const string BaseUrl = "https://assetstore.sharpengine.com/";
+ public static string GetById(Guid assetId) => $"assets/{assetId}";
+}
diff --git a/Engine/SharpEngine.Rest/Urls/Portal.cs b/Engine/SharpEngine.Rest/Urls/Portal.cs
new file mode 100644
index 0000000..749348f
--- /dev/null
+++ b/Engine/SharpEngine.Rest/Urls/Portal.cs
@@ -0,0 +1,6 @@
+namespace SharpEngine.Rest.Urls;
+
+public static class Portal
+{
+ public const string BaseUrl = "https://portal.sharpengine.com/";
+}
diff --git a/Engine/SharpEngine.Shared/AssetType.cs b/Engine/SharpEngine.Shared/AssetType.cs
new file mode 100644
index 0000000..e460210
--- /dev/null
+++ b/Engine/SharpEngine.Shared/AssetType.cs
@@ -0,0 +1,8 @@
+namespace SharpEngine.Shared;
+
+public enum AssetType
+{
+ Shader,
+ Model,
+ Texture,
+}
diff --git a/Engine/SharpEngine.Shared/Attributes/FilterAttribute.cs b/Engine/SharpEngine.Shared/Attributes/FilterAttribute.cs
new file mode 100644
index 0000000..f37af87
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Attributes/FilterAttribute.cs
@@ -0,0 +1,20 @@
+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/Core/Attributes/InspectorAttribute.cs b/Engine/SharpEngine.Shared/Attributes/InspectorAttribute.cs
similarity index 100%
rename from Core/Attributes/InspectorAttribute.cs
rename to Engine/SharpEngine.Shared/Attributes/InspectorAttribute.cs
diff --git a/Engine/SharpEngine.Shared/Debug.cs b/Engine/SharpEngine.Shared/Debug.cs
new file mode 100644
index 0000000..07fa37d
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Debug.cs
@@ -0,0 +1,32 @@
+using Serilog;
+using Serilog.Events;
+using System;
+
+namespace SharpEngine.Shared;
+
+///
+/// Contains methods for debugging the application.
+///
+public static class Debug
+{
+ public static ILogger Log;
+
+ private static LogEventLevel _logLevel = LogEventLevel.Information;
+
+ static Debug()
+ {
+ SetLogger();
+ }
+
+ public static void SetLogger()
+ => Log = new LoggerConfiguration()
+ .MinimumLevel.Is(_logLevel)
+ .WriteTo.Console()
+ .CreateLogger();
+
+ public static void SetLogLevel(LogEventLevel logLevel)
+ {
+ _logLevel = logLevel;
+ SetLogger();
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Shared/Dto/AssetStore/AssetDto.cs b/Engine/SharpEngine.Shared/Dto/AssetStore/AssetDto.cs
new file mode 100644
index 0000000..1acf076
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Dto/AssetStore/AssetDto.cs
@@ -0,0 +1,33 @@
+using SharpEngine.Shared.Dto.Primitives;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace SharpEngine.Shared.Dto.AssetStore;
+
+public class AssetDto
+{
+ public AssetId Id { get; init; }
+ public string Name { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public decimal Price { get; set; }
+ public DateTime CreatedAt { get; init; }
+ public DateTime LastUpdatedAt { get; init; }
+ public IReadOnlyList Tags { get; set; } = [];
+ public IReadOnlyList Comments { get; set; } = [];
+ public UserId AuthorId { get; init; }
+ public bool Tombstoned { get; set; }
+ public Version Version { get; set; }
+
+
+ // (e.g., model, texture, sound, etc.)
+ // public AssetType
+
+ public required string BlobUri { get; set; }
+ public required string FileSize { get; set; }
+
+ public required string Checksum { get; set; }
+}
diff --git a/Engine/SharpEngine.Shared/Dto/AssetStore/CommentDto.cs b/Engine/SharpEngine.Shared/Dto/AssetStore/CommentDto.cs
new file mode 100644
index 0000000..053abeb
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Dto/AssetStore/CommentDto.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SharpEngine.Shared.Dto.AssetStore;
+
+public class CommentDto
+{
+}
diff --git a/Engine/SharpEngine.Shared/Dto/Primitives/AssetId.cs b/Engine/SharpEngine.Shared/Dto/Primitives/AssetId.cs
new file mode 100644
index 0000000..eada9db
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Dto/Primitives/AssetId.cs
@@ -0,0 +1,8 @@
+namespace SharpEngine.Shared.Dto.Primitives;
+
+public readonly record struct AssetId(Guid Value)
+{
+ public static AssetId New() => new(Guid.NewGuid());
+ public static implicit operator Guid(AssetId id) => id.Value;
+ public static implicit operator AssetId(Guid value) => new(value);
+}
diff --git a/Engine/SharpEngine.Shared/Dto/Primitives/CommentId.cs b/Engine/SharpEngine.Shared/Dto/Primitives/CommentId.cs
new file mode 100644
index 0000000..1ec8ef4
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Dto/Primitives/CommentId.cs
@@ -0,0 +1,8 @@
+namespace SharpEngine.Shared.Dto.Primitives;
+
+public readonly record struct CommentId(Guid Value)
+{
+ public static CommentId New() => new(Guid.NewGuid());
+ public static implicit operator Guid(CommentId id) => id.Value;
+ public static implicit operator CommentId(Guid value) => new(value);
+}
diff --git a/Engine/SharpEngine.Shared/Dto/Primitives/UserId.cs b/Engine/SharpEngine.Shared/Dto/Primitives/UserId.cs
new file mode 100644
index 0000000..d0fb0c2
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Dto/Primitives/UserId.cs
@@ -0,0 +1,8 @@
+namespace SharpEngine.Shared.Dto.Primitives;
+
+public readonly record struct UserId(Guid Value)
+{
+ public static UserId New() => new(Guid.NewGuid());
+ public static implicit operator Guid(UserId id) => id.Value;
+ public static implicit operator UserId(Guid value) => new(value);
+}
diff --git a/SharpEngine.Shared/Project.cs b/Engine/SharpEngine.Shared/Dto/Project.cs
similarity index 85%
rename from SharpEngine.Shared/Project.cs
rename to Engine/SharpEngine.Shared/Dto/Project.cs
index 5cac973..cf6b427 100644
--- a/SharpEngine.Shared/Project.cs
+++ b/Engine/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/Engine/SharpEngine.Shared/Dto/UserProfile.cs b/Engine/SharpEngine.Shared/Dto/UserProfile.cs
new file mode 100644
index 0000000..b7a2f8b
--- /dev/null
+++ b/Engine/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: #60 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;
+}
diff --git a/Engine/SharpEngine.Shared/Enums/FilterMode.cs b/Engine/SharpEngine.Shared/Enums/FilterMode.cs
new file mode 100644
index 0000000..71d1da1
--- /dev/null
+++ b/Engine/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/SharpEngine.Shared/EnvironmentVariables.cs b/Engine/SharpEngine.Shared/EnvironmentVariables.cs
similarity index 100%
rename from SharpEngine.Shared/EnvironmentVariables.cs
rename to Engine/SharpEngine.Shared/EnvironmentVariables.cs
diff --git a/Engine/SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs b/Engine/SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs
new file mode 100644
index 0000000..d6f93d6
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Extensions/OrderedEnumerableExtensions.cs
@@ -0,0 +1,39 @@
+using Launcher.UI;
+using SharpEngine.Shared.Dto;
+using SharpEngine.Shared.Enums;
+using SharpEngine.Shared.Extensions;
+
+///
+/// Handles ordering collections.
+///
+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/SharpEngine.Shared/ProcessExtensions.cs b/Engine/SharpEngine.Shared/Extensions/ProcessExtensions.cs
similarity index 97%
rename from SharpEngine.Shared/ProcessExtensions.cs
rename to Engine/SharpEngine.Shared/Extensions/ProcessExtensions.cs
index 00cb0bc..f8d2a36 100644
--- a/SharpEngine.Shared/ProcessExtensions.cs
+++ b/Engine/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/Engine/SharpEngine.Shared/Extensions/ReflectionExtensions.cs b/Engine/SharpEngine.Shared/Extensions/ReflectionExtensions.cs
new file mode 100644
index 0000000..a663bf7
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Extensions/ReflectionExtensions.cs
@@ -0,0 +1,23 @@
+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);
+ if (property == null)
+ return null;
+
+ return property.GetValue(obj);
+ }
+}
diff --git a/Engine/SharpEngine.Shared/Extensions/StringExtensions.cs b/Engine/SharpEngine.Shared/Extensions/StringExtensions.cs
new file mode 100644
index 0000000..f9d5e36
--- /dev/null
+++ b/Engine/SharpEngine.Shared/Extensions/StringExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Globalization;
+
+namespace ObjLoader.Loader.Common
+{
+ public static class StringExtensions
+ {
+ public static float ParseInvariantFloat(this string floatString)
+ {
+ return float.Parse(floatString, CultureInfo.InvariantCulture.NumberFormat);
+ }
+
+ public static int ParseInvariantInt(this string intString)
+ {
+ return int.Parse(intString, CultureInfo.InvariantCulture.NumberFormat);
+ }
+
+ public static bool EqualsOrdinalIgnoreCase(this string str, string s)
+ {
+ return str.Equals(s, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool IsNullOrEmpty(this string str)
+ {
+ return string.IsNullOrEmpty(str);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Math.cs b/Engine/SharpEngine.Shared/Math.cs
similarity index 92%
rename from Core/Math.cs
rename to Engine/SharpEngine.Shared/Math.cs
index 5006ec2..7b74cad 100644
--- a/Core/Math.cs
+++ b/Engine/SharpEngine.Shared/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;
@@ -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/Engine/SharpEngine.Shared/NaturalStringComparer.cs b/Engine/SharpEngine.Shared/NaturalStringComparer.cs
new file mode 100644
index 0000000..50b10b7
--- /dev/null
+++ b/Engine/SharpEngine.Shared/NaturalStringComparer.cs
@@ -0,0 +1,73 @@
+using System.Globalization;
+
+namespace Launcher.UI;
+
+///
+/// Checks if two strings are equal in a natural way.
+///
+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 static 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/Engine/SharpEngine.Shared/SharpEngine.Shared.csproj b/Engine/SharpEngine.Shared/SharpEngine.Shared.csproj
new file mode 100644
index 0000000..e0034eb
--- /dev/null
+++ b/Engine/SharpEngine.Shared/SharpEngine.Shared.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/Engine/SharpEngine.sln b/Engine/SharpEngine.sln
new file mode 100644
index 0000000..c51bee3
--- /dev/null
+++ b/Engine/SharpEngine.sln
@@ -0,0 +1,175 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.35906.104
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Editor", "Editor\SharpEngine.Editor.csproj", "{F72A74DC-54F9-0C76-2D40-32D2524683F8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Launcher", "Launcher\Launcher.csproj", "{3751B661-BE0E-9CD7-CA51-361A34450372}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Shared", "SharpEngine.Shared\SharpEngine.Shared.csproj", "{F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minecraft", "Examples\MinecraftClone\Minecraft.csproj", "{E8A75C3B-1D82-8B30-C854-AD35FB123CDC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minimal", "Examples\Minimal\Minimal.csproj", "{1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{089100B1-113F-4E66-888A-E83F3999EAFD}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ Directory.Build.props = Directory.Build.props
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultipleWindows", "Examples\MultipleWindows\MultipleWindows.csproj", "{0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+ ProjectSection(SolutionItems) = preProject
+ Test.Directory.Build.props = Test.Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Core.Tests", "Tests\SharpEngine.Core.Tests\SharpEngine.Core.Tests.csproj", "{60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Shared.Tests", "Tests\SharpEngine.Shared.Tests\SharpEngine.Shared.Tests.csproj", "{385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Editor.Tests", "Tests\SharpEngine.Editor.Tests\SharpEngine.Editor.Tests.csproj", "{D374B8A7-51F6-4688-BD99-74EB0E159079}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Core.Numerics", "SharpEngine.Core.Numerics\SharpEngine.Core.Numerics.csproj", "{C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Core.Components", "SharpEngine.Core.Components\SharpEngine.Core.Components.csproj", "{94482B3A-4230-45C3-A899-689DA882942E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Core", "SharpEngine.Core\SharpEngine.Core.csproj", "{52043A27-E9FF-3216-A026-E241F706D49B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Core.ObjLoader", "ObjLoader\SharpEngine.Core.ObjLoader.csproj", "{9D23A8B2-C143-B170-10B8-069D9FA119FD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjLoader.Tests", "Tests\ObjLoader.Test\ObjLoader.Tests.csproj", "{1807AD4A-C310-4BD8-9542-07D3D3EAE617}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tutorial 4.1 - Model Loading", "Examples\Tutorial 4.1 - Model Loading\Tutorial 4.1 - Model Loading.csproj", "{17243B7C-E72B-6C26-FCFC-3D194F29B0AF}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpEngine.Rest", "SharpEngine.Rest\SharpEngine.Rest.csproj", "{FE7C7460-6599-4BF8-A756-0C04315D3144}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ LauncherNonWindows|Any CPU = LauncherNonWindows|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.LauncherNonWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.LauncherNonWindows|Any CPU.Build.0 = Debug|Any CPU
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F72A74DC-54F9-0C76-2D40-32D2524683F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.LauncherNonWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.LauncherNonWindows|Any CPU.Build.0 = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.LauncherNonWindows|Any CPU.Deploy.0 = Debug|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3751B661-BE0E-9CD7-CA51-361A34450372}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0BEECF6-BA81-40E2-AA60-FC5B1539FC65}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.LauncherNonWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.LauncherNonWindows|Any CPU.Build.0 = Debug|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.LauncherNonWindows|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.LauncherNonWindows|Any CPU.Build.0 = Debug|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D374B8A7-51F6-4688-BD99-74EB0E159079}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5A54BE1-00E6-43F5-B0DB-41C6C1F47DDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {94482B3A-4230-45C3-A899-689DA882942E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.LauncherNonWindows|Any CPU.ActiveCfg = LauncherNonWindows|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.LauncherNonWindows|Any CPU.Build.0 = LauncherNonWindows|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {52043A27-E9FF-3216-A026-E241F706D49B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9D23A8B2-C143-B170-10B8-069D9FA119FD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617}.Release|Any CPU.Build.0 = Release|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.LauncherNonWindows|Any CPU.ActiveCfg = Release|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.LauncherNonWindows|Any CPU.Build.0 = Release|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE7C7460-6599-4BF8-A756-0C04315D3144}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E8A75C3B-1D82-8B30-C854-AD35FB123CDC} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ {1BEC06D8-094D-8BC2-288E-E2C4C13F3D2E} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ {0AF16D15-5BD2-4FED-9C6A-227DE6A56C5E} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ {60B90F0B-14B8-4FA5-8482-B09EDEC46BF8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {385CCB8D-CA17-4B58-8DA6-D3E9210CE49B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {D374B8A7-51F6-4688-BD99-74EB0E159079} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {1807AD4A-C310-4BD8-9542-07D3D3EAE617} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
+ {17243B7C-E72B-6C26-FCFC-3D194F29B0AF} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AA03EC4B-4E7B-4B73-BEA4-60C539332773}
+ EndGlobalSection
+EndGlobal
diff --git a/SharpEngine.slnx b/Engine/SharpEngine.slnx
similarity index 100%
rename from SharpEngine.slnx
rename to Engine/SharpEngine.slnx
diff --git a/Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs b/Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs
new file mode 100644
index 0000000..39c13c4
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs
@@ -0,0 +1,148 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using NUnit.Framework;
+using ObjLoader.Loader.Data;
+using System.Linq;
+using FluentAssertions;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Components.Properties;
+using ObjLoader.Loaders.MaterialLoader;
+
+namespace ObjLoader.Test.Loaders
+{
+ [TestFixture]
+ public class MaterialLibraryLoaderTests
+ {
+ private MaterialLibrarySpy _materialLibrarySpy;
+ private Material _firstMaterial;
+ private Material _secondMaterial;
+ private const float Epsilon = 0.000001f;
+
+ private MaterialLibraryLoader _materialLibraryLoader;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _materialLibrarySpy = new MaterialLibrarySpy();
+ // _materialLibraryLoader = new MaterialLibraryLoader(_materialLibrarySpy);
+ }
+
+ [Test]
+ public void Adds_correct_materials()
+ {
+ LoadMaterial();
+
+ _materialLibrarySpy.Materials.Count().Should().Be(2);
+ _firstMaterial.Name.Should().BeEquivalentTo("wire_134110008");
+ _secondMaterial.Name.Should().BeEquivalentTo("second_material");
+ }
+
+ [Test]
+ public void Sets_correct_ambient_color()
+ {
+ LoadMaterial();
+
+ _firstMaterial.AmbientColor.X.Should().BeApproximately(1, Epsilon);
+ _firstMaterial.AmbientColor.Y.Should().BeApproximately(2, Epsilon);
+ _firstMaterial.AmbientColor.Z.Should().BeApproximately(3, Epsilon);
+ }
+
+ [Test]
+ public void Sets_correct_diffuse_color()
+ {
+ LoadMaterial();
+
+ _firstMaterial.DiffuseColor.X.Should().BeApproximately(0.5255f, Epsilon);
+ _firstMaterial.DiffuseColor.Y.Should().BeApproximately(0.4314f, Epsilon);
+ _firstMaterial.DiffuseColor.Z.Should().BeApproximately(0.0314f, Epsilon);
+ }
+
+ [Test]
+ public void Sets_correct_specular()
+ {
+ LoadMaterial();
+
+ _firstMaterial.SpecularColor.X.Should().BeApproximately(0.3500f, Epsilon);
+ _firstMaterial.SpecularColor.Y.Should().BeApproximately(0.3600f, Epsilon);
+ _firstMaterial.SpecularColor.Z.Should().BeApproximately(0.3700f, Epsilon);
+
+ _firstMaterial.SpecularCoefficient.Should().BeApproximately(32, Epsilon);
+ }
+
+ [Test]
+ public void Sets_correct_transparency()
+ {
+ LoadMaterial();
+
+ _firstMaterial.Transparency.Should().BeApproximately(0.5f, Epsilon);
+ }
+
+ [Test]
+ public void Sets_correct_illumination_model()
+ {
+ LoadMaterial();
+
+ _firstMaterial.IlluminationModel.Should().Be(2);
+ }
+
+ [Test]
+ public void Sets_correct_texure_maps()
+ {
+ LoadMaterial();
+
+ _firstMaterial.AmbientTextureMap.Should().BeEquivalentTo("lenna1.tga");
+ _firstMaterial.DiffuseTextureMap.Should().BeEquivalentTo("lenna2.tga");
+ _firstMaterial.SpecularTextureMap.Should().BeEquivalentTo("lenna3.tga");
+ _firstMaterial.SpecularHighlightTextureMap.Should().BeEquivalentTo("lenna_spec.tga");
+ _firstMaterial.AlphaTextureMap.Should().BeEquivalentTo("lenna_alpha.tga");
+ _firstMaterial.BumpMap.Should().BeEquivalentTo("lenna_bump.tga");
+ _firstMaterial.DisplacementMap.Should().BeEquivalentTo("lenna_disp.tga");
+ _firstMaterial.StencilDecalMap.Should().BeEquivalentTo("lenna_stencil.tga");
+ }
+
+ private void LoadMaterial()
+ {
+ var data = Encoding.ASCII.GetBytes(MaterialLibrary);
+ var materialStream = new MemoryStream(data);
+
+ _materialLibraryLoader.Load(materialStream);
+ _firstMaterial = _materialLibrarySpy.Materials.First();
+ _secondMaterial = _materialLibrarySpy.Materials.ElementAt(1);
+ }
+
+ private const string MaterialLibrary =
+ "newmtl wire_134110008\r\n" +
+"\tNs\t32\r\n" +
+@"Tr 0.5
+illum 2
+Ka 1.0000 2.0000 3.0000
+Kd 0.5255 0.4314 0.0314
+Ks 0.3500 0.3600 0.3700
+map_Ka lenna1.tga
+map_Kd lenna2.tga
+map_Ks lenna3.tga
+map_Ns lenna_spec.tga
+map_d lenna_alpha.tga
+map_bump lenna_bump.tga
+disp lenna_disp.tga
+decal lenna_stencil.tga
+newmtl second_material";
+
+ private class MaterialLibrarySpy : IMaterialLibrary
+ {
+ public List Materials { get; private set; }
+
+ public MaterialLibrarySpy()
+ {
+ Materials = new List();
+ }
+
+ public void Push(Material material)
+ {
+ Materials.Add(material);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs b/Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs
new file mode 100644
index 0000000..8e4190a
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs
@@ -0,0 +1,190 @@
+using System.IO;
+using System.Text;
+using System.Linq;
+using NUnit.Framework;
+using ObjLoader.Loader.Loaders;
+using ObjLoader.Loader.TypeParsers;
+using FluentAssertions;
+using ObjLoader.Loaders.MaterialLoader;
+using SharpEngine.Core.Entities.Properties.Meshes;
+
+namespace ObjLoader.Test.Loaders
+{
+ [TestFixture]
+ public class ObjLoaderTests
+ {
+ private class MaterialStreamProviderSpy : IMaterialStreamProvider
+ {
+ public string RequestedMaterialFilePath { get; private set; }
+ public Stream StreamToReturn { get; set; }
+
+ public Stream Open(string materialFilePath)
+ {
+ RequestedMaterialFilePath = materialFilePath;
+ return StreamToReturn;
+ }
+ }
+
+ private ObjLoader.Loaders.ObjLoader.ObjLoader _loader;
+
+ private Mesh _loadResult;
+ private DataStore _textureDataStore;
+ private FaceParser _faceParser;
+ private GroupParser _groupParser;
+ private NormalParser _normalParser;
+ private TextureParser _textureParser;
+ private VertexParser _vertexParser;
+ private MaterialLibraryLoader _materialLibraryLoader;
+ private MaterialLibraryParser _materialLibraryParser;
+ private UseMaterialParser _useMaterialParser;
+ private MaterialLibraryLoaderFacade _materialLibraryLoaderFacade;
+ private MaterialStreamProviderSpy _materialStreamProviderSpy;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _textureDataStore = new DataStore();
+
+ _faceParser = new FaceParser(_textureDataStore);
+ _groupParser = new GroupParser(_textureDataStore);
+ _normalParser = new NormalParser(_textureDataStore);
+ _textureParser = new TextureParser(_textureDataStore);
+ _vertexParser = new VertexParser(_textureDataStore);
+ _materialStreamProviderSpy = new MaterialStreamProviderSpy();
+ _materialStreamProviderSpy.StreamToReturn = CreateMemoryStreamFromString(MaterialLibraryString);
+
+ _materialLibraryLoader = new MaterialLibraryLoader("", _textureDataStore);
+ _materialLibraryLoaderFacade = new MaterialLibraryLoaderFacade(_materialLibraryLoader);
+ // _materialLibraryParser = new MaterialLibraryParser(_materialLibraryLoaderFacade);
+ _useMaterialParser = new UseMaterialParser(_textureDataStore);
+
+ // _loader = new Loader.Loaders.ObjLoader(_textureDataStore, _faceParser, _groupParser, _normalParser, _textureParser, _vertexParser, _materialLibraryParser, _useMaterialParser);
+ }
+
+ [Test]
+ public void Loads_object_and_material_correctly()
+ {
+ Load();
+
+ _loadResult.Vertices.Should().HaveCount(8);
+ _loadResult.TextureCoordinates2.Should().HaveCount(14);
+ _loadResult.Normals.Should().HaveCount(8);
+ _loadResult.Materials.Should().HaveCount(1);
+
+ _materialStreamProviderSpy.RequestedMaterialFilePath.Should().BeEquivalentTo("cube.mtl");
+
+ _loadResult.Groups.Should().HaveCount(1);
+
+ var group = _loadResult.Groups.First();
+ group.Faces.Should().HaveCount(12);
+ group.Material.Name.Should().BeEquivalentTo("cube_material");
+ }
+
+ [Test]
+ public void Loads_object_correctly_when_material_is_not_found()
+ {
+ _materialStreamProviderSpy.StreamToReturn = null;
+ var materialLibraryLoaderFacade = new MaterialLibraryLoaderFacade(_materialLibraryLoader);
+ // var materialLibraryParser = new MaterialLibraryParser(materialLibraryLoaderFacade);
+
+ // _loader = new Loader.Loaders.ObjLoader(_textureDataStore, _faceParser, _groupParser, _normalParser, _textureParser, _vertexParser, materialLibraryParser, _useMaterialParser);
+
+ Load();
+
+ _loadResult.Vertices.Should().HaveCount(8);
+ _loadResult.TextureCoordinates2.Should().HaveCount(14);
+ _loadResult.Normals.Should().HaveCount(8);
+ _loadResult.Materials.Should().HaveCount(0);
+
+ _loadResult.Groups.Should().HaveCount(1);
+
+ var group = _loadResult.Groups.First();
+ group.Faces.Should().HaveCount(12);
+ group.Material.Should().BeNull();
+ }
+
+ private void Load()
+ {
+ var objectStream = CreateMemoryStreamFromString(ObjectFileString);
+
+ // _loadResult = _loader.Load(objectStream);
+ }
+
+ private Stream CreateMemoryStreamFromString(string str)
+ {
+ var data = Encoding.ASCII.GetBytes(str);
+ return new MemoryStream(data);
+ }
+
+ private const string ObjectFileString =
+"#Comment\r\n" +
+"mtllib\tcube.mtl\r\n" +
+@"v 1.000000 -1.000000 -1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+v -1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 -1.000000
+v 0.999999 1.000000 1.000001
+v -1.000000 1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+vt 0.748573 0.750412
+vt 0.749279 0.501284
+vt 0.999110 0.501077
+vt 0.999455 0.750380
+vt 0.250471 0.500702
+vt 0.249682 0.749677
+vt 0.001085 0.750380
+vt 0.001517 0.499994
+vt 0.499422 0.500239
+vt 0.500149 0.750166
+vt 0.748355 0.998230
+vt 0.500193 0.998728
+vt 0.498993 0.250415
+vt 0.748953 0.250920
+
+
+vn 0.000000 0.000000 -1.000000
+vn -1.000000 -0.000000 -0.000000
+vn -0.000000 -0.000000 1.000000
+vn -0.000001 0.000000 1.000000
+vn 1.000000 -0.000000 0.000000
+vn 1.000000 0.000000 0.000001
+vn 0.000000 1.000000 -0.000000
+vn -0.000000 -1.000000 0.000000
+usemtl cube_material
+s off
+f 5/1/1 1/2/1 4/3/1
+f 5/1/1 4/3/1 8/4/1
+f 3/5/2 7/6/2 8/7/2
+f 3/5/2 8/7/2 4/8/2
+f 2/9/3 6/10/3 3/5/3
+f 6/10/4 7/6/4 3/5/4
+f 1/2/5 5/1/5 2/9/5
+f 5/1/6 6/10/6 2/9/6
+f 5/1/7 8/11/7 6/10/7
+f 8/11/7 7/12/7 6/10/7
+f 1/2/8 2/9/8 3/13/8
+f 1/2/8 3/13/8 4/14/8";
+
+ private const string MaterialLibraryString =
+@"newmtl cube_material
+Ns 32
+Tr 0.5
+illum 2
+Ka 1.0000 2.0000 3.0000
+Kd 0.5255 0.4314 0.0314
+Ks 0.3500 0.3600 0.3700
+map_Ka lenna1.tga
+map_Kd lenna2.tga
+map_Ks lenna3.tga
+map_Ns lenna_spec.tga
+map_d lenna_alpha.tga
+map_bump lenna_bump.tga
+disp lenna_disp.tga
+decal lenna_stencil.tga";
+ }
+
+ internal interface IMaterialStreamProvider
+ {
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj b/Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj
new file mode 100644
index 0000000..7725988
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs
new file mode 100644
index 0000000..64ba447
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs
@@ -0,0 +1,147 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.Data;
+using ObjLoader.Loader.Data.Elements;
+using ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class FaceParserTests
+ {
+ private FaceGroupSpy _faceGroupSpy;
+ private FaceParser _faceParser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _faceGroupSpy = new FaceGroupSpy();
+
+ _faceParser = new FaceParser(_faceGroupSpy);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_face_line()
+ {
+ const string groupKeyword = "f";
+
+ bool canParse = _faceParser.CanParse(groupKeyword);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_normal_line()
+ {
+ const string invalidKeyword = "vt";
+
+ bool canParse = _faceParser.CanParse(invalidKeyword);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly_1()
+ {
+ const string faceLine = "1 2 3";
+ _faceParser.Parse(faceLine);
+
+ var parsedFace = _faceGroupSpy.ParsedFace;
+
+ parsedFace[0].VertexIndex.Should().Be(1);
+ parsedFace[0].TextureIndex.Should().Be(0);
+ parsedFace[0].NormalIndex.Should().Be(0);
+
+ parsedFace[1].VertexIndex.Should().Be(2);
+ parsedFace[1].TextureIndex.Should().Be(0);
+ parsedFace[1].NormalIndex.Should().Be(0);
+
+ parsedFace[2].VertexIndex.Should().Be(3);
+ parsedFace[2].TextureIndex.Should().Be(0);
+ parsedFace[2].NormalIndex.Should().Be(0);
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly_2()
+ {
+ const string faceLine = "3/1 4/2 5/3";
+ _faceParser.Parse(faceLine);
+
+ var parsedFace = _faceGroupSpy.ParsedFace;
+
+ parsedFace.Count.Should().Be(3);
+
+ parsedFace[0].VertexIndex.Should().Be(3);
+ parsedFace[0].TextureIndex.Should().Be(1);
+ parsedFace[0].NormalIndex.Should().Be(0);
+
+ parsedFace[1].VertexIndex.Should().Be(4);
+ parsedFace[1].TextureIndex.Should().Be(2);
+ parsedFace[1].NormalIndex.Should().Be(0);
+
+ parsedFace[2].VertexIndex.Should().Be(5);
+ parsedFace[2].TextureIndex.Should().Be(3);
+ parsedFace[2].NormalIndex.Should().Be(0);
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly_3()
+ {
+ const string faceLine = "6/4/1 3/5/3 7/6/5";
+ _faceParser.Parse(faceLine);
+
+ var parsedFace = _faceGroupSpy.ParsedFace;
+
+ parsedFace.Count.Should().Be(3);
+
+ parsedFace[0].VertexIndex.Should().Be(6);
+ parsedFace[0].TextureIndex.Should().Be(4);
+ parsedFace[0].NormalIndex.Should().Be(1);
+
+ parsedFace[1].VertexIndex.Should().Be(3);
+ parsedFace[1].TextureIndex.Should().Be(5);
+ parsedFace[1].NormalIndex.Should().Be(3);
+
+ parsedFace[2].VertexIndex.Should().Be(7);
+ parsedFace[2].TextureIndex.Should().Be(6);
+ parsedFace[2].NormalIndex.Should().Be(5);
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly_4()
+ {
+ const string faceLine = "6//1 3//3 7//5";
+ _faceParser.Parse(faceLine);
+
+ var parsedFace = _faceGroupSpy.ParsedFace;
+
+ parsedFace.Count.Should().Be(3);
+
+ parsedFace[0].VertexIndex.Should().Be(6);
+ parsedFace[0].TextureIndex.Should().Be(0);
+ parsedFace[0].NormalIndex.Should().Be(1);
+
+ parsedFace[1].VertexIndex.Should().Be(3);
+ parsedFace[1].TextureIndex.Should().Be(0);
+ parsedFace[1].NormalIndex.Should().Be(3);
+
+ parsedFace[2].VertexIndex.Should().Be(7);
+ parsedFace[2].TextureIndex.Should().Be(0);
+ parsedFace[2].NormalIndex.Should().Be(5);
+ }
+ }
+
+ public class FaceGroupSpy : IFaceGroup
+ {
+ public Face ParsedFace { get; private set; }
+
+ public Face GetFace(int i)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void AddFace(Face face)
+ {
+ ParsedFace = face;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs
new file mode 100644
index 0000000..97ea846
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs
@@ -0,0 +1,61 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.Data;
+using ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class GroupParserTests
+ {
+ private GroupDataStoreMock _groupDataStoreMock;
+ private GroupParser _groupParser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _groupDataStoreMock = new GroupDataStoreMock();
+
+ // _groupParser = new GroupParser(_groupDataStoreMock);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_normal_line()
+ {
+ const string groupKeyword = "g";
+
+ bool canParse = _groupParser.CanParse(groupKeyword);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_normal_line()
+ {
+ const string invalidKeyword = "vt";
+
+ bool canParse = _groupParser.CanParse(invalidKeyword);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly()
+ {
+ const string normalLine = "test group";
+ _groupParser.Parse(normalLine);
+
+ var parsedGroupName = _groupDataStoreMock.ParsedGroupName;
+ parsedGroupName.Should().Be("test group");
+ }
+ }
+
+ class GroupDataStoreMock : IGroupDataStore
+ {
+ public string ParsedGroupName { get; set; }
+
+ public void PushGroup(string groupName)
+ {
+ ParsedGroupName = groupName;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs
new file mode 100644
index 0000000..91089f1
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs
@@ -0,0 +1,58 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.TypeParsers;
+using ObjLoader.Loaders.MaterialLoader;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class MaterialLibraryParserTests
+ {
+ private MaterialLibraryLoaderFacadeSpy _materialLibraryLoaderFacadeSpy;
+ private MaterialLibraryParser _parser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _materialLibraryLoaderFacadeSpy = new MaterialLibraryLoaderFacadeSpy();
+ // _parser = new MaterialLibraryParser(_materialLibraryLoaderFacadeSpy);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_mtlib_line()
+ {
+ const string groupKeyword = "mtllib";
+
+ bool canParse = _parser.CanParse(groupKeyword);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_mtlib_line()
+ {
+ const string invalidKeyword = "vt";
+
+ bool canParse = _parser.CanParse(invalidKeyword);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_mtlib_line_correctly()
+ {
+ const string faceLine = "cube.mtl";
+ _parser.Parse(faceLine);
+
+ _materialLibraryLoaderFacadeSpy.RequestedLoadFileName.Should().BeEquivalentTo("cube.mtl");
+ }
+
+ private class MaterialLibraryLoaderFacadeSpy : IMaterialLibraryLoaderFacade
+ {
+ public string RequestedLoadFileName { get; set; }
+
+ public void Load(string materialFileName)
+ {
+ RequestedLoadFileName = materialFileName;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs
new file mode 100644
index 0000000..f9e03c7
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs
@@ -0,0 +1,68 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class NormalParserTests
+ {
+ private NormalParser _normalParser;
+ private NormalDataStoreMock _normalDataStoreMock;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _normalDataStoreMock = new NormalDataStoreMock();
+
+ // _normalParser = new NormalParser(_normalDataStoreMock);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_normal_line()
+ {
+ const string normalLine = "vn";
+
+ bool canParse = _normalParser.CanParse(normalLine);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_normal_line()
+ {
+ const string invalidNormal = "vt";
+
+ bool canParse = _normalParser.CanParse(invalidNormal);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly()
+ {
+ const string normalLine = "1.000000 2.000000 -1.000000";
+ _normalParser.Parse(normalLine);
+
+ var parsedNormal = _normalDataStoreMock.ParsedNormal;
+ parsedNormal.X.Should().BeApproximately(1, 0.000001f);
+ parsedNormal.Y.Should().BeApproximately(2, 0.000001f);
+ parsedNormal.Z.Should().BeApproximately(-1, 0.000001f);
+ }
+ }
+
+ class NormalDataStoreMock : INormalDataStore
+ {
+ public Normal ParsedNormal { get; set; }
+
+ public Normal GetNormal(int i)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void AddNormal(Normal normal)
+ {
+ ParsedNormal = normal;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs
new file mode 100644
index 0000000..539308b
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs
@@ -0,0 +1,67 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class TextureParserTests
+ {
+ private TextureDataStoreMock _textureDataStoreMock;
+ private TextureParser _textureParser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _textureDataStoreMock = new TextureDataStoreMock();
+
+ // _textureParser = new TextureParser(_textureDataStoreMock);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_normal_line()
+ {
+ const string textureKeyword = "vt";
+
+ bool canParse = _textureParser.CanParse(textureKeyword);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_normal_line()
+ {
+ const string invalidKeyword = "vn";
+
+ bool canParse = _textureParser.CanParse(invalidKeyword);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_normal_line_correctly()
+ {
+ const string textureLine = "0.500 -1.352";
+ _textureParser.Parse(textureLine);
+
+ var parsedNormal = _textureDataStoreMock.ParsedTexture;
+ parsedNormal.X.Should().BeApproximately(0.5f, 0.000001f);
+ parsedNormal.Y.Should().BeApproximately(-1.352f, 0.000001f);
+ }
+
+ class TextureDataStoreMock : ITextureDataStore
+ {
+ public TextureCoordinate ParsedTexture { get; private set; }
+
+ public TextureCoordinate GetTexture(int i)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void AddTexture(TextureCoordinate texture)
+ {
+ ParsedTexture = texture;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs
new file mode 100644
index 0000000..863fac8
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs
@@ -0,0 +1,57 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.TypeParsers;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class UseMaterialParserTests
+ {
+ private ElementGroupSpy _elementGroupSpy;
+ private UseMaterialParser _parser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _elementGroupSpy = new ElementGroupSpy();
+ // _parser = new UseMaterialParser(_elementGroupSpy);
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_usemtl_line()
+ {
+ const string groupKeyword = "usemtl";
+
+ bool canParse = _parser.CanParse(groupKeyword);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_usemtl_line()
+ {
+ const string invalidKeyword = "vt";
+
+ bool canParse = _parser.CanParse(invalidKeyword);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void Parses_usemtl_line_correctly_1()
+ {
+ const string useMtlLine = "materialName";
+ _parser.Parse(useMtlLine);
+
+ _elementGroupSpy.MaterialName.Should().BeEquivalentTo("materialName");
+ }
+
+ private class ElementGroupSpy // : IElementGroup
+ {
+ public string MaterialName { get; private set; }
+
+ public void SetMaterial(string materialName)
+ {
+ MaterialName = materialName;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs b/Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs
new file mode 100644
index 0000000..ba27316
--- /dev/null
+++ b/Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs
@@ -0,0 +1,69 @@
+using FluentAssertions;
+using NUnit.Framework;
+using ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties.Meshes.MeshData;
+
+namespace ObjLoader.Test.TypeParsers
+{
+ [TestFixture]
+ public class VertexParserTests
+ {
+ private VertexDataStoreMock _vertexDataStoreMock;
+ private VertexParser _vertexParser;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _vertexDataStoreMock = new VertexDataStoreMock();
+
+ // _vertexParser = new VertexParser(_vertexDataStoreMock);
+ }
+
+ [Test]
+ public void CanParse_returns_false_on_non_vertex_line()
+ {
+ const string invalidVertex = "vt";
+
+ bool canParse = _vertexParser.CanParse(invalidVertex);
+ canParse.Should().BeFalse();
+ }
+
+ [Test]
+ public void CanParse_returns_true_on_vertex_line()
+ {
+ const string vertexLine = "v";
+
+ bool canParse = _vertexParser.CanParse(vertexLine);
+ canParse.Should().BeTrue();
+ }
+
+ [Test]
+ public void Parses_vertex_line_correctly()
+ {
+ const string vertexLine = "0.123 0.234 0.345";
+ _vertexParser.Parse(vertexLine);
+
+ var parsedNormal = _vertexDataStoreMock.ParsedVertex.Position;
+ parsedNormal.X.Should().BeApproximately(0.123f, 0.000001f);
+ parsedNormal.Y.Should().BeApproximately(0.234f, 0.000001f);
+ parsedNormal.Z.Should().BeApproximately(0.345f, 0.000001f);
+ }
+
+ class VertexDataStoreMock : IVertexDataStore
+ {
+ public Vertex ParsedVertex { get; set; }
+
+ public Vertex GetVertex(int i)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void AddVertex(Vertex vertex)
+ {
+ ParsedVertex = vertex;
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Engine/Tests/SharpEngine.Core.Tests/Class1.cs b/Engine/Tests/SharpEngine.Core.Tests/Class1.cs
new file mode 100644
index 0000000..ff34b98
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Core.Tests/Class1.cs
@@ -0,0 +1,6 @@
+namespace SharpEngine.Core.Tests;
+
+public class Class1
+{
+
+}
diff --git a/SharpEngine.Shared/SharpEngine.Shared.csproj b/Engine/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj
similarity index 100%
rename from SharpEngine.Shared/SharpEngine.Shared.csproj
rename to Engine/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj
diff --git a/Engine/Tests/SharpEngine.Editor.Tests/Class1.cs b/Engine/Tests/SharpEngine.Editor.Tests/Class1.cs
new file mode 100644
index 0000000..2ae551d
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Editor.Tests/Class1.cs
@@ -0,0 +1,6 @@
+namespace SharpEngine.Editor.Tests;
+
+public class Class1
+{
+
+}
diff --git a/Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj b/Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/Engine/Tests/SharpEngine.Shared.Net8/Class1.cs b/Engine/Tests/SharpEngine.Shared.Net8/Class1.cs
new file mode 100644
index 0000000..e8f6eff
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Shared.Net8/Class1.cs
@@ -0,0 +1,6 @@
+namespace SharpEngine.Shared.Net8;
+
+public class Class1
+{
+
+}
diff --git a/Engine/Tests/SharpEngine.Shared.Net8/SharpEngine.Shared.Net8.csproj b/Engine/Tests/SharpEngine.Shared.Net8/SharpEngine.Shared.Net8.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Shared.Net8/SharpEngine.Shared.Net8.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/Engine/Tests/SharpEngine.Shared.Tests/Class1.cs b/Engine/Tests/SharpEngine.Shared.Tests/Class1.cs
new file mode 100644
index 0000000..f2af683
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Shared.Tests/Class1.cs
@@ -0,0 +1,6 @@
+namespace SharpEngine.Shared.Tests;
+
+public class Class1
+{
+
+}
diff --git a/Engine/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj b/Engine/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/Engine/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/Engine/Tests/Test.Directory.Build.props b/Engine/Tests/Test.Directory.Build.props
new file mode 100644
index 0000000..a52359c
--- /dev/null
+++ b/Engine/Tests/Test.Directory.Build.props
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/CODE_OF_CONDUCT.md b/Engine/docs/CODE_OF_CONDUCT.md
similarity index 100%
rename from docs/CODE_OF_CONDUCT.md
rename to Engine/docs/CODE_OF_CONDUCT.md
diff --git a/docs/CONTRIBUTING.md b/Engine/docs/CONTRIBUTING.md
similarity index 100%
rename from docs/CONTRIBUTING.md
rename to Engine/docs/CONTRIBUTING.md
diff --git a/docs/SECURITY.md b/Engine/docs/SECURITY.md
similarity index 100%
rename from docs/SECURITY.md
rename to Engine/docs/SECURITY.md
diff --git a/docs/materials_and_inspiration.md b/Engine/docs/materials_and_inspiration.md
similarity index 100%
rename from docs/materials_and_inspiration.md
rename to Engine/docs/materials_and_inspiration.md
diff --git a/docs/pull_request_template.md b/Engine/docs/pull_request_template.md
similarity index 100%
rename from docs/pull_request_template.md
rename to Engine/docs/pull_request_template.md
diff --git a/Launcher/UI/Components/Dialogs/Notification.razor b/Launcher/UI/Components/Dialogs/Notification.razor
deleted file mode 100644
index 8a40e57..0000000
--- a/Launcher/UI/Components/Dialogs/Notification.razor
+++ /dev/null
@@ -1,29 +0,0 @@
-@using Launcher.Services
-@inherits DialogBase
-
-@inject INotificationService NotificationService
-
-
-
- @foreach (var notification in NotificationService.Notifications)
- {
-
- @notification.Key @(notification.Value > 1 ? $"({notification.Value})" : "")
-
-
- }
-
-
-
-@code {
- /// Gets or sets the type of the notification.
- [Parameter]
- public NotificationType Type { get; set; } = NotificationType.Warning;
-
- ///
- protected override void OnInitialized()
- {
- NotificationService.SetInvokeAsync(InvokeAsync);
- NotificationService.SetOnNotificationsChanged(StateHasChanged);
- }
-}
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/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
-
-
-
-
- | Name |
- Last Modified |
- Actions |
-
-
-
- @foreach (var item in Items)
- {
-
-
- @Row(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 Row { get; set; }
-}
\ No newline at end of file
diff --git a/Launcher/UI/Components/Layout.razor b/Launcher/UI/Components/Layout.razor
deleted file mode 100644
index 21adf40..0000000
--- a/Launcher/UI/Components/Layout.razor
+++ /dev/null
@@ -1,5 +0,0 @@
-@inherits LayoutComponentBase
-
-
-@Body
-
\ No newline at end of file
diff --git a/Launcher/UI/Home.razor b/Launcher/UI/Home.razor
deleted file mode 100644
index 35fb873..0000000
--- a/Launcher/UI/Home.razor
+++ /dev/null
@@ -1,31 +0,0 @@
-@page "/"
-
-@using Launcher.UI.Components
-@using Launcher.UI.Components.Dialogs
-@using System.Diagnostics
-
-
-
-
-
-
-
-
-
-
- | @context.Name |
- @context.LastModified |
-
- |