diff --git a/Engine/.editorconfig b/.editorconfig similarity index 100% rename from Engine/.editorconfig rename to .editorconfig diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1318621 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,17 @@ +When adding new public members or editing existing ones, please ensure that you update the XML documentation comments for those members. +This helps maintain code readability and provides useful information for other developers who may use or maintain the code in the future. + +Additionally, if you are adding new features or making significant changes, consider updating any relevant unit tests to cover the new functionality and ensure that existing tests still pass. + +All changes need to be unit testable. Favor composition over inheritance when designing new classes or features, and ensure that your code adheres to SOLID principles for maintainability and scalability. +When referencing classes like System.IO, System.Net.Http or similar, try to use a abstraction layer or interface to allow for easier testing and flexibility in the future. +This will help decouple your code from specific implementations and make it easier to mock dependencies in unit tests. + +Give me a brief summary of the changes you are making, including the purpose and any relevant details, so that I can provide more specific guidance or suggestions if needed. + +If you see any areas of the codebase that could benefit from refactoring or improvement, please feel free to suggest those changes as well. +This could include improving code readability, reducing complexity, or enhancing performance. +All suggestions should be accompanied by a clear rationale and, if possible, examples of how the changes would improve the codebase. +Suggestions need to be manually approved, so do not implement those by yourself without prior discussion and approval. + +Finally, please make sure to run all existing tests and any new tests you create to verify that your changes do not introduce any regressions or issues in the codebase. \ No newline at end of file diff --git a/.github/workflows/README.md b/.github/workflows/README.md deleted file mode 100644 index 8f5aa5e..0000000 --- a/.github/workflows/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# SharpEngine Pipelines - -## Pull Request pipelines - - - -## Deployment Pipelines - -SharpEngine consists of 5 different deployment pipelines: -- Publish Core -- Publish Asset Store -- Publish Web -- Publish Docs -- Publish Launcher - -All of these pipelines are responsible of building and exposing the respective components of the system. - -### Publish Core -The `publish-core.yml` creates a zip file of the engine and adds a tag to github and the zip to the releases section. That tag is also added for the commit that it was built off. If a tag for the commit this is being run-off is already found, we don't want to create another copy of the essentially the same version so we skip it. - -### Publish Asset Store -The `publish-asset-store.yml` is responsible of deploying the Asset Store web interface, API and Database into Azure. - -All Asset Store's resources can be found under the `rg-asset-store` resource group. -TODO: Enable from selected access ips - -### Publish Web -The `publish-web.yml` is responsible of (similarly to the Asset Store) deploying the SharpEngine web interface, API and Database into Azure. - -All SharpEngine Web's resources can be found under the `rg-sharpengine-web` resource group. - -### Publish Docs -The `publish-docs.yml` deploys the documentation files under the ´./docs´ in the root of the repository into the `docs.sharpengine.com` site. - -### Publish Launcher -The `` \ No newline at end of file diff --git a/.github/workflows/azure-static-web-apps-victorious-moss-06d128203.yml b/.github/workflows/azure-static-web-apps-victorious-moss-06d128203.yml deleted file mode 100644 index 3bc08ab..0000000 --- a/.github/workflows/azure-static-web-apps-victorious-moss-06d128203.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Azure Static Web Apps CI/CD - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - name: Install OIDC Client from Core Package - run: npm install @actions/core@1.6.0 @actions/http-client - - name: Get Id Token - uses: actions/github-script@v6 - id: idtoken - with: - script: | - const coredemo = require('@actions/core') - return await coredemo.getIDToken() - result-encoding: string - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_VICTORIOUS_MOSS_06D128203 }} - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "./Portal/sharpengine-web-ui" # App source code path - # api_location: "" # Api source code path - optional - output_location: "build" # Built app content directory - optional - github_id_token: ${{ steps.idtoken.outputs.result }} - app_build_command: "CI=false npm run build" - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - action: "close" diff --git a/.github/workflows/publish-asset-store.yml b/.github/workflows/publish-asset-store.yml deleted file mode 100644 index e1a0afc..0000000 --- a/.github/workflows/publish-asset-store.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Publish Asset Store - -on: - push: - branches: - - main - - feature/52-av-web - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - - feature/52-av-web - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - - name: Install OIDC Client from Core Package - run: npm install @actions/core@1.6.0 @actions/http-client - - - name: Get Id Token - uses: actions/github-script@v6 - id: idtoken - with: - script: | - const coredemo = require('@actions/core') - return await coredemo.getIDToken() - result-encoding: string - - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_WHITE_SKY_0ACD4AD03 }} - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "/AssetStore/asset-store-ui" # App source code path - api_location: "" # Api source code path - optional - # output_location: "app" # Built app content directory - optional - github_id_token: ${{ steps.idtoken.outputs.result }} - app_build_command: "CI=false npm run build" - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - action: "close" - diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml deleted file mode 100644 index 7931f80..0000000 --- a/.github/workflows/publish-docs.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish Docs - -on: - push: - branches: - - test - pull_request: - branches: - - test - -jobs: - echo: - runs-on: ubuntu-latest - steps: - - name: Echo message - run: echo "Hello from the test branch 👋" diff --git a/.github/workflows/publish-launcher.yml b/.github/workflows/publish-launcher.yml deleted file mode 100644 index 685b861..0000000 --- a/.github/workflows/publish-launcher.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish Launcher - -on: - push: - branches: - - test - pull_request: - branches: - - test - -jobs: - echo: - runs-on: ubuntu-latest - steps: - - name: Echo message - run: echo "Hello from the test branch 👋" diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml deleted file mode 100644 index 017a6b2..0000000 --- a/.github/workflows/publish-web.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish SharpEngine Web Portal - -on: - push: - branches: - # - main - - feature/52-av-web - pull_request: - types: [closed] - branches: - - main - # - feature/52-av-web - -jobs: - build_and_deploy_job: - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v3 - with: - submodules: true - lfs: false - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_MEADOW_06167BF03 }} - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: "upload" - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: "./Portal/sharpengine-web-ui" # App source code path - # api_location: "" # Api source code path - optional - output_location: "build" # Built app content directory - optional - app_build_command: "CI=false npm run build" - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_KIND_MEADOW_06167BF03 }} - action: "close" diff --git a/ATAM.md b/ATAM.md new file mode 100644 index 0000000..e69de29 diff --git a/Engine/Directory.Build.props b/Directory.Build.props similarity index 94% rename from Engine/Directory.Build.props rename to Directory.Build.props index c13bb19..368b137 100644 --- a/Engine/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - net8.0 + net8.0 diff --git a/Engine/Examples/MinecraftClone/Minecraft.csproj b/Engine/Examples/MinecraftClone/Minecraft.csproj deleted file mode 100644 index d3d98c6..0000000 --- a/Engine/Examples/MinecraftClone/Minecraft.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - AnyCPU;x64;x86 - - - - - - - - - - - - - diff --git a/Engine/Examples/MinecraftClone/Program.cs b/Engine/Examples/MinecraftClone/Program.cs deleted file mode 100644 index 727a8f4..0000000 --- a/Engine/Examples/MinecraftClone/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using SharpEngine.Core.Interfaces; -using SharpEngine.Core.Scenes; -using SharpEngine.Core.Windowing; - -namespace Minecraft; - -/// -/// Represents the entry point of the application. -/// -public static class Program -{ - private static void Main() - { - DefaultSettings gameSettings = new() - { - UseWireFrame = false - }; - - Scene scene = new Scene(); - Minecraft game = new Minecraft(scene, gameSettings); - - using var window = new Window(game.Camera, scene, game.Camera.Settings); - window.OnLoaded += game.Initialize; - window.OnHandleMouse += game.HandleMouse; - window.OnUpdate += game.Update; - window.OnHandleKeyboard += game.HandleKeyboard; - window.OnButtonMouseDown += game.HandleMouseDown; - window.HandleMouseWheel += game.HandleMouseWheel; - window.OnAfterRender += frame => game.OnAfterRender(frame); - - // TODO: #85 This needs to be streamlined. - game.Window = window; - - window.Run(); - } -} diff --git a/Engine/ObjLoader/DataStore.cs b/Engine/ObjLoader/DataStore.cs deleted file mode 100644 index 5ee2b5c..0000000 --- a/Engine/ObjLoader/DataStore.cs +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 0de39a2..0000000 --- a/Engine/ObjLoader/Loaders/LoaderBase.cs +++ /dev/null @@ -1,38 +0,0 @@ -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 deleted file mode 100644 index 4417065..0000000 --- a/Engine/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 90ebb1b..0000000 --- a/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs +++ /dev/null @@ -1,107 +0,0 @@ -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 deleted file mode 100644 index c6f62af..0000000 --- a/Engine/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs +++ /dev/null @@ -1,34 +0,0 @@ -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/TypeParsers/GroupParser.cs b/Engine/ObjLoader/TypeParsers/GroupParser.cs deleted file mode 100644 index b8d1512..0000000 --- a/Engine/ObjLoader/TypeParsers/GroupParser.cs +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 9cb2d7e..0000000 --- a/Engine/ObjLoader/TypeParsers/ITypeParser.cs +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 7b7c2b4..0000000 --- a/Engine/ObjLoader/TypeParsers/MaterialLibraryParser.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 49af71a..0000000 --- a/Engine/ObjLoader/TypeParsers/NormalParser.cs +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index 0c596ba..0000000 --- a/Engine/ObjLoader/TypeParsers/TextureParser.cs +++ /dev/null @@ -1,31 +0,0 @@ -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 deleted file mode 100644 index 7880db8..0000000 --- a/Engine/ObjLoader/TypeParsers/TypeParserBase.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 72f33d8..0000000 --- a/Engine/ObjLoader/TypeParsers/UseMaterialParser.cs +++ /dev/null @@ -1,20 +0,0 @@ -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/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs b/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs deleted file mode 100644 index e6c0922..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IFaceGroup.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 3fe9d69..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IGroupDataStore.cs +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 5652449..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IMaterialLibrary.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 10ae87d..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/INormalDataStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index a1591ff..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/ITextureDataStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 6667f06..0000000 --- a/Engine/SharpEngine.Core.Components/Obsolete/ObjLoader/DataStore/IVertexDataStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -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/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs deleted file mode 100644 index e4dfdb5..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 1cd52c9..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 5e40c14..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 5bd29ce..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 5eb1fda..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs b/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs deleted file mode 100644 index 513cf4e..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/Model.cs +++ /dev/null @@ -1,104 +0,0 @@ -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 deleted file mode 100644 index 0f477aa..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/Model_Old.cs +++ /dev/null @@ -1,214 +0,0 @@ -// 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/Textures/Texture.cs b/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs deleted file mode 100644 index d271f1e..0000000 --- a/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs +++ /dev/null @@ -1,31 +0,0 @@ -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/Entities/GameObject.cs b/Engine/SharpEngine.Core/Entities/GameObject.cs deleted file mode 100644 index 160896a..0000000 --- a/Engine/SharpEngine.Core/Entities/GameObject.cs +++ /dev/null @@ -1,114 +0,0 @@ -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/Engine/SharpEngine.Core/Entities/MeshService.cs b/Engine/SharpEngine.Core/Entities/MeshService.cs deleted file mode 100644 index 99a60a2..0000000 --- a/Engine/SharpEngine.Core/Entities/MeshService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace SharpEngine.Core.Entities.Properties.Meshes; - -/// -/// Represents a service that loads meshes into the GPU. -/// -public class MeshService -{ - /// A global instance of the service. - public static readonly MeshService Instance = new(); - - private readonly Dictionary Meshes = []; - - private MeshService() { } - - /// - /// Loads a mesh into the mesh cache. - /// - /// Used to cache the loaded mesh. - /// The mesh that should be stored in to the GPU buffer. - /// The loaded mesh. - public Mesh LoadMesh(string identifier, Mesh mesh) - { - if (Meshes.TryGetValue(identifier, out var cachedMesh)) - return cachedMesh; - - Meshes.Add(identifier, mesh); - return mesh; - } -} \ No newline at end of file diff --git a/Engine/SharpEngine.Core/Entities/UI/UIElement.cs b/Engine/SharpEngine.Core/Entities/UI/UIElement.cs deleted file mode 100644 index 4230410..0000000 --- a/Engine/SharpEngine.Core/Entities/UI/UIElement.cs +++ /dev/null @@ -1,108 +0,0 @@ -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/Engine/SharpEngine.Core/Primitives/Cube.cs b/Engine/SharpEngine.Core/Primitives/Cube.cs deleted file mode 100644 index 16be872..0000000 --- a/Engine/SharpEngine.Core/Primitives/Cube.cs +++ /dev/null @@ -1,203 +0,0 @@ -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/Engine/SharpEngine.Core/Renderers/TextRenderer.cs b/Engine/SharpEngine.Core/Renderers/TextRenderer.cs deleted file mode 100644 index 5787fc5..0000000 --- a/Engine/SharpEngine.Core/Renderers/TextRenderer.cs +++ /dev/null @@ -1,22 +0,0 @@ -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/Engine/SharpEngine.Core/Shaders/LampShader.cs b/Engine/SharpEngine.Core/Shaders/LampShader.cs deleted file mode 100644 index 898d08b..0000000 --- a/Engine/SharpEngine.Core/Shaders/LampShader.cs +++ /dev/null @@ -1,39 +0,0 @@ -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 deleted file mode 100644 index 0893499..0000000 --- a/Engine/SharpEngine.Core/Shaders/LightingShader.cs +++ /dev/null @@ -1,53 +0,0 @@ -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/Engine/SharpEngine.Core/Shaders/ShaderService.cs b/Engine/SharpEngine.Core/Shaders/ShaderService.cs deleted file mode 100644 index ee8f516..0000000 --- a/Engine/SharpEngine.Core/Shaders/ShaderService.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.IO; -using System.Collections.Generic; - -using SharpEngine.Core.Windowing; -using SharpEngine.Shared; - -namespace SharpEngine.Core.Shaders; - -/// -/// Contains all the shaders used in the game. -/// -public class ShaderService -{ - /// - /// Gets the singleton instance of the . - /// - public static ShaderService Instance { get; } = new ShaderService(); - - private readonly Dictionary _shaderCache = []; - - /// - /// Gets or sets whether there are shaders to load. - /// - public bool HasShadersToLoad { get; set; } = true; - - /// - /// Private constructor to prevent instantiation. - /// - private ShaderService() { } - - /// - /// Gets all the shaders in the cache. - /// - /// All the shaders found from the cache. - public List GetAll() - { - HasShadersToLoad = false; - return [.. _shaderCache.Values]; - } - - /// - /// Gets a shader by its name. - /// - /// The name of the shader to be found. - /// The found shader. - /// - /// Thrown if a shader by that is not found. - /// This exception is thrown to make sure there are no unexpected issues made by the developer. - /// - public Shader GetByName(string name) - { - if (_shaderCache.TryGetValue(name, out var cachedShader)) - return cachedShader; - - throw new KeyNotFoundException($"Shader with name {name} not found in cache."); - } - - /// - /// Loads a shader from the specified vertex and fragment paths.
- /// If the shader is loaded already, adds it to the cache. - ///
- /// The vertex shader full path. - /// The fragment shader full path. - /// A name identifier for the shader. - /// A shader with the given name. - /// Thrown when either the vertex or fragment shader is not found. - public Shader LoadShader(string vertPath, string fragPath, string name) - { - // Check if the shader is already in the cache - if (_shaderCache.TryGetValue(name, out var cachedShader)) - return cachedShader; - - if (!File.Exists(vertPath)) - { - Debug.Log.Information("Vertex shader file not found: {VertPath}", vertPath); - throw new FileNotFoundException($"Vertex shader file not found: {vertPath}"); - } - - if (!File.Exists(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(Window.GL, vertPath, fragPath, name).Initialize(); - _shaderCache[name] = shader; - - HasShadersToLoad = true; - - return shader; - } -} diff --git a/Engine/SharpEngine.Core/Shaders/UIShader.cs b/Engine/SharpEngine.Core/Shaders/UIShader.cs deleted file mode 100644 index 37caf51..0000000 --- a/Engine/SharpEngine.Core/Shaders/UIShader.cs +++ /dev/null @@ -1,48 +0,0 @@ -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/Tests/SharpEngine.Shared.Net8/Class1.cs b/Engine/Tests/SharpEngine.Shared.Net8/Class1.cs deleted file mode 100644 index e8f6eff..0000000 --- a/Engine/Tests/SharpEngine.Shared.Net8/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index fa71b7a..0000000 --- a/Engine/Tests/SharpEngine.Shared.Net8/SharpEngine.Shared.Net8.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - - diff --git a/Engine/Tests/Test.Directory.Build.props b/Engine/Tests/Test.Directory.Build.props deleted file mode 100644 index a52359c..0000000 --- a/Engine/Tests/Test.Directory.Build.props +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Engine/Examples/MinecraftClone/Block/BlockBase.cs b/Examples/MinecraftClone/Block/BlockBase.cs similarity index 100% rename from Engine/Examples/MinecraftClone/Block/BlockBase.cs rename to Examples/MinecraftClone/Block/BlockBase.cs diff --git a/Engine/Examples/MinecraftClone/Block/Dirt.cs b/Examples/MinecraftClone/Block/Dirt.cs similarity index 100% rename from Engine/Examples/MinecraftClone/Block/Dirt.cs rename to Examples/MinecraftClone/Block/Dirt.cs diff --git a/Engine/Examples/MinecraftClone/Block/Stone.cs b/Examples/MinecraftClone/Block/Stone.cs similarity index 100% rename from Engine/Examples/MinecraftClone/Block/Stone.cs rename to Examples/MinecraftClone/Block/Stone.cs diff --git a/Engine/Examples/MinecraftClone/BlockFactory.cs b/Examples/MinecraftClone/BlockFactory.cs similarity index 100% rename from Engine/Examples/MinecraftClone/BlockFactory.cs rename to Examples/MinecraftClone/BlockFactory.cs diff --git a/Engine/Examples/MinecraftClone/Input.cs b/Examples/MinecraftClone/Input.cs similarity index 100% rename from Engine/Examples/MinecraftClone/Input.cs rename to Examples/MinecraftClone/Input.cs diff --git a/Engine/Examples/MinecraftClone/Inventory.cs b/Examples/MinecraftClone/Inventory.cs similarity index 100% rename from Engine/Examples/MinecraftClone/Inventory.cs rename to Examples/MinecraftClone/Inventory.cs diff --git a/Engine/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs similarity index 85% rename from Engine/Examples/MinecraftClone/Minecraft.cs rename to Examples/MinecraftClone/Minecraft.cs index 92494da..1b347e2 100644 --- a/Engine/Examples/MinecraftClone/Minecraft.cs +++ b/Examples/MinecraftClone/Minecraft.cs @@ -1,25 +1,24 @@ using ImGuiNET; + using Minecraft.Block; -using ObjLoader.Loaders.ObjLoader; +using Microsoft.Extensions.Logging; + using SharpEngine.Core; using SharpEngine.Core.Entities; using SharpEngine.Core.Entities.Lights; using SharpEngine.Core.Entities.Properties; -using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Entities.UI; using SharpEngine.Core.Entities.UI.Layouts; using SharpEngine.Core.Enums; using SharpEngine.Core.Interfaces; using SharpEngine.Core.Scenes; using SharpEngine.Core.Windowing; -using SharpEngine.Shared; -using Silk.NET.Core.Native; +using SharpEngine.Core.ObjLoader.Loaders.ObjLoader; + using Silk.NET.Input; + using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; -using Tutorial; namespace Minecraft; @@ -31,6 +30,7 @@ namespace Minecraft; public class Minecraft : Game { private readonly Scene _scene; + private readonly ILogger _logger; private SceneNode _lightsNode; private SceneNode _blocksNode; @@ -43,15 +43,20 @@ public class Minecraft : Game /// /// Gets the main window. /// - public Window? Window { get; set; } + public static Window Window + { + get => field ?? throw new InvalidOperationException("The game window has not been assigned yet."); + set; + } /// /// Initializes a new instance of the . /// - public Minecraft(Scene scene, ISettings settings) + public Minecraft(Scene scene, ISettings settings, ILogger logger) { _scene = scene; CoreSettings = settings; + _logger = logger; _inventory = new Inventory(); @@ -76,21 +81,21 @@ public override void Initialize() _uiElem = new UIElement("uiElement"); _scene.UIElements.Add(_uiElem); - //var uiElem2 = new UIElement("uiElement"); - //uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); - //uiElem2.Transform.Position = new Vector2(30, 0); + var uiElem2 = new UIElement("uiElement"); + uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f); + uiElem2.Transform.Position = new SharpEngine.Core.Numerics.Vector2(30, 0); - // gridLayout.AddChild(_uiElem, uiElem2); - // _scene.UIElements.Add(_uiElem); - // _scene.UIElements.Add(uiElem2); + gridLayout.AddChild(_uiElem, uiElem2); + _scene.UIElements.Add(_uiElem); + _scene.UIElements.Add(uiElem2); - //_scene.UIElements.Add(gridLayout); + _scene.UIElements.Add(gridLayout); InitializeWorld(); } catch (Exception ex) { - Debug.Log.Information(ex.Message, "{Message}", ex.Message); + _logger.LogInformation(ex, "{Message}", ex.Message); } } @@ -151,7 +156,7 @@ private void InitializeWorld() // TODO: #2 Does not work yet. // var torus = MeshService.Instance.LoadMesh("torus", @"C:\Users\antti\Documents\Untitled2.obj"); - var model = ObjLoaderFactory.Load(Window.GL, @"C:\Users\antti\Documents\Untitled2.obj"); + var model = ObjLoaderFactory.Load(Window.GetGL(), @"C:\Users\antti\Documents\Untitled2.obj"); var go = new GameObject(model); var go2 = new GameObject(model) { @@ -220,14 +225,8 @@ private void GenerateChunk(int chunkSize, Vector3 chunkPos) } /// - public override void Update(double deltaTime, IInputContext input) - { - //UpdateUI(); - _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime); - } - - private void UpdateUI() - => _uiElem.Transform.Rotation.Angle += 0.01f; + public override void Update(double deltaTime, IInputContext input) + => _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime); // TODO: #21 Input system to let users change change key bindings? /// @@ -238,7 +237,7 @@ public override void HandleKeyboard(IKeyboard input, double deltaTime) if (input.IsKeyPressed(Key.Number0 + i)) { _inventory.SetSelectedSlot(i); - Debug.Log.Information("Selected slot: {I} ({Type})", i, _inventory.SelectedSlot.Items.Type); + _logger.LogInformation("Selected slot: {I} ({Type})", i, _inventory.SelectedSlot.Items.Type); } } @@ -273,7 +272,7 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button) } else { - Debug.Log.Information("No more {Type}s.", _inventory.SelectedSlot.Items.Type); + _logger.LogInformation("No more {Type}s.", _inventory.SelectedSlot.Items.Type); } } @@ -284,7 +283,7 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button) { // TODO: #86 The block should be added to the slot so that 0 is the last slot instead of 9. // TODO: #86 The first block destroyed doesn't seem to be added to the inventory. - Debug.Log.Information("Block destroyed: {DestroyedBlockType}.", destroyedBlockType); + _logger.LogInformation("Block destroyed: {DestroyedBlockType}.", destroyedBlockType); _inventory.AddToolbarItem(destroyedBlockType); } } @@ -313,7 +312,7 @@ private void PlaceBlock() var newBlock = BlockFactory.CreateBlock(_inventory.SelectedSlot.Items.Type, newBlockPosition, $"Dirt ({_blocksNode.Children.Count})"); _blocksNode.AddChild(newBlock); - Debug.Log.Information("New block created: {Pos}, block in view location: {IntersectingPos}", newBlock.Transform.Position, intersectingObject!.Transform.Position); + _logger.LogInformation("New block created: {Pos}, block in view location: {IntersectingPos}", newBlock.Transform.Position, intersectingObject!.Transform.Position); } private static Vector3 GetNewBlockPosition(Vector3 hitPosition, GameObject intersectingObject) @@ -338,7 +337,7 @@ public override void HandleMouseWheel(MouseWheelScrollDirection direction, Scrol slotIndex = 0; _inventory.SetSelectedSlot(slotIndex); - Debug.Log.Information("Selected slot: {Index}", slotIndex); + _logger.LogInformation("Selected slot: {Index}", slotIndex); } } diff --git a/Examples/MinecraftClone/Minecraft.csproj b/Examples/MinecraftClone/Minecraft.csproj new file mode 100644 index 0000000..447f24c --- /dev/null +++ b/Examples/MinecraftClone/Minecraft.csproj @@ -0,0 +1,19 @@ + + + + Exe + $(DotNetTargetFramework) + AnyCPU;x64;x86 + + + + + + + + + + + + + diff --git a/Engine/Examples/MinecraftClone/Minecraft.sln b/Examples/MinecraftClone/Minecraft.sln similarity index 100% rename from Engine/Examples/MinecraftClone/Minecraft.sln rename to Examples/MinecraftClone/Minecraft.sln diff --git a/Examples/MinecraftClone/Program.cs b/Examples/MinecraftClone/Program.cs new file mode 100644 index 0000000..97df2f9 --- /dev/null +++ b/Examples/MinecraftClone/Program.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +using SharpEngine.Core.DependencyInjection; +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Interfaces; +using SharpEngine.Core.Renderers; +using SharpEngine.Core.Scenes; +using SharpEngine.Core.Windowing; + +namespace Minecraft; + +/// +/// Represents the entry point of the application. +/// +public static class Program +{ + private static void Main() + { + var builder = new AppBuilder() + .ConfigureServices(ConfigureServices); + + var app = builder.Build(); + app.Run(); + } + + private static void ConfigureServices(IServiceCollection services) + { + services.AddLogging(builder => builder.AddConsole()); + + services.AddSingleton(_ => new DefaultSettings + { + UseWireFrame = false + }); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService().Camera); + services.AddTransient(); + services.AddTransient(); + + services.AddWindow( + factory: serviceProvider => + { + var game = serviceProvider.GetRequiredService(); + var scene = serviceProvider.GetRequiredService(); + var windowLogger = serviceProvider.GetRequiredService>(); + var renderers = serviceProvider.GetServices(); + + return new Window(game.Camera, scene, game.Camera.Settings, windowLogger, renderers); + }, + configure: (serviceProvider, window) => + { + var game = serviceProvider.GetRequiredService(); + + window.OnLoaded += game.Initialize; + window.OnHandleMouse += game.HandleMouse; + window.OnUpdate += game.Update; + window.OnHandleKeyboard += game.HandleKeyboard; + window.OnButtonMouseDown += game.HandleMouseDown; + window.HandleMouseWheel += game.HandleMouseWheel; + window.OnAfterRender += game.OnAfterRender; + + Minecraft.Window = window; + }, + name: "minecraft", + isDefault: true); + } +} diff --git a/Engine/Examples/MinecraftClone/Resources/container2.png b/Examples/MinecraftClone/Resources/container2.png similarity index 100% rename from Engine/Examples/MinecraftClone/Resources/container2.png rename to Examples/MinecraftClone/Resources/container2.png diff --git a/Engine/Examples/MinecraftClone/Resources/container2_specular.png b/Examples/MinecraftClone/Resources/container2_specular.png similarity index 100% rename from Engine/Examples/MinecraftClone/Resources/container2_specular.png rename to Examples/MinecraftClone/Resources/container2_specular.png diff --git a/Engine/Examples/MinecraftClone/Resources/grass.jpg b/Examples/MinecraftClone/Resources/grass.jpg similarity index 100% rename from Engine/Examples/MinecraftClone/Resources/grass.jpg rename to Examples/MinecraftClone/Resources/grass.jpg diff --git a/Engine/Examples/Minimal/Minimal.csproj b/Examples/Minimal/Minimal.csproj similarity index 82% rename from Engine/Examples/Minimal/Minimal.csproj rename to Examples/Minimal/Minimal.csproj index 8bbdf5a..e786576 100644 --- a/Engine/Examples/Minimal/Minimal.csproj +++ b/Examples/Minimal/Minimal.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(DotNetTargetFramework) enable enable diff --git a/Engine/Examples/Minimal/Minimal.sln b/Examples/Minimal/Minimal.sln similarity index 100% rename from Engine/Examples/Minimal/Minimal.sln rename to Examples/Minimal/Minimal.sln diff --git a/Engine/Examples/Minimal/Program.cs b/Examples/Minimal/Program.cs similarity index 61% rename from Engine/Examples/Minimal/Program.cs rename to Examples/Minimal/Program.cs index 9751459..26310ec 100644 --- a/Engine/Examples/Minimal/Program.cs +++ b/Examples/Minimal/Program.cs @@ -1,5 +1,4 @@ using SharpEngine.Core.Interfaces; -using SharpEngine.Core.Scenes; using SharpEngine.Core.Windowing; namespace Minimal; @@ -14,10 +13,9 @@ public static class Program /// public static void Main(string[] _) { - var game = new Minimal(new DefaultSettings()); - var scene = new Scene(); + var game = new Minimal(); - using var window = new Window(game.Camera, scene, game.Camera.Settings); + using var window = new Window(game); window.Run(); } } @@ -30,9 +28,7 @@ public class Minimal : Game /// /// Initializes a new instance of /// - /// Provides configuration options for the instance. - public Minimal(ISettings settings) + public Minimal() { - CoreSettings = settings; } } diff --git a/Engine/Examples/MultipleWindows/MultipleWindows.csproj b/Examples/MultipleWindows/MultipleWindows.csproj similarity index 55% rename from Engine/Examples/MultipleWindows/MultipleWindows.csproj rename to Examples/MultipleWindows/MultipleWindows.csproj index e48ece3..1596f7d 100644 --- a/Engine/Examples/MultipleWindows/MultipleWindows.csproj +++ b/Examples/MultipleWindows/MultipleWindows.csproj @@ -2,18 +2,12 @@ Exe - net8.0 + $(DotNetTargetFramework) enable enable true - - - - - - diff --git a/Engine/Examples/MultipleWindows/Program.cs b/Examples/MultipleWindows/Program.cs similarity index 68% rename from Engine/Examples/MultipleWindows/Program.cs rename to Examples/MultipleWindows/Program.cs index 788b808..67751b3 100644 --- a/Engine/Examples/MultipleWindows/Program.cs +++ b/Examples/MultipleWindows/Program.cs @@ -1,10 +1,12 @@ -using Silk.NET.Input; +using Microsoft.Extensions.Logging; +using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.Windowing; using System.Collections.Concurrent; using SharpEngine.Core.Entities.Views.Settings; -using SharpEngine.Shared; + +using Window = SharpEngine.Core.Windowing.Window; namespace SharpEngine.Examples.MultipleWindows; @@ -16,6 +18,10 @@ namespace SharpEngine.Examples.MultipleWindows; /// public static partial class Program { + private static readonly ILoggerFactory _loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + private static readonly ILogger _logger = _loggerFactory.CreateLogger(typeof(Program)); + private static readonly ILogger _windowLogger = _loggerFactory.CreateLogger(); + private static readonly List _windows = []; private static readonly List _inputContexts = []; private static readonly ConcurrentQueue _windowQueue = []; @@ -25,28 +31,23 @@ public static partial class Program /// The main entry point of the application. /// /// Arguments discarded. - public static void Main(string[] _) + public static async Task Main(string[] _) { - StartWindowQueueTask(); - - try - { - while (!_cancellationTokenSource.IsCancellationRequested) - { - for (int i = 0; i < _windows.Count; i++) - UpdateWindow(ref i); - - DequeueWindows(); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - _cancellationTokenSource.Dispose(); - } + Core.Handlers.WindowHandler windowHandler = new(); + windowHandler.Start(); + + Console.WriteLine("end"); + Console.ReadLine(); + + // StartWindowQueueTask(); + // + // while (!_cancellationTokenSource.IsCancellationRequested) + // { + // for (int i = 0; i < _windows.Count; i++) + // UpdateWindow(ref i); + // + // DequeueWindows(); + // } } private static void UpdateWindow(ref int i) @@ -88,11 +89,11 @@ private static void StartWindowQueueTask() while (!_cancellationTokenSource.IsCancellationRequested) { await Task.Delay(1000); - Debug.Log.Information("Running loop on background thread..."); + _logger.LogInformation("Running loop on background thread..."); } }); - private static SharpEngine.Core.Windowing.Window CreateWindow() + private static Window CreateWindow() { var options = new DefaultViewSettings() with { @@ -106,8 +107,8 @@ private static SharpEngine.Core.Windowing.Window CreateWindow() y: 400 + (50 * _windows.Count)) } }; - - var window = new SharpEngine.Core.Windowing.Window(new(), options); + + var window = new Window(new(), options, _windowLogger); window.Initialize(); return window; @@ -123,8 +124,6 @@ private static void EnqueueWindow() _windows.Add(window); } - private static void Mouse_Click(IMouse args1, Silk.NET.Input.MouseButton arg2, System.Numerics.Vector2 arg3) - { - _windowQueue.Enqueue(WindowOptions.Default); - } + private static void Mouse_Click(IMouse args1, MouseButton arg2, System.Numerics.Vector2 arg3) + => _windowQueue.Enqueue(WindowOptions.Default); } diff --git a/Engine/Examples/Tutorial 4.1 - Model Loading/Program.cs b/Examples/Tutorial 4.1 - Model Loading/Program.cs similarity index 78% rename from Engine/Examples/Tutorial 4.1 - Model Loading/Program.cs rename to Examples/Tutorial 4.1 - Model Loading/Program.cs index 1bffe63..742d903 100644 --- a/Engine/Examples/Tutorial 4.1 - Model Loading/Program.cs +++ b/Examples/Tutorial 4.1 - Model Loading/Program.cs @@ -1,6 +1,7 @@ -using ObjLoader.Loaders.ObjLoader; using SharpEngine.Core._Resources; using SharpEngine.Core.Extensions; +using SharpEngine.Core.Components.Properties.Meshes; +using SharpEngine.Core.ObjLoader.Loaders.ObjLoader; using Shader = SharpEngine.Core.Shaders.Shader; using Texture = SharpEngine.Core.Components.Properties.Textures.Texture; @@ -15,16 +16,20 @@ namespace Tutorial { + /// + /// Represents the main entry point of the application, demonstrating model loading and rendering using Silk.NET and SharpEngine.Core. + /// public static class Program { #region fields - private static IWindow window; - private static GL Gl; - private static IKeyboard primaryKeyboard; - private static Texture Texture; - private static Shader Shader; - private static List Models = []; + private static IWindow? window; + private static GL? Gl; + private static IKeyboard? primaryKeyboard; + + private static Texture? Texture; + private static Shader? Shader; + private static readonly List _models = []; //Setup the camera's location, directions, and movement speed private static Vector3 CameraPosition = new(0.0f, 0.0f, 3.0f); @@ -37,6 +42,7 @@ public static class Program //Used to track change in mouse movement to allow for moving of the Camera private static Vector2 LastMousePosition; + #endregion private static void Main(string[] _) @@ -59,11 +65,13 @@ private static void Main(string[] _) private static void OnLoad() { + if (window == null) + throw new NullReferenceException("Window is not initialized."); + IInputContext input = window.CreateInput(); primaryKeyboard = input.Keyboards[0]; - if (primaryKeyboard != null) - primaryKeyboard.KeyDown += KeyDown; + primaryKeyboard?.KeyDown += KeyDown; for (int i = 0; i < input.Mice.Count; i++) { @@ -79,12 +87,15 @@ private static void OnLoad() var model = ObjLoaderFactory.Load(Gl, "Untitled2.obj"); - Models.Add(model); - Models.Add(model); + _models.Add(model); + _models.Add(model); } private static void OnUpdate(double deltaTime) { + if (primaryKeyboard == null) + throw new NullReferenceException("Primary keyboard is not initialized."); + var moveSpeed = 2.5f * (float) deltaTime; //Move forwards @@ -106,6 +117,12 @@ private static void OnUpdate(double deltaTime) private static void OnRender(double deltaTime) { + if (Gl == null) + throw new NullReferenceException("OpenGL context is not initialized."); + + if (window == null) + throw new NullReferenceException("Window is not initialized."); + Gl.Enable(EnableCap.DepthTest); Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); @@ -121,17 +138,26 @@ private static void OnRender(double deltaTime) //Note that the aspect ratio calculation must be performed as a float, otherwise integer division will be performed (truncating the result). var projection = Matrix4x4.CreatePerspectiveFieldOfView(SharpEngine.Core.Math.DegreesToRadians(CameraZoom), (float)size.X / size.Y, 0.1f, 100.0f); - for (int i = 0; i < Models.Count; i++) + for (int i = 0; i < _models.Count; i++) { if (i >= 1) modelMatrix = Matrix4x4.CreateTranslation(0, -30.0f, 0.0f) * modelMatrix; - RenderModel(Models[i], modelMatrix, view, projection); + RenderModel(_models[i], modelMatrix, view, projection); } } - private static void RenderModel(Model_Old model, Matrix4x4 modelMatrix, Matrix4x4 view, Matrix4x4 projection) + private static void RenderModel(Model model, Matrix4x4 modelMatrix, Matrix4x4 view, Matrix4x4 projection) { + if (Texture == null) + throw new NullReferenceException("Texture is not initialized."); + + if (Shader == null) + throw new NullReferenceException("Shader is not initialized."); + + if (Gl == null) + throw new NullReferenceException("OpenGL context is not initialized."); + foreach (var mesh in model.Meshes) { mesh.Bind(); @@ -148,7 +174,7 @@ private static void RenderModel(Model_Old model, Matrix4x4 modelMatrix, Matrix4x } } - private static void OnFramebufferResize(Vector2D newSize) => Gl.Viewport(newSize); + private static void OnFramebufferResize(Vector2D newSize) => Gl!.Viewport(newSize); private static void OnMouseMove(IMouse mouse, Vector2 position) { @@ -183,18 +209,18 @@ private static void OnMouseWheel(IMouse mouse, ScrollWheel scrollWheel) private static void OnClose() { - foreach (var model in Models) + foreach (var model in _models) model.Dispose(); - Shader.Dispose(); - Texture.Dispose(); + Shader?.Dispose(); + Texture?.Dispose(); } private static void KeyDown(IKeyboard keyboard, Key key, int arg3) { if (key == Key.Escape) { - window.Close(); + window!.Close(); } } } diff --git a/Engine/Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj b/Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj similarity index 56% rename from Engine/Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj rename to Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj index 079bf1a..bbd8dbb 100644 --- a/Engine/Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj +++ b/Examples/Tutorial 4.1 - Model Loading/Tutorial 4.1 - Model Loading.csproj @@ -2,22 +2,10 @@ Exe - net8.0 + $(DotNetTargetFramework) Tutorial - - - - - - - diff --git a/Engine/Examples/Tutorial 4.1 - Model Loading/shader2.vert b/Examples/Tutorial 4.1 - Model Loading/shader2.vert similarity index 100% rename from Engine/Examples/Tutorial 4.1 - Model Loading/shader2.vert rename to Examples/Tutorial 4.1 - Model Loading/shader2.vert diff --git a/Engine/Examples/Tutorial 4.1 - Model Loading/silk.png b/Examples/Tutorial 4.1 - Model Loading/silk.png similarity index 100% rename from Engine/Examples/Tutorial 4.1 - Model Loading/silk.png rename to Examples/Tutorial 4.1 - Model Loading/silk.png diff --git a/ObjLoader/DataStore.cs b/ObjLoader/DataStore.cs new file mode 100644 index 0000000..5833552 --- /dev/null +++ b/ObjLoader/DataStore.cs @@ -0,0 +1,83 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.Components.Properties; +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Shared.Extensions; + +using System.Collections.Generic; +using System.Linq; + +namespace SharpEngine.Core.ObjLoader +{ + /// + /// Stores parsed OBJ file data such as vertices, normals, texture coordinates, materials and groups. + /// This type is used by the OBJ loader type parsers to collect geometry and material information. + /// + /// + /// Stores parsed OBJ file data such as vertices, normals, texture coordinates, materials and groups. + /// This type is used by the OBJ loader type parsers to collect geometry and material information. + /// + public class DataStore : IGroupDataStore, IFaceGroup, ITextureDataStore, INormalDataStore, IVertexDataStore, IMaterialDataStore + { + private Group _currentGroup = null!; + + /// Gets the list of parsed vertices. + public List Vertices { get; } = []; + + /// Gets the list of parsed texture coordinates. + public List Textures { get; } = []; + + /// Gets the list of parsed normals. + public List Normals { get; } = []; + + /// Gets the collection of materials discovered in the file. + public List Materials { get; } = []; + + /// Gets the list of groups present in the file. + public List Groups { get; } = []; + + /// + /// Initializes a new instance of and creates a default group. + /// + public DataStore() + { + PushGroup("default"); + } + + /// + /// Adds a parsed face to the active group. + /// + /// The parsed face to add. + public void AddFace(Face face) + => _currentGroup.AddFace(face); + + /// + /// Creates and activates a new group with the given name. + /// + /// The name of the group to create. + public void PushGroup(string groupName) + { + _currentGroup = new Group(groupName); + Groups.Add(_currentGroup); + } + + /// + public void SetMaterial(string materialName) + { + // TOOD: This should probably throw or handle the case where the material is not found, but for now we'll just set it to null. + var material = Materials.SingleOrDefault(x => x.Name.EqualsOrdinalIgnoreCase(materialName)); + _currentGroup.Material = material; + } + + /// + public void AddTexture(TextureCoordinate texture) + => Textures.Add(texture); + + /// + public void AddNormal(Normal normal) + => Normals.Add(normal); + + /// + public void AddVertex(Vertex vertex) + => Vertices.Add(vertex); + } +} \ No newline at end of file diff --git a/ObjLoader/IMaterialDataStore.cs b/ObjLoader/IMaterialDataStore.cs new file mode 100644 index 0000000..38f3168 --- /dev/null +++ b/ObjLoader/IMaterialDataStore.cs @@ -0,0 +1,14 @@ +namespace SharpEngine.Core.ObjLoader; + +/// +/// Contains definitions for the material data store, which provides methods to manage material information during the OBJ file loading process. +/// +public interface IMaterialDataStore +{ + /// + /// Sets the material for the current group. + /// + /// The name of the material to set. + public void SetMaterial(string materialName); + +} \ No newline at end of file diff --git a/ObjLoader/Loaders/LoaderBase.cs b/ObjLoader/Loaders/LoaderBase.cs new file mode 100644 index 0000000..4ecbabc --- /dev/null +++ b/ObjLoader/Loaders/LoaderBase.cs @@ -0,0 +1,50 @@ +using System.IO; + +namespace SharpEngine.Core.ObjLoader.Loader.Loaders +{ + /// + /// Represents a base class for loading .obj and .mtl files, providing common line parsing functionality. + /// + public abstract class LoaderBase + { + /// + /// Parses the file at the specified path and processes each line via . + /// + /// + /// I/O exceptions (for example FileNotFoundException, UnauthorizedAccessException, IOException) are propagated to the caller. + /// + /// The path of the file to open and parse. + public void ParseFile(string path) + { + using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var lineStreamReader = new StreamReader(fileStream); + + while (!lineStreamReader.EndOfStream) + { + var currentLine = lineStreamReader.ReadLine(); + + if (!string.IsNullOrWhiteSpace(currentLine)) + ParseLine(currentLine); + } + } + + private void ParseLine(string currentLine) + { + if (currentLine[0] == '#') + return; + + var fields = currentLine.Trim().Split(null, 2); + var keyword = fields[0].Trim(); + var data = fields[1].Trim(); + + ParseLine(keyword, data); + } + + /// + /// Parses a single line consisting of a keyword and its associated data. + /// + /// Keyword that identifies the line type or command. + /// Data associated with the keyword; may be empty or contain parameters or raw text to interpret. + protected abstract void ParseLine(string keyword, string data); + } +} \ No newline at end of file diff --git a/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs b/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs new file mode 100644 index 0000000..5d4cde3 --- /dev/null +++ b/ObjLoader/Loaders/MaterialLoader/IMaterialLibraryLoaderFacade.cs @@ -0,0 +1,14 @@ +namespace SharpEngine.Core.ObjLoader.Loaders.MaterialLoader +{ + /// + /// Provides a facade for loading material library files into the application's material system. + /// + public interface IMaterialLibraryLoaderFacade + { + /// + /// Loads the specified material file into the application's material system. + /// + /// The name of the material file to load. + void Load(string materialFileName); + } +} \ No newline at end of file diff --git a/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs b/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs new file mode 100644 index 0000000..39aa7d3 --- /dev/null +++ b/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs @@ -0,0 +1,109 @@ +using SharpEngine.Core.Components.Properties; +using SharpEngine.Core.ObjLoader.Loader.Loaders; +using SharpEngine.Shared.Extensions; + +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace SharpEngine.Core.ObjLoader.Loaders.MaterialLoader +{ + // https://paulbourke.net/dataformats/mtl/ + + /// + /// Handles loading of material libraries (.mtl files) referenced by .obj files. + /// + /// + /// Parses material properties and texture maps, and stores them in the provided . + /// Also handles opening material library files based on the path of the .obj file. + /// + public class MaterialLibraryLoader : LoaderBase + { + private readonly DataStore _dataStore; + private readonly Dictionary> _parseActionDictionary = []; + private readonly List _unrecognizedLines = []; + + private Material _currentMaterial = new(string.Empty); + + // TODO: #2 See Model class. This support both materials and textures + + /// + /// Initializes a new instance of and registers parse actions for .mtl directives. + /// + /// + /// Registers parse actions for common .mtl directives (for example: newmtl, Ka, Kd, Ks, + /// Ns, d, Tr, illum, map_*, bump, disp, decal) to populate Material instances. + /// + /// DataStore used to register and store parsed materials and texture references. + public MaterialLibraryLoader(DataStore dataStore) + { + _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, out bool found); + + if (!found) + { + _unrecognizedLines.Add(keyword + " " + data); + return; + } + + parseAction!(data); + } + + private Action? GetKeywordAction(string keyword, out bool found) + { + found = _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); + } + } +} \ No newline at end of file diff --git a/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs b/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs new file mode 100644 index 0000000..9ce3ee6 --- /dev/null +++ b/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoaderFacade.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging; +using SharpEngine.Telemetry; +using System.IO; + +namespace SharpEngine.Core.ObjLoader.Loaders.MaterialLoader +{ + /// + /// A facade for the to provide a simplified interface for loading material libraries. + /// + public class MaterialLibraryLoaderFacade : IMaterialLibraryLoaderFacade + { + private readonly ILogger _logger; + private readonly MaterialLibraryLoader _loader; + private readonly string _path; + + /// + /// Initializes a new instance of . + /// + /// Provides the functionality to load material libraries. + /// Optional path where the material file should exist. + /// The logger to use for logging messages. + public MaterialLibraryLoaderFacade(MaterialLibraryLoader loader, string path, ILogger? logger = null) + { + _loader = loader; + _path = path; + + _logger = logger ?? LoggingExtensions.CreateLogger(); + } + + /// + public void Load(string materialFileName) + { + string materialFilePath = Path.Combine(Path.GetDirectoryName(_path)!, materialFileName); + if (!File.Exists(materialFilePath)) + { + _logger.LogWarning("Material file '{MaterialFileName}' doesn't exist.", materialFileName); + return; + } + + _loader.ParseFile(materialFilePath); + } + } +} \ No newline at end of file diff --git a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs b/ObjLoader/Loaders/ObjLoader/ObjLoader.cs similarity index 54% rename from Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs rename to ObjLoader/Loaders/ObjLoader/ObjLoader.cs index 798bc3c..cfcd5f0 100644 --- a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs +++ b/ObjLoader/Loaders/ObjLoader/ObjLoader.cs @@ -1,14 +1,14 @@ -using ObjLoader.Loader.Loaders; -using ObjLoader.TypeParsers; -using SharpEngine.Core.Entities.Properties.Meshes; -using Silk.NET.Core.Contexts; +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.ObjLoader.Loader.Loaders; +using SharpEngine.Core.ObjLoader.TypeParsers; using Silk.NET.OpenGL; using System.Collections.Generic; -using System.IO; -using Tutorial; -namespace ObjLoader.Loaders.ObjLoader +namespace SharpEngine.Core.ObjLoader.Loaders.ObjLoader { + /// + /// Loads an OBJ model file and converts its parsed data into runtime Mesh instances. + /// public class ObjLoader : LoaderBase { private readonly string _path; @@ -16,12 +16,22 @@ public class ObjLoader : LoaderBase private readonly List _typeParsers = []; private readonly List _unrecognizedLines = []; + /// + /// Initializes a new instance of with the specified file path and data store. + /// + /// The path to the OBJ file. + /// The data store to populate during parsing. public ObjLoader(string path, DataStore dataStore) { _path = path; _dataStore = dataStore; } + /// + /// Adds the provided type parsers to the loader's internal list, enabling it to recognize and parse different line types in the OBJ file. + /// + /// The type parsers to add. + /// The current instance of . public ObjLoader SetupTypeParsers(params ITypeParser[] parsers) { foreach (var parser in parsers) @@ -43,10 +53,13 @@ protected override void ParseLine(string keyword, string data) _unrecognizedLines.Add(keyword + " " + data); } + /// + /// Loads the OBJ file and produces a list of objects suitable for rendering. + /// + /// The OpenGL context used to construct GPU resources. public List Load(GL gl) { - var fileStream = new FileStream(_path, FileMode.Open, FileAccess.Read); - StartLoad(fileStream); + ParseFile(_path); return [ diff --git a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs b/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs similarity index 61% rename from Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs rename to ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs index 408855c..5a7d90c 100644 --- a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs +++ b/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs @@ -1,14 +1,15 @@ -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 +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Components.Properties.Meshes; +using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Loaders.ObjLoader { /// /// Handles loading 3D models. @@ -22,10 +23,10 @@ public static class ObjLoaderFactory /// 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. + /// 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) + public static Model Load(GL gl, string path) => Path.GetExtension(path) switch { FbxExtension => LoadFbx("", path), @@ -34,18 +35,20 @@ public static Model_Old Load(GL gl, string path) }; // TODO: #3 Load fbx mesh from file - private static Model_Old LoadFbx(string identifier, string meshFilePath) + private static Model LoadFbx(string identifier, string meshFilePath) { throw new NotImplementedException(); } - private static Model_Old LoadObj(GL gl, string path) + private static Model 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 meshes = LoadObjMeshes(gl, path); + return new Model(gl, path, meshes); + } - /*var dataStore = new DataStore(); + private static List LoadObjMeshes(GL gl, string path) + { + var dataStore = new DataStore(); var faceParser = new FaceParser(dataStore); var groupParser = new GroupParser(dataStore); @@ -53,13 +56,16 @@ private static Model_Old LoadObj(GL gl, string path) var textureParser = new TextureParser(dataStore); var vertexParser = new VertexParser(dataStore); - var materialLibraryLoader = new MaterialLibraryLoader(path, dataStore); - var materialLibraryParser = new MaterialLibraryParser(materialLibraryLoader); + var materialLibraryLoader = new MaterialLibraryLoader(dataStore); + + var materialLoader = new MaterialLibraryLoaderFacade(materialLibraryLoader, path); + var materialLibraryParser = new MaterialLibraryParser(materialLoader); var useMaterialParser = new UseMaterialParser(dataStore); - var loader = new ObjLoader(path, dataStore).SetupTypeParsers(faceParser, groupParser, normalParser, textureParser, vertexParser, materialLibraryParser, useMaterialParser); + 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/ObjLoader/SharpEngine.Core.ObjLoader.csproj similarity index 60% rename from Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj rename to ObjLoader/SharpEngine.Core.ObjLoader.csproj index 7cc24cf..7a52e96 100644 --- a/Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj +++ b/ObjLoader/SharpEngine.Core.ObjLoader.csproj @@ -1,16 +1,16 @@ - + - net8.0 + $(DotNetTargetFramework) - + - + \ No newline at end of file diff --git a/Engine/ObjLoader/TypeParsers/FaceParser.cs b/ObjLoader/TypeParsers/FaceParser.cs similarity index 73% rename from Engine/ObjLoader/TypeParsers/FaceParser.cs rename to ObjLoader/TypeParsers/FaceParser.cs index c7328f7..0df5e68 100644 --- a/Engine/ObjLoader/TypeParsers/FaceParser.cs +++ b/ObjLoader/TypeParsers/FaceParser.cs @@ -1,15 +1,23 @@ -using ObjLoader.Loader.Common; -using ObjLoader.Loader.Data.Elements; -using ObjLoader.TypeParsers; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.TypeParsers; +using SharpEngine.Shared.Extensions; + using System; -namespace ObjLoader.Loader.TypeParsers +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers { + /// + /// Parses face definitions ("f") and populates the current group's faces. + /// public class FaceParser : TypeParserBase, ITypeParser { private readonly IFaceGroup _faceGroup; + /// + /// Initializes a new instance of the . + /// + /// public FaceParser(IFaceGroup faceGroup) { _faceGroup = faceGroup; diff --git a/ObjLoader/TypeParsers/GroupParser.cs b/ObjLoader/TypeParsers/GroupParser.cs new file mode 100644 index 0000000..e5dae69 --- /dev/null +++ b/ObjLoader/TypeParsers/GroupParser.cs @@ -0,0 +1,29 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.ObjLoader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Parses group definition lines ("g") and pushes a new group into the data store. + /// + public class GroupParser : TypeParserBase, ITypeParser + { + private readonly IGroupDataStore _dataStore; + + /// + /// Initializes a new instance of with the specified data store to populate during parsing. + /// + /// The data store to populate. + public GroupParser(IGroupDataStore 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/ObjLoader/TypeParsers/ITypeParser.cs b/ObjLoader/TypeParsers/ITypeParser.cs new file mode 100644 index 0000000..1b041ba --- /dev/null +++ b/ObjLoader/TypeParsers/ITypeParser.cs @@ -0,0 +1,21 @@ +namespace SharpEngine.Core.ObjLoader.TypeParsers +{ + /// + /// Defines a parser for handling different types of OBJ file lines. + /// + public interface ITypeParser + { + /// + /// Determines whether the specified keyword can be parsed. + /// + /// The keyword to check. + /// True if the keyword can be parsed; otherwise, false. + bool CanParse(string keyword); + + /// + /// Parses a single input line and updates the object's state accordingly. + /// + /// The input line to parse. + void Parse(string line); + } +} \ No newline at end of file diff --git a/ObjLoader/TypeParsers/MaterialLibraryParser.cs b/ObjLoader/TypeParsers/MaterialLibraryParser.cs new file mode 100644 index 0000000..f622cc8 --- /dev/null +++ b/ObjLoader/TypeParsers/MaterialLibraryParser.cs @@ -0,0 +1,28 @@ +using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader; +using SharpEngine.Core.ObjLoader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Represents a parser for material library definitions in OBJ files, responsible for parsing lines that specify material libraries and delegating the loading of the material library to the provided loader facade. + /// + public class MaterialLibraryParser : TypeParserBase, ITypeParser + { + private readonly IMaterialLibraryLoaderFacade _libraryLoaderFacade; + + /// + /// Initializes a new instance of the . + /// + /// The material library loader facade. + public MaterialLibraryParser(IMaterialLibraryLoaderFacade facade) + { + _libraryLoaderFacade = facade; + } + + /// + protected override string Keyword => "mtllib"; + + /// + public override void Parse(string line) => _libraryLoaderFacade.Load(line); + } +} \ No newline at end of file diff --git a/ObjLoader/TypeParsers/NormalParser.cs b/ObjLoader/TypeParsers/NormalParser.cs new file mode 100644 index 0000000..af7974b --- /dev/null +++ b/ObjLoader/TypeParsers/NormalParser.cs @@ -0,0 +1,40 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.TypeParsers; +using SharpEngine.Shared.Extensions; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Represents a parser for normal vector definitions in an OBJ file, responsible for parsing lines that define vertex normals and storing them in the data store. + /// + public class NormalParser : TypeParserBase, ITypeParser + { + private readonly INormalDataStore _dataStore; + + /// + /// Initializes a new instance of the . + /// + /// The data store to which the parsed normals will be added. + public NormalParser(INormalDataStore 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.AddNormal(normal); + } + } +} \ No newline at end of file diff --git a/ObjLoader/TypeParsers/TextureParser.cs b/ObjLoader/TypeParsers/TextureParser.cs new file mode 100644 index 0000000..43a2e8e --- /dev/null +++ b/ObjLoader/TypeParsers/TextureParser.cs @@ -0,0 +1,39 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.TypeParsers; +using SharpEngine.Shared.Extensions; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Represents a parser for texture coordinate definitions in an OBJ file, responsible for parsing lines that define texture coordinates and storing them in the data store. + /// + public class TextureParser : TypeParserBase, ITypeParser + { + private readonly ITextureDataStore _dataStore; + + /// + /// Initializes a new instance of the . + /// + /// The data store to which the parsed texture coordinates will be added. + public TextureParser(ITextureDataStore 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.AddTexture(texture); + } + } +} \ No newline at end of file diff --git a/ObjLoader/TypeParsers/TypeParserBase.cs b/ObjLoader/TypeParsers/TypeParserBase.cs new file mode 100644 index 0000000..7410790 --- /dev/null +++ b/ObjLoader/TypeParsers/TypeParserBase.cs @@ -0,0 +1,22 @@ +using SharpEngine.Core.ObjLoader.TypeParsers; +using SharpEngine.Shared.Extensions; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Base implementation for OBJ file line type parsers. Provides a simple keyword matching helper. + /// + public abstract class TypeParserBase : ITypeParser + { + /// + /// The keyword this parser recognizes (e.g. "v", "vn", "f"). + /// + 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/ObjLoader/TypeParsers/UseMaterialParser.cs b/ObjLoader/TypeParsers/UseMaterialParser.cs new file mode 100644 index 0000000..aa4d6eb --- /dev/null +++ b/ObjLoader/TypeParsers/UseMaterialParser.cs @@ -0,0 +1,28 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.ObjLoader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers +{ + /// + /// Parses material usage lines ("usemtl") and assigns materials to the current group. + /// + public class UseMaterialParser : TypeParserBase, ITypeParser + { + private readonly IMaterialDataStore _dataStore; + + /// + /// Initializes a new instance of with the specified data store. + /// + /// The data store to use. + public UseMaterialParser(IMaterialDataStore 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/ObjLoader/TypeParsers/VertexParser.cs similarity index 52% rename from Engine/ObjLoader/TypeParsers/VertexParser.cs rename to ObjLoader/TypeParsers/VertexParser.cs index d6b4fcf..94159b3 100644 --- a/Engine/ObjLoader/TypeParsers/VertexParser.cs +++ b/ObjLoader/TypeParsers/VertexParser.cs @@ -1,16 +1,24 @@ -using ObjLoader.Loader.Common; -using ObjLoader.TypeParsers; +using SharpEngine.Core.Components.ObjLoader.DataStore; using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.TypeParsers; +using SharpEngine.Shared.Extensions; + using System; -using Tutorial; -namespace ObjLoader.Loader.TypeParsers +namespace SharpEngine.Core.ObjLoader.Loader.TypeParsers { + /// + /// Parses vertex position lines ("v x y z") and adds them to the data store. + /// public class VertexParser : TypeParserBase, ITypeParser { - private readonly DataStore _dataStore; + private readonly IVertexDataStore _dataStore; - public VertexParser(DataStore dataStore) + /// + /// Initializes a new instance of with the specified data store to populate. + /// + /// The data store to populate. + public VertexParser(IVertexDataStore dataStore) { _dataStore = dataStore; } @@ -31,7 +39,8 @@ public override void Parse(string line) { Position = new System.Numerics.Vector3(x, y, z), }; - _dataStore.Vertices.Add(vertex); + + _dataStore.AddVertex(vertex); } } } \ No newline at end of file diff --git a/Engine/SharpEngine.Core.Components/IComponent.cs b/SharpEngine.Core.Components/IComponent.cs similarity index 54% rename from Engine/SharpEngine.Core.Components/IComponent.cs rename to SharpEngine.Core.Components/IComponent.cs index fdeaa85..5a744ba 100644 --- a/Engine/SharpEngine.Core.Components/IComponent.cs +++ b/SharpEngine.Core.Components/IComponent.cs @@ -8,6 +8,10 @@ namespace SharpEngine.Core.Components; // TODO: #53 Entity Component System +/// +/// Represents a generic component used by the entity component system. +/// This interface is intentionally empty and serves as a marker for component types. +/// internal interface IComponent { } diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/IDataStore.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/IDataStore.cs new file mode 100644 index 0000000..ddb9558 --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/IDataStore.cs @@ -0,0 +1,10 @@ +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Represents a data store that can be used to store and retrieve data related to the OBJ file loading process, such as vertex data, material information, and other relevant details. + /// This interface can be implemented by various classes to provide different storage mechanisms (e.g., in-memory, file-based, database) for managing the data during the OBJ loading process. + /// + public interface IDataStore + { + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/IFaceGroup.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/IFaceGroup.cs new file mode 100644 index 0000000..1ccee9c --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/IFaceGroup.cs @@ -0,0 +1,19 @@ +using SharpEngine.Core.Components.Properties.Meshes.MeshData; + +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Contains definitions for the interface, which represents a group of faces in an OBJ model, allowing for the organization and management of faces based on their associated materials or other grouping criteria. + /// + public interface IFaceGroup + { + /// + /// Adds a face to the face group. + /// + /// + /// This method allows for the inclusion of a face, represented by the class, into the group, enabling the organization of faces based on their associated materials or other grouping criteria. + /// + /// The face to add to the group. + void AddFace(Face face); + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/IGroupDataStore.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/IGroupDataStore.cs new file mode 100644 index 0000000..69bc335 --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/IGroupDataStore.cs @@ -0,0 +1,14 @@ +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Defines operations for managing groups of faces while parsing OBJ files. + /// + public interface IGroupDataStore : IDataStore + { + /// + /// Creates and activates a new face group with the provided name. + /// + /// The name of the group to push. + void PushGroup(string groupName); + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/IMaterialLibrary.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/IMaterialLibrary.cs new file mode 100644 index 0000000..7a85ab6 --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/IMaterialLibrary.cs @@ -0,0 +1,16 @@ +using SharpEngine.Core.Components.Properties; + +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Represents a storage for materials discovered while parsing an OBJ's associated MTL file. + /// + public interface IMaterialLibrary + { + /// + /// Adds a material to the library. + /// + /// The material to add. + void Push(Material material); + } +} diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/INormalDataStore.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/INormalDataStore.cs new file mode 100644 index 0000000..6c70e15 --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/INormalDataStore.cs @@ -0,0 +1,16 @@ +using SharpEngine.Core.Components.Properties.Meshes.MeshData; + +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Defines storage operations for normal vector data parsed from an OBJ file. + /// + public interface INormalDataStore + { + /// + /// Adds a normal vector to the data store. + /// + /// The normal vector to add. + void AddNormal(Normal normal); + } +} diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/ITextureDataStore.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/ITextureDataStore.cs new file mode 100644 index 0000000..32ef8fb --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/ITextureDataStore.cs @@ -0,0 +1,16 @@ +using SharpEngine.Core.Components.Properties.Meshes.MeshData; + +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Defines storage operations for texture coordinate data parsed from an OBJ file. + /// + public interface ITextureDataStore + { + /// + /// Adds a texture coordinate to the underlying data store. + /// + /// The texture coordinate to add. + void AddTexture(TextureCoordinate texture); + } +} diff --git a/SharpEngine.Core.Components/ObjLoader/DataStore/IVertexDataStore.cs b/SharpEngine.Core.Components/ObjLoader/DataStore/IVertexDataStore.cs new file mode 100644 index 0000000..b143211 --- /dev/null +++ b/SharpEngine.Core.Components/ObjLoader/DataStore/IVertexDataStore.cs @@ -0,0 +1,16 @@ +using SharpEngine.Core.Components.Properties.Meshes.MeshData; + +namespace SharpEngine.Core.Components.ObjLoader.DataStore +{ + /// + /// Defines storage operations for vertex data parsed from an OBJ file. + /// + public interface IVertexDataStore + { + /// + /// Adds a vertex to the underlying data store. + /// + /// The vertex to add. + void AddVertex(Vertex vertex); + } +} diff --git a/Engine/SharpEngine.Core.Components/Properties/BoundingBox.cs b/SharpEngine.Core.Components/Properties/BoundingBox.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/BoundingBox.cs rename to SharpEngine.Core.Components/Properties/BoundingBox.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/ITransform.cs b/SharpEngine.Core.Components/Properties/ITransform.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/ITransform.cs rename to SharpEngine.Core.Components/Properties/ITransform.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/Material.cs b/SharpEngine.Core.Components/Properties/Material.cs similarity index 69% rename from Engine/SharpEngine.Core.Components/Properties/Material.cs rename to SharpEngine.Core.Components/Properties/Material.cs index 07c21be..5d01401 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Material.cs +++ b/SharpEngine.Core.Components/Properties/Material.cs @@ -8,14 +8,15 @@ namespace SharpEngine.Core.Components.Properties; /// /// Represents the material rendered onto a game object. /// -public class Material +public class Material : ICloneable, IEquatable { /// /// Initializes a new instance of . /// + /// The name assigned to the new material. /// 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) + public Material(string materialName, Texture? diffuseMap = null, Texture? specularMap = null) : this(materialName) { DiffuseMap = diffuseMap; SpecularMap = specularMap ?? diffuseMap; @@ -37,19 +38,19 @@ public Material(string materialName) public string Name { get; set; } /// Gets or sets the diffuse map texture. - public Texture DiffuseMap { get; set; } + public Texture? DiffuseMap { get; set; } /// Gets or sets the path to the diffuse texture map. - public string DiffuseTextureMap { get; set; } + public string? DiffuseTextureMap { get; set; } /// Gets or sets the specular map texture. - public Texture SpecularMap { get; set; } + public Texture? SpecularMap { get; set; } /// Gets or sets the path to the specular texture map. - public string SpecularTextureMap { get; set; } + public string? SpecularTextureMap { get; set; } /// Gets a value indicating whether the material uses a specular map. - public bool UseSpecularMap => SpecularMap.Handle != DiffuseMap.Handle; + public bool UseSpecularMap => SpecularMap is not null && SpecularMap?.Handle != DiffuseMap?.Handle; /// The texture unit for the diffuse map. public const int DIFFUSE_UNIT = 0; @@ -82,22 +83,22 @@ public Material(string materialName) public int IlluminationModel { get; set; } /// Gets or sets the path to the ambient texture map. - public string AmbientTextureMap { get; set; } + public string? AmbientTextureMap { get; set; } /// Gets or sets the path to the specular highlight texture map. - public string SpecularHighlightTextureMap { get; set; } + public string? SpecularHighlightTextureMap { get; set; } /// Gets or sets the path to the bump map. - public string BumpMap { get; set; } + public string? BumpMap { get; set; } /// Gets or sets the path to the displacement map. - public string DisplacementMap { get; set; } + public string? DisplacementMap { get; set; } /// Gets or sets the path to the stencil decal map. - public string StencilDecalMap { get; set; } + public string? StencilDecalMap { get; set; } /// Gets or sets the path to the alpha texture map. - public string AlphaTextureMap { get; set; } + public string? AlphaTextureMap { get; set; } /// /// Sets the uniform values for the material in the specified shader. @@ -107,21 +108,50 @@ 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 (DiffuseMap is not null) + { + DiffuseMap.Use(TextureUnit.Texture0); + shader.SetInt("material.diffuse", DIFFUSE_UNIT); + } - if (UseSpecularMap) + if (SpecularMap is not null) { 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.SetInt("material.specular", DIFFUSE_UNIT); shader.SetFloat("material.shininess", 0); } } + + /// + public object Clone() + => MemberwiseClone(); + + /// + public override bool Equals(object? obj) + { + if (obj is null) + return false; + + if (ReferenceEquals(this, obj)) + return true; + + if (GetType() != obj.GetType()) + return false; + + return true; + } + + /// + public bool Equals(Material? other) => Equals(other); + + /// + public override int GetHashCode() + { + throw new NotImplementedException(); + } } diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs b/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs similarity index 83% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs rename to SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs index 1279b1d..9035976 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs +++ b/SharpEngine.Core.Components/Properties/Meshes/BufferObject.cs @@ -1,12 +1,12 @@ using Silk.NET.OpenGL; using System.Runtime.InteropServices; -namespace SharpEngine.Core.Components.Properties.Meshes.MeshData +namespace SharpEngine.Core.Components.Properties.Meshes { /// - /// Represents a buffer object in OpenGL that manages memory allocation and data transfer for a specific data type. + /// Represents an OpenGL buffer object that manages memory allocation and data transfer for a specific value type. /// - /// Specifies the type of data stored in the buffer, which must be an unmanaged type for proper memory handling. + /// The unmanaged element type stored in the buffer. public class BufferObject : IDisposable where TDataType : unmanaged { private readonly uint _handle; diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs b/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs similarity index 92% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs rename to SharpEngine.Core.Components/Properties/Meshes/Mesh.cs index e6f66fc..bf63131 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs +++ b/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs @@ -1,17 +1,15 @@ -using ObjLoader.Loader.Data.Elements; -using SharpEngine.Core.Attributes; +using SharpEngine.Core.Attributes; using SharpEngine.Core.Components.Properties; +using SharpEngine.Core.Components.Properties.Meshes; using SharpEngine.Core.Components.Properties.Meshes.MeshData; using Silk.NET.OpenGL; -using Tutorial; - +using System.Diagnostics.CodeAnalysis; 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. +/// Represents a mesh composed of vertex data, indices, textures and materials which can be uploaded to GPU buffers. /// public class Mesh : IDisposable { @@ -74,17 +72,17 @@ public class Mesh : IDisposable /// /// Gets or sets the textures used by the mesh. /// - public IReadOnlyList Textures { get; set; } + public IReadOnlyList Textures { get; set; } = []; /// /// Gets or sets the legacy textures used by the mesh. /// - public IReadOnlyList Textures_Old { get; set; } + public IReadOnlyList Textures_Old { get; set; } = []; /// /// Gets or sets the Vertex Array Object (VAO) for the mesh. /// - public VertexArrayObject VAO { get; set; } + public VertexArrayObject VAO { get; private set; } /// /// Gets or sets the Vertex Buffer Object (VBO) for the mesh. @@ -146,6 +144,7 @@ public Mesh(GL gl) /// /// Allocates the required memory for the mesh and sets up the Vertex Array Object (VAO), Vertex Buffer Object (VBO), and Element Buffer Object (EBO). /// + [MemberNotNull(nameof(EBO), nameof(VBO), nameof(VAO))] public void SetupMesh() { EBO = new BufferObject(GL, Indices, BufferTargetARB.ElementArrayBuffer); diff --git a/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs new file mode 100644 index 0000000..39b7b76 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Face.cs @@ -0,0 +1,59 @@ +namespace SharpEngine.Core.Components.Properties.Meshes.MeshData +{ + /// + /// Represents a face in a 3D mesh, which is defined by a list of vertices. + /// + /// + /// Each vertex is represented by a struct that contains indices for the vertex position, texture coordinate, and normal vector. + /// + public class Face + { + private readonly List _vertices = []; + + /// + /// Adds a vertex to the face. + /// + /// The vertex to add. + public void AddVertex(FaceVertex vertex) => _vertices.Add(vertex); + + /// + /// An indexer to access the vertices of the face by their index. + /// + /// The index of the vertex to access. + /// The vertex at the specified index. + public FaceVertex this[int i] => _vertices[i]; + + /// Gets the number of vertices in the face. + public int Count => _vertices.Count; + } + + /// + /// Represents a vertex in a face, which contains indices for the vertex position, texture coordinate, and normal vector. + /// + public struct FaceVertex + { + /// + /// Initializes a new instance of the . + /// + /// The index of the vertex position. + /// The index of the texture coordinate. + /// The index of the normal vector. + public FaceVertex(int vertexIndex, int textureIndex, int normalIndex) + { + VertexIndex = vertexIndex; + TextureIndex = textureIndex; + NormalIndex = normalIndex; + } + + /// + /// Gets or sets the index of the vertex position in the mesh's vertex list. + /// + public int VertexIndex { get; set; } + + /// Gets or sets the index of the texture coordinate in the mesh's texture coordinate list. + public int TextureIndex { get; set; } + + /// Gets or sets the index of the normal vector in the mesh's normal vector list. + public int NormalIndex { get; set; } + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs new file mode 100644 index 0000000..504d811 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Group.cs @@ -0,0 +1,38 @@ +using SharpEngine.Core.Components.ObjLoader.DataStore; + +namespace SharpEngine.Core.Components.Properties.Meshes.MeshData +{ + /// + /// Represents a group of faces in a mesh, which can be used to organize faces that share the same material or other properties. + /// + public class Group : IFaceGroup + { + private readonly List _faces = []; + + /// + /// Initializes a new instance of the . + /// + /// The name of the group. + public Group(string name) + { + Name = name; + } + + /// + /// Gets the name of the group, which can be used to identify the group and associate it with specific materials or properties in the mesh. + /// + public string Name { get; private set; } + + /// Gets or sets the material associated with the group, which defines the appearance of the faces in the group when rendered. + public Material? Material { get; set; } + + /// Gets the list of faces that belong to the group. + public IList Faces => _faces; + + /// + /// Adds a face to the group. + /// + /// The face to add to the group. + public void AddFace(Face face) => _faces.Add(face); + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs new file mode 100644 index 0000000..44e220f --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Normal.cs @@ -0,0 +1,36 @@ +namespace SharpEngine.Core.Components.Properties.Meshes.MeshData +{ + /// + /// Represents a normal vector in 3D space, which is used in mesh data to define the direction perpendicular to the surface of a triangle, affecting how light interacts with the surface for rendering purposes. + /// + public struct Normal + { + /// + /// Initializes a new instance of the struct with the specified x, y, and z components, representing the normal vector's direction in 3D space. + /// + /// The x-component of the normal vector. + /// The y-component of the normal vector. + /// The z-component of the normal vector. + public Normal(float x, float y, float z) : this() + { + X = x; + Y = y; + Z = z; + } + + /// + /// Gets the x-component of the normal vector, representing the horizontal direction in 3D space. + /// + public float X { get; private set; } + + /// + /// Gets the y-component of the normal vector, representing the vertical direction in 3D space. + /// + public float Y { get; private set; } + + /// + /// Gets the z-component of the normal vector, representing the depth direction in 3D space. + /// + public float Z { get; private set; } + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs new file mode 100644 index 0000000..c997bf8 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshData/TextureCoordinate.cs @@ -0,0 +1,36 @@ +namespace SharpEngine.Core.Components.Properties.Meshes.MeshData +{ + /// + /// Represents a texture coordinate, which is used to map textures onto the surface of a 3D model. + /// Each texture coordinate consists of two components: X and Y, which range from 0 to 1, where (0,0) corresponds to the bottom-left corner of the texture and (1,1) corresponds to the top-right corner. + /// + public struct TextureCoordinate + { + /// + /// Initializes a new instance of the struct with the specified X and Y values. + /// + /// The X-component of the texture coordinate. + /// The Y-component of the texture coordinate. + public TextureCoordinate(float x, float y) : this() + { + X = x; + Y = y; + } + + /// + /// Gets the X-component of the texture coordinate, which represents the horizontal position on the texture map. + /// + /// + /// The value ranges from 0 to 1, where 0 corresponds to the left edge of the texture and 1 corresponds to the right edge. + /// + public float X { get; private set; } + + /// + /// Gets the Y-component of the texture coordinate, which represents the vertical position on the texture map. + /// + /// + /// The value ranges from 0 to 1, where 0 corresponds to the bottom edge of the texture and 1 corresponds to the top edge. + /// + public float Y { get; private set; } + } +} \ No newline at end of file diff --git a/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs new file mode 100644 index 0000000..9a5bdfd --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshData/Vertex.cs @@ -0,0 +1,56 @@ +// 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 +{ + /// + /// Represents a vertex in a 3D mesh. + /// + public struct Vertex + { + /// + /// The position of the vertex in 3D space, represented as a containing the X, Y, and Z coordinates. + /// + public Vector3 Position; + + /// + /// The normal vector at the vertex, which is used for lighting calculations to determine how light interacts with the surface of the mesh. + /// + public Vector3 Normal; + + /// + /// The tangent vector at the vertex, which is used in conjunction with the normal and bitangent vectors to create a tangent space for normal mapping and other advanced shading techniques. + /// + public Vector3 Tangent; + + /// + /// The texture coordinates (UV coordinates) for the vertex, represented as a containing the U and V coordinates that map the vertex to a specific point on a texture image. + /// + public Vector2 TexCoords; + + /// + /// The bitangent vector at the vertex, which is used in conjunction with the normal and tangent vectors to create a tangent space for normal mapping and other advanced shading techniques. + /// + public Vector3 Bitangent; + + // TODO: #65 Skeletal mesh + + /// + /// The maximum number of bone influences that can affect a single vertex, which is typically set to 4 in many 3D graphics applications to balance performance and visual quality when animating skeletal meshes. + /// + public const int MAX_BONE_INFLUENCE = 4; + + /// + /// The IDs of the bones that influence this vertex, where each ID corresponds to a specific bone in the skeleton of a skeletal mesh. + /// The length of this array should not exceed to ensure efficient processing during animation. + /// + public int[] BoneIds; + + /// + /// The weights corresponding to each bone influence on this vertex, where each weight represents the degree of influence that a particular bone has on the vertex's position during skeletal animation. + /// + public float[] Weights; + } +} diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs b/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs similarity index 93% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs rename to SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs index b28219a..908e955 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs +++ b/SharpEngine.Core.Components/Properties/Meshes/MeshExtensions.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace SharpEngine.Core.Entities.Properties.Meshes; +namespace SharpEngine.Core.Entities.Properties.Meshes; /// /// Contains extension methods for handling meshes. diff --git a/SharpEngine.Core.Components/Properties/Meshes/Model.cs b/SharpEngine.Core.Components/Properties/Meshes/Model.cs new file mode 100644 index 0000000..e5ba6b5 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Meshes/Model.cs @@ -0,0 +1,304 @@ +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.Entities.Properties.Meshes; +using EngineTexture = SharpEngine.Core.Components.Properties.Textures.Texture; +using EngineTextureType = SharpEngine.Core.Components.Properties.Textures.TextureType; + +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Components.Properties.Meshes +{ + /// + /// Represents a 3D model, which may consist of multiple meshes, materials, and textures. + /// + /// + /// This class is responsible for processing raw mesh data into a format suitable for rendering, including expanding vertices based on face definitions and building materials and textures as needed. + /// + public class Model : IDisposable + { + private readonly GL _gl; + + /// Gets the file path to where the model is stored. + public string Path { get; } + + /// Gets the list of meshes contained within the model. + public List Meshes { get; set; } = []; + + /// + /// Initializes a new instance of the Model class with the specified OpenGL context and model file path. + /// + /// The GL context used for rendering and resource management. + /// The file path of the model asset. + public Model(GL gl, string path) + { + _gl = gl; + Path = path; + } + + /// + /// Initializes a new instance of the Model class, stores the GL context and path, and processes the provided meshes. + /// + /// + /// Input meshes are enumerated and processed immediately to populate the Model's Meshes collection. + /// + /// The OpenGL context used for creating and managing rendering resources. + /// The file path of the model resource. + /// A sequence of meshes to be processed and stored; each mesh is converted via ProcessMesh. + public Model(GL gl, string path, IEnumerable meshes) + { + _gl = gl; + Path = path; + Meshes = [.. meshes.Select(ProcessMesh)]; + } + + /// + public void Dispose() + { + foreach (var mesh in Meshes) + mesh.Dispose(); + + GC.SuppressFinalize(this); + } + + /// + /// Produces a GPU-ready Mesh from the provided mesh by cloning when already processed or by expanding vertices and constructing indices, materials, and textures. + /// + /// Source mesh to process. Must not be null. + /// + /// A Mesh prepared for rendering: either a cloned processed mesh if the input already contains vertex buffer + /// data, or a newly constructed Mesh with expanded vertices, generated index buffer, and associated textures + /// and materials. + /// + /// Thrown if the input mesh is null. + public Mesh ProcessMesh(Mesh mesh) + { + ArgumentNullException.ThrowIfNull(mesh); + + // TODO: Instead of cloning the object, we should just update the values. + if (mesh.Vertices.Length > 0) + return CloneProcessedMesh(mesh); + + var vertices = ExpandVertices(mesh); + var indices = Enumerable.Range(0, vertices.Count).Select(index => (uint)index).ToArray(); + var materials = BuildMaterials(mesh).ToList(); + var textures = BuildTextures(materials).ToList(); + + // TODO: Instead of cloning the object, we should just update the values. + return new Mesh(_gl, BuildVertices(vertices), indices, textures) + { + Name = mesh.Name, + Vertices2 = vertices, + Normals2 = mesh.Normals2, + TextureCoordinates2 = mesh.TextureCoordinates2, + Groups = mesh.Groups, + Materials = materials, + }; + } + + private Mesh CloneProcessedMesh(Mesh template) + { + var materials = template.Materials.Select(CloneMaterial).ToList(); + var textures = materials.Count > 0 ? BuildTextures(materials) : CloneTextures(template.Textures); + + return new Mesh(_gl, template.Vertices, template.Indices, textures.ToList()) + { + Name = template.Name, + Vertices2 = template.Vertices2, + Normals2 = template.Normals2, + TextureCoordinates2 = template.TextureCoordinates2, + Groups = template.Groups, + Materials = materials, + }; + } + + private static List ExpandVertices(Mesh mesh) + { + var vertices = new List(); + + foreach (var faceVertex in EnumerateFaceVertices(mesh.Groups)) + vertices.Add(CreateVertex(mesh, faceVertex)); + + if (vertices.Count > 0) + return vertices; + + return [.. mesh.Vertices2.Select(vertex => + { + vertex.BoneIds ??= new int[Vertex.MAX_BONE_INFLUENCE]; + vertex.Weights ??= new float[Vertex.MAX_BONE_INFLUENCE]; + return vertex; + })]; + } + + private static IEnumerable EnumerateFaceVertices(IEnumerable groups) + { + foreach (var group in groups) + foreach (var face in group.Faces) + for (int i = 0; i < face.Count; i++) + yield return face[i]; + } + + private static Vertex CreateVertex(Mesh mesh, FaceVertex faceVertex) + => new() + { + BoneIds = new int[Vertex.MAX_BONE_INFLUENCE], + Weights = new float[Vertex.MAX_BONE_INFLUENCE], + Position = GetVertexPosition(mesh, faceVertex.VertexIndex), + Normal = GetNormal(mesh, faceVertex.NormalIndex), + TexCoords = GetTextureCoordinates(mesh, faceVertex.TextureIndex), + }; + + private static System.Numerics.Vector3 GetVertexPosition(Mesh mesh, int vertexIndex) + => vertexIndex > 0 && vertexIndex <= mesh.Vertices2.Count + ? mesh.Vertices2[vertexIndex - 1].Position + : default; + + private static System.Numerics.Vector3 GetNormal(Mesh mesh, int normalIndex) + => normalIndex > 0 && normalIndex <= mesh.Normals2.Count + ? new System.Numerics.Vector3( + mesh.Normals2[normalIndex - 1].X, + mesh.Normals2[normalIndex - 1].Y, + mesh.Normals2[normalIndex - 1].Z) + : default; + + private static System.Numerics.Vector2 GetTextureCoordinates(Mesh mesh, int textureIndex) + => textureIndex > 0 && textureIndex <= mesh.TextureCoordinates2.Count ? + new System.Numerics.Vector2(mesh.TextureCoordinates2[textureIndex - 1].X, mesh.TextureCoordinates2[textureIndex - 1].Y) : + default; + + private IEnumerable BuildMaterials(Mesh mesh) + { + var materialDefinitions = mesh.Groups + .Select(group => group.Material) + .Where(material => material is not null) + .DistinctBy(material => material!.Name) + .ToList(); + + if (materialDefinitions.Count == 0) + materialDefinitions = [.. mesh.Materials + .Where(material => material is not null) + .DistinctBy(material => material!.Name)]; + + foreach (var material in materialDefinitions) + { + var runtimeMaterial = CreateRuntimeMaterial(material!); + if (runtimeMaterial is not null) + yield return runtimeMaterial; + } + } + + private Material? CreateRuntimeMaterial(Material definition) + { + if (string.IsNullOrWhiteSpace(definition.DiffuseTextureMap)) + return null; + + var diffuseTexture = new EngineTexture(_gl, ResolveAssetPath(definition.DiffuseTextureMap), EngineTextureType.Diffuse); + EngineTexture? specularTexture = null; + + if (!string.IsNullOrWhiteSpace(definition.SpecularTextureMap)) + specularTexture = new EngineTexture(_gl, ResolveAssetPath(definition.SpecularTextureMap), EngineTextureType.Specular); + + return new Material(definition.Name, diffuseTexture, specularTexture) + { + Name = definition.Name, + AmbientColor = definition.AmbientColor, + DiffuseColor = definition.DiffuseColor, + SpecularColor = definition.SpecularColor, + SpecularCoefficient = definition.SpecularCoefficient, + Transparency = definition.Transparency, + IlluminationModel = definition.IlluminationModel, + DiffuseTextureMap = definition.DiffuseTextureMap, + SpecularTextureMap = definition.SpecularTextureMap, + AmbientTextureMap = definition.AmbientTextureMap, + SpecularHighlightTextureMap = definition.SpecularHighlightTextureMap, + AlphaTextureMap = definition.AlphaTextureMap, + BumpMap = definition.BumpMap, + DisplacementMap = definition.DisplacementMap, + StencilDecalMap = definition.StencilDecalMap, + }; + } + + private IEnumerable CloneTextures(IReadOnlyList textures) + => textures?.Select(texture => new EngineTexture(_gl, texture.Path, texture.Type)) ?? []; + + private Material CloneMaterial(Material material) + { + EngineTexture? diffuseTexture = null; + if (material.DiffuseMap != null) + diffuseTexture = new EngineTexture(_gl, material.DiffuseMap.Path, material.DiffuseMap.Type); + + EngineTexture? specularTexture = null; + if (material.UseSpecularMap) + specularTexture = new EngineTexture(_gl, material!.SpecularMap!.Path, material.SpecularMap.Type); + + return new Material(material.Name, diffuseTexture, specularTexture) + { + Name = material.Name, + AmbientColor = material.AmbientColor, + DiffuseColor = material.DiffuseColor, + SpecularColor = material.SpecularColor, + SpecularCoefficient = material.SpecularCoefficient, + Transparency = material.Transparency, + IlluminationModel = material.IlluminationModel, + DiffuseTextureMap = material.DiffuseTextureMap, + SpecularTextureMap = material.SpecularTextureMap, + AmbientTextureMap = material.AmbientTextureMap, + SpecularHighlightTextureMap = material.SpecularHighlightTextureMap, + AlphaTextureMap = material.AlphaTextureMap, + BumpMap = material.BumpMap, + DisplacementMap = material.DisplacementMap, + StencilDecalMap = material.StencilDecalMap, + Specular = material.Specular, + Shininess = material.Shininess, + }; + } + + private static IEnumerable BuildTextures(IEnumerable materials) + => materials + .SelectMany(GetMaterialEngineTextures) + .Distinct(); + private static EngineTexture[] GetMaterialEngineTextures(Material material) + { + ArgumentNullException.ThrowIfNull(material); + + var materials = new List(); + if (material.UseSpecularMap) + materials.Add(material.SpecularMap!); + + if (material.DiffuseMap != null) + materials.Add(material.DiffuseMap); + + return [.. materials]; + } + + private string ResolveAssetPath(string assetPath) + { + if (System.IO.Path.IsPathRooted(assetPath) || string.IsNullOrWhiteSpace(Path)) + return assetPath; + + var directory = System.IO.Path.GetDirectoryName(Path); + + return string.IsNullOrWhiteSpace(directory) ? + assetPath : System.IO.Path.Combine(directory, assetPath); + } + + private static float[] BuildVertices(IEnumerable 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]; + } + } +} diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs b/SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs rename to SharpEngine.Core.Components/Properties/Meshes/ObjMeshData.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs b/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs rename to SharpEngine.Core.Components/Properties/Meshes/VertexArrayObject.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/Meshes/VertexData.cs b/SharpEngine.Core.Components/Properties/Meshes/VertexData.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/Meshes/VertexData.cs rename to SharpEngine.Core.Components/Properties/Meshes/VertexData.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/Quaternion.cs b/SharpEngine.Core.Components/Properties/Quaternion.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/Quaternion.cs rename to SharpEngine.Core.Components/Properties/Quaternion.cs diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs b/SharpEngine.Core.Components/Properties/Shaders/Shader.cs similarity index 81% rename from Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs rename to SharpEngine.Core.Components/Properties/Shaders/Shader.cs index 13aea94..06e8322 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs +++ b/SharpEngine.Core.Components/Properties/Shaders/Shader.cs @@ -1,6 +1,6 @@ -using SharpEngine.Shared; +using Microsoft.Extensions.Logging; +using SharpEngine.Telemetry; using Silk.NET.OpenGL; -using System.Diagnostics.CodeAnalysis; namespace SharpEngine.Core.Shaders; @@ -21,9 +21,11 @@ public partial class Shader : IDisposable /// Gets or sets the path to the fragment shader file. public string FragPath { get; set; } + private readonly GL _gl; + private readonly ILogger _logger; + private Dictionary _uniformLocations = []; private bool disposedValue; - private readonly GL _gl; /// /// Initializes a new instance of . @@ -32,14 +34,16 @@ public partial class Shader : IDisposable /// 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 OpenGL context. /// 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; + Name = name; + + _logger = LoggingExtensions.CreateLogger(); // 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. @@ -48,10 +52,10 @@ public Shader(GL gl, string vertPath, string fragPath, string name) // 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."); + _logger.LogWarning("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."); + _logger.LogWarning("Fragment shaders should have the file extension '.frag' for easier manageability."); VertPath = vertPath; FragPath = fragPath; @@ -65,7 +69,8 @@ protected virtual void Dispose(bool disposing) if (disposing) { - _gl.DeleteProgram(Handle); + // TODO: Collect handles in a separate container with proper access to the shared GL context. + //_gl.DeleteProgram(Handle); Handle = 0; _uniformLocations.Clear(); } diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs b/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs similarity index 85% rename from Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs rename to SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs index 29c14ac..d6fbfd2 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs +++ b/SharpEngine.Core.Components/Properties/Shaders/ShaderAttributes.cs @@ -18,7 +18,9 @@ public static class ShaderAttributes /// Represents the model matrix attribute. public const string Model = "uModel"; + /// Represents the view matrix attribute. public const string View = "uView"; + /// Represents the projection matrix attribute. public const string Projection = "uProjection"; } diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs b/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs similarity index 91% rename from Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs rename to SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs index 830a2aa..f9fb638 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs +++ b/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs @@ -1,4 +1,4 @@ -using SharpEngine.Shared; +using Microsoft.Extensions.Logging; using Silk.NET.OpenGL; using System.Numerics; using System.Text.RegularExpressions; @@ -19,13 +19,13 @@ public Shader Initialize() // Load and compile shader if (!LoadShader(ShaderType.VertexShader, VertPath, out uint vertexShader)) { - Debug.Log.Error("Unable to load vertex shader."); + _logger.LogError("Unable to load vertex shader."); return this; } if (!LoadShader(ShaderType.FragmentShader, FragPath, out uint fragmentShader)) { - Debug.Log.Error("Unable to load fragment shader."); + _logger.LogError("Unable to load fragment shader."); return this; } @@ -50,7 +50,7 @@ public Shader Initialize() if (!shaderLinked) { - Debug.Log.Information("Unable to link shader program."); + _logger.LogInformation("Unable to link shader program."); return this; } @@ -66,7 +66,7 @@ private bool LoadShader(ShaderType shaderType, string shaderPath, out uint shade { if (!File.Exists(shaderPath)) { - Debug.Log.Information("Shader file not found: {Path}", shaderPath); + _logger.LogInformation("Shader file not found: {Path}", shaderPath); shader = 0; return false; @@ -81,7 +81,7 @@ private bool LoadShader(ShaderType shaderType, string shaderPath, out uint shade if (!CompileShader(shader)) { - Debug.Log.Information("Unable to load {Type} shader from '{Path}'.", shaderType, shaderPath); + _logger.LogInformation("Unable to load {Type} shader from '{Path}'.", shaderType, shaderPath); return false; } @@ -133,7 +133,7 @@ private bool CompileShader(uint shader) { // 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); + _logger.LogError("Error occurred whilst compiling Shader({Shader}).\n\n{Log}", shader, infoLog); return false; } @@ -149,7 +149,7 @@ private bool LinkProgram(uint program) if (statusCode != (int)GLEnum.True) { string infoLog = _gl.GetProgramInfoLog(program); - Debug.Log.Error("Error occurred whilst linking Program({Program}): {Info}", program, infoLog); + _logger.LogError("Error occurred whilst linking Program({Program}): {Info}", program, infoLog); return false; } @@ -174,7 +174,7 @@ 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); + _logger.LogWarning("Attribute '{Attribute}' not found in shader program.", attribName); return false; } @@ -194,7 +194,7 @@ 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); + _logger.LogInformation("Uniform '{UniformName}' not found in shader '{ShaderName}'.", uniformName, Name); return false; } diff --git a/SharpEngine.Core.Components/Properties/Textures/Texture.cs b/SharpEngine.Core.Components/Properties/Textures/Texture.cs new file mode 100644 index 0000000..66213d3 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Textures/Texture.cs @@ -0,0 +1,66 @@ +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Components.Properties.Textures; + +/// +/// Represents a texture resource managed in OpenGL with associated metadata. +/// +public partial class Texture : IDisposable, IEquatable +{ + /// The OpenGL handle for the texture. + public readonly uint Handle; + + /// Gets the type of the texture. + public readonly TextureType Type; + + /// Gets the path to the texture file. + public readonly string Path; + + private readonly GL _gl; + + /// + /// Initializes a new instance of . + /// + /// The OpenGL context where this texture should be available. + /// The path to the texture file. + /// The type of the texture. + public Texture(GL gl, string path, TextureType type = TextureType.Diffuse) + { + _gl = gl; + Handle = _gl.GenTexture(); + + Path = path; + Type = type; + + Initialize(); + } + + /// + /// Creates a deep clone of the texture using the same OpenGL context. + /// + /// Assumes that the OpenGL context is the same as the original texture. + public Texture DeepClone() + => new(_gl, Path, Type); + + /// + public bool Equals(Texture? other) + { + if (other is null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + return Handle == other.Handle && + Type == other.Type && + Path == other.Path; + } + + /// + public override bool Equals(object? obj) + => Equals(obj as Texture); + + /// + public override int GetHashCode() + => HashCode.Combine(Handle, Type, Path); +} diff --git a/Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs b/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs similarity index 84% rename from Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs rename to SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs index a060f8c..81c3f1f 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs +++ b/SharpEngine.Core.Components/Properties/Textures/TextureExtensions.cs @@ -3,8 +3,11 @@ namespace SharpEngine.Core.Components.Properties.Textures; -public partial class Texture +public partial class Texture : IDisposable { + /// + /// Initializes the texture by loading the image data and creating the OpenGL texture. + /// public void Initialize() { // Bind the handle @@ -30,14 +33,17 @@ public void Initialize() _gl.GenerateMipmap(GLEnum.Texture2D); } + /// + /// Configures the currently bound 2D texture. + /// 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.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0); // TODO: Calculate this based on the texture size + _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8); // TODO: Calculate this based on the texture size _gl.GenerateMipmap(TextureTarget.Texture2D); } @@ -55,6 +61,7 @@ public void Use(TextureUnit unit = TextureUnit.Texture0) _gl.BindTexture(TextureTarget.Texture2D, Handle); } + /// public void Dispose() { _gl.DeleteTexture(Handle); diff --git a/SharpEngine.Core.Components/Properties/Textures/TextureType.cs b/SharpEngine.Core.Components/Properties/Textures/TextureType.cs new file mode 100644 index 0000000..9fd26b1 --- /dev/null +++ b/SharpEngine.Core.Components/Properties/Textures/TextureType.cs @@ -0,0 +1,27 @@ +namespace SharpEngine.Core.Components.Properties.Textures; + +/// +/// Represents the semantic role of a texture in a material. +/// +public enum TextureType +{ + /// + /// A texture that defines the base color of a material, affecting how it appears under direct lighting. + /// + Diffuse, + + /// + /// The texture that defines the shininess and reflectivity of a material, affecting how it interacts with light and creates highlights. + /// + Specular, + + /// + /// The texture that defines the normal vectors of a surface, which are used to create the illusion of surface detail and depth without adding additional geometry. + /// + Ambient, + + /// + /// The texture that defines the height information of a surface, which can be used to create parallax effects or to simulate surface displacement in advanced rendering techniques. + /// + Height +} \ No newline at end of file diff --git a/Engine/SharpEngine.Core.Components/Properties/Transform.cs b/SharpEngine.Core.Components/Properties/Transform.cs similarity index 88% rename from Engine/SharpEngine.Core.Components/Properties/Transform.cs rename to SharpEngine.Core.Components/Properties/Transform.cs index 3f2f075..be8462d 100644 --- a/Engine/SharpEngine.Core.Components/Properties/Transform.cs +++ b/SharpEngine.Core.Components/Properties/Transform.cs @@ -12,6 +12,11 @@ public class Transform : ITransform /// Initializes a new instance of . /// public Transform() { } + + /// + /// Initializes a new instance of . + /// + /// The initial position of the game object. public Transform(Vector3 position) { Position = position; diff --git a/Engine/SharpEngine.Core.Components/Properties/Transform2D.cs b/SharpEngine.Core.Components/Properties/Transform2D.cs similarity index 100% rename from Engine/SharpEngine.Core.Components/Properties/Transform2D.cs rename to SharpEngine.Core.Components/Properties/Transform2D.cs diff --git a/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj b/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj similarity index 51% rename from Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj rename to SharpEngine.Core.Components/SharpEngine.Core.Components.csproj index 3ba046b..f411581 100644 --- a/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj +++ b/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj @@ -1,7 +1,7 @@ - + - net8.0 + $(DotNetTargetFramework) enable enable @@ -11,23 +11,15 @@ true - - - - - - + + - - - - - + diff --git a/Engine/SharpEngine.Core.Numerics/Float.cs b/SharpEngine.Core.Numerics/Float.cs similarity index 69% rename from Engine/SharpEngine.Core.Numerics/Float.cs rename to SharpEngine.Core.Numerics/Float.cs index 333f856..f1f19c7 100644 --- a/Engine/SharpEngine.Core.Numerics/Float.cs +++ b/SharpEngine.Core.Numerics/Float.cs @@ -1,5 +1,8 @@ namespace SharpEngine.Core.Numerics; +/// +/// Contains constants and utility methods related to floating-point numbers. +/// public static class Float { /// diff --git a/Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs b/SharpEngine.Core.Numerics/HashCodeHelper.cs similarity index 51% rename from Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs rename to SharpEngine.Core.Numerics/HashCodeHelper.cs index 601d499..d8fa3e3 100644 --- a/Engine/SharpEngine.Core.Numerics/HashCodeHelper.cs +++ b/SharpEngine.Core.Numerics/HashCodeHelper.cs @@ -4,11 +4,18 @@ namespace SharpEngine.Core.Numerics { + /// + /// Provides helper methods for combining hash codes in a deterministic manner. + /// This is used internally by numeric types to produce stable hash codes based on multiple fields. + /// internal static class HashCodeHelper { /// - /// Combines two hash codes, useful for combining hash codes of individual vector elements + /// Combines two hash codes into a single hash code. /// + /// The first hash code. + /// The second hash code. + /// A combined hash code value. internal static int CombineHashCodes(int h1, int h2) => (((h1 << 5) + h1) ^ h2); } diff --git a/Engine/SharpEngine.Core.Numerics/IVector.cs b/SharpEngine.Core.Numerics/IVector.cs similarity index 88% rename from Engine/SharpEngine.Core.Numerics/IVector.cs rename to SharpEngine.Core.Numerics/IVector.cs index a620e8d..939cf80 100644 --- a/Engine/SharpEngine.Core.Numerics/IVector.cs +++ b/SharpEngine.Core.Numerics/IVector.cs @@ -10,6 +10,4 @@ public interface IVector /// 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/SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs similarity index 100% rename from Engine/SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs rename to SharpEngine.Core.Numerics/JitIntrinsicAttribute.cs diff --git a/Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj b/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj similarity index 81% rename from Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj rename to SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj index 3082977..8e540fa 100644 --- a/Engine/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj +++ b/SharpEngine.Core.Numerics/SharpEngine.Core.Numerics.csproj @@ -1,7 +1,7 @@  - net8.0 + $(DotNetTargetFramework) enable enable diff --git a/Engine/SharpEngine.Core.Numerics/Vector2.cs b/SharpEngine.Core.Numerics/Vector2.cs similarity index 100% rename from Engine/SharpEngine.Core.Numerics/Vector2.cs rename to SharpEngine.Core.Numerics/Vector2.cs diff --git a/Engine/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs b/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs similarity index 100% rename from Engine/SharpEngine.Core.Numerics/Vector2_Intrinsics.cs rename to SharpEngine.Core.Numerics/Vector2_Intrinsics.cs diff --git a/Engine/SharpEngine.Core.Numerics/Vector3.cs b/SharpEngine.Core.Numerics/Vector3.cs similarity index 99% rename from Engine/SharpEngine.Core.Numerics/Vector3.cs rename to SharpEngine.Core.Numerics/Vector3.cs index da6f282..8e5fc83 100644 --- a/Engine/SharpEngine.Core.Numerics/Vector3.cs +++ b/SharpEngine.Core.Numerics/Vector3.cs @@ -304,18 +304,6 @@ public static Vector3 Transform(Vector3 position, Matrix4x4 matrix) (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. /// @@ -344,6 +332,19 @@ public static Vector3 Transform(Vector3 value, Quaternion rotation) (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))); } + + /// + /// 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)); + #endregion Public Static Methods #region Public operator methods diff --git a/Engine/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs b/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs similarity index 100% rename from Engine/SharpEngine.Core.Numerics/Vector3_Intrinsics.cs rename to SharpEngine.Core.Numerics/Vector3_Intrinsics.cs diff --git a/Engine/SharpEngine.Core/Abstractions/IGame.cs b/SharpEngine.Core/Abstractions/IGame.cs similarity index 63% rename from Engine/SharpEngine.Core/Abstractions/IGame.cs rename to SharpEngine.Core/Abstractions/IGame.cs index de0b798..0fad3ee 100644 --- a/Engine/SharpEngine.Core/Abstractions/IGame.cs +++ b/SharpEngine.Core/Abstractions/IGame.cs @@ -1,7 +1,7 @@ -using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Enums; - +using SharpEngine.Core.Scenes; using Silk.NET.Input; namespace SharpEngine.Core.Interfaces; @@ -10,8 +10,41 @@ namespace SharpEngine.Core.Interfaces; /// Contains definitions the Game class must implement. /// To consider: move into a abstract class so that the game doesn't necessarily have to implement all methods. /// -public class Game +public abstract class Game { + /// + /// Initializes a new instance of the . + /// + /// + /// Sets up a default scene and camera. + /// + public Game() + { + Scene = new Scene(); + Camera = new(new System.Numerics.Vector3(0), new DefaultViewSettings()); + } + + /// + /// Initializes a new instance of the . + /// + /// + /// Sets up a default camera. + /// + public Game(Scene scene) + { + Scene = scene; + Camera = new(new System.Numerics.Vector3(0), new DefaultViewSettings()); + } + + /// + /// Initializes a new instance of the . + /// + public Game(Scene scene, CameraView camera) + { + Scene = scene; + Camera = camera; + } + /// /// Gets or sets the core settings of the game. /// @@ -20,7 +53,12 @@ public class Game /// /// Gets or sets the camera of the game. /// - public CameraView Camera { get; set; } = new(new System.Numerics.Vector3(0), new DefaultViewSettings()); + public CameraView Camera { get; set; } = CameraView.CreateDefault(); + + /// + /// Gets or sets the scene loaded in the game. + /// + public Scene Scene { get; init; } /// /// Executed when a mouse button is pressed. diff --git a/Engine/SharpEngine.Core/Abstractions/IRenderable.cs b/SharpEngine.Core/Abstractions/IRenderable.cs similarity index 66% rename from Engine/SharpEngine.Core/Abstractions/IRenderable.cs rename to SharpEngine.Core/Abstractions/IRenderable.cs index e248e60..72eb7e8 100644 --- a/Engine/SharpEngine.Core/Abstractions/IRenderable.cs +++ b/SharpEngine.Core/Abstractions/IRenderable.cs @@ -1,6 +1,4 @@ -using SharpEngine.Core.Entities.Properties.Meshes; - -namespace SharpEngine.Core.Interfaces; +namespace SharpEngine.Core.Interfaces; // TODO: #55 Make an abstract class and move Render method here? diff --git a/Engine/SharpEngine.Core/Abstractions/ISettings.cs b/SharpEngine.Core/Abstractions/ISettings.cs similarity index 98% rename from Engine/SharpEngine.Core/Abstractions/ISettings.cs rename to SharpEngine.Core/Abstractions/ISettings.cs index 23888d2..6800463 100644 --- a/Engine/SharpEngine.Core/Abstractions/ISettings.cs +++ b/SharpEngine.Core/Abstractions/ISettings.cs @@ -1,6 +1,5 @@ using SharpEngine.Core.Renderers; using Silk.NET.Input; -using Silk.NET.Windowing; namespace SharpEngine.Core.Interfaces; diff --git a/Engine/SharpEngine.Core/Audio/Audio.cs b/SharpEngine.Core/Audio/Audio.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/Audio.cs rename to SharpEngine.Core/Audio/Audio.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioBuffer.cs b/SharpEngine.Core/Audio/AudioBuffer.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/AudioBuffer.cs rename to SharpEngine.Core/Audio/AudioBuffer.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioDevice.cs b/SharpEngine.Core/Audio/AudioDevice.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/AudioDevice.cs rename to SharpEngine.Core/Audio/AudioDevice.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioFileExtensions.cs b/SharpEngine.Core/Audio/AudioFileExtensions.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/AudioFileExtensions.cs rename to SharpEngine.Core/Audio/AudioFileExtensions.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs b/SharpEngine.Core/Audio/AudioPlayerBase.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs rename to SharpEngine.Core/Audio/AudioPlayerBase.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioProperties.cs b/SharpEngine.Core/Audio/AudioProperties.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/AudioProperties.cs rename to SharpEngine.Core/Audio/AudioProperties.cs diff --git a/Engine/SharpEngine.Core/Audio/AudioSource.cs b/SharpEngine.Core/Audio/AudioSource.cs similarity index 82% rename from Engine/SharpEngine.Core/Audio/AudioSource.cs rename to SharpEngine.Core/Audio/AudioSource.cs index 9e59179..d0a3669 100644 --- a/Engine/SharpEngine.Core/Audio/AudioSource.cs +++ b/SharpEngine.Core/Audio/AudioSource.cs @@ -3,7 +3,7 @@ namespace SharpEngine.Core.Audio; /// -/// Represents a source for audio +/// Represents a source for audio that can be played, stopped and queried. /// public class AudioSource { @@ -21,9 +21,9 @@ public AudioSource(AL al) } /// - /// Gets the source identifier. + /// Gets the numeric identifier for the OpenAL source. /// - /// The identifier to the source. + /// The identifier of the underlying OpenAL source. public uint Get() => _source; /// diff --git a/Engine/SharpEngine.Core/Audio/WavData.cs b/SharpEngine.Core/Audio/WavData.cs similarity index 100% rename from Engine/SharpEngine.Core/Audio/WavData.cs rename to SharpEngine.Core/Audio/WavData.cs diff --git a/Engine/SharpEngine.Core/Audio/WavPlayer.cs b/SharpEngine.Core/Audio/WavPlayer.cs similarity index 92% rename from Engine/SharpEngine.Core/Audio/WavPlayer.cs rename to SharpEngine.Core/Audio/WavPlayer.cs index a686bea..7869f28 100644 --- a/Engine/SharpEngine.Core/Audio/WavPlayer.cs +++ b/SharpEngine.Core/Audio/WavPlayer.cs @@ -1,4 +1,4 @@ -using SharpEngine.Shared; +using Microsoft.Extensions.Logging; using Silk.NET.OpenAL; using System; @@ -12,6 +12,8 @@ namespace SharpEngine.Core.Audio; /// public class WavPlayer : AudioPlayerBase { + private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + /// protected override string FileExtension => ".wav"; @@ -41,7 +43,7 @@ public override void Play(string filePath) if (!CheckHeader(file, ref index, WavConstants.RiffHeader)) { - Debug.Log.Warning("Given file is not in RIFF format"); + Logger.LogWarning("Given file is not in RIFF format"); return; } @@ -49,7 +51,7 @@ public override void Play(string filePath) if (!CheckHeader(file, ref index, WavConstants.WaveHeader)) { - Debug.Log.Warning("Given file is not in WAVE format"); + Logger.LogWarning("Given file is not in WAVE format"); return; } diff --git a/SharpEngine.Core/Components/ObjLoader/DataStore/IVertexDataStore.cs b/SharpEngine.Core/Components/ObjLoader/DataStore/IVertexDataStore.cs new file mode 100644 index 0000000..e69de29 diff --git a/SharpEngine.Core/DependencyInjection/App.cs b/SharpEngine.Core/DependencyInjection/App.cs new file mode 100644 index 0000000..3b9b7cb --- /dev/null +++ b/SharpEngine.Core/DependencyInjection/App.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +using SharpEngine.Core.Windowing; + +using System; +using System.Threading; + +namespace SharpEngine.Core.DependencyInjection; + +/// +/// Represents the application entry point responsible for resolving required services and running the main loop or window. +/// +public class App +{ + private static IConfiguration? _configuration; + private readonly IServiceProvider _serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider for dependency injection. + /// The application configuration. + public App(IServiceProvider serviceProvider, IConfiguration configuration) + { + _serviceProvider = serviceProvider; + _configuration = configuration; + } + + /// + /// Runs the application by resolving required services and starting either a window or a main processing loop. + /// + /// Signals that the operation should be canceled. + public void Run(CancellationToken cancellationToken) + { + var logger = _serviceProvider.GetService>()!; + + try + { + + logger.LogInformation("App started. Resolving services."); + + if (cancellationToken.IsCancellationRequested) + return; + + var windowFactory = _serviceProvider.GetService(); + if (windowFactory is not null && windowFactory.RegisteredWindows.Count > 0) + { + using var factoryWindow = windowFactory.CreateWindow(); + + logger.LogInformation("Required services resolved."); + logger.LogInformation("Starting window."); + + factoryWindow.Initialize(); + factoryWindow.Run(); + return; + } + + var window = _serviceProvider.GetService(); + if (window is not null) + { + logger.LogInformation("Required services resolved."); + logger.LogInformation("Starting window."); + + using (window) + { + window.Initialize(); + window.Run(); + } + + return; + } + + // This is just an example of how to use the scopes. In App we should always use '_serviceProvider'. + var scopeFactory = _serviceProvider.GetRequiredService(); + using var scope = scopeFactory.CreateScope(); + + logger.LogInformation("Required services resolved."); + logger.LogInformation("Entering main loop."); + + bool running = !cancellationToken.IsCancellationRequested; + while (running) + { + logger.LogInformation("frame tick."); + + Thread.Sleep(1000); // Simulate frame delay + } + } + catch (Exception ex) + { + logger.LogError(ex, "Exception occurred."); + } + } + + /// + /// Runs the application without a . + /// + public void Run() => Run(CancellationToken.None); +} diff --git a/SharpEngine.Core/DependencyInjection/AppBuilder.cs b/SharpEngine.Core/DependencyInjection/AppBuilder.cs new file mode 100644 index 0000000..9cf7f30 --- /dev/null +++ b/SharpEngine.Core/DependencyInjection/AppBuilder.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace SharpEngine.Core.DependencyInjection; + +/// +/// Represents a Dependency Injection builder for configuring services and building the application. +/// +public class AppBuilder +{ + private readonly IServiceCollection _services = new ServiceCollection(); + private IConfiguration? _configuration; + + private App? App; + + /// + /// Configures the application using the specified configuration action. + /// + /// An action to configure the configuration builder. + /// The current instance for method chaining. + public AppBuilder Configure(Action configure) + { + var configBuilder = new ConfigurationBuilder(); + configure(configBuilder); + + Configure(configBuilder); + return this; + } + + private void Configure(ConfigurationBuilder? configBuilder = null) + { + var builder = configBuilder ?? new ConfigurationBuilder(); + + _configuration = builder.Build(); + _services.AddSingleton(_configuration); + } + + /// + /// Configures the application services using the specified configuration action. + /// + /// The configuration action to apply to the service collection. + /// The current instance for method chaining. + public AppBuilder ConfigureServices(Action configure) + { + configure(_services); + return this; + } + + /// + /// Builds the App instance if not already built. + /// + /// + /// This method lazily initializes and caches the App instance. + /// + /// The App instance. + public App Build() + { + if (App != null) + return App; + + if (_configuration == null) + Configure(); + + App = new App(_services.BuildServiceProvider(), _configuration!); + return App; + } +} \ No newline at end of file diff --git a/SharpEngine.Core/DependencyInjection/ServiceCollectionExtensions.cs b/SharpEngine.Core/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..72ac3d8 --- /dev/null +++ b/SharpEngine.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using SharpEngine.Core.Windowing; +using System; +using System.Linq; + +namespace SharpEngine.Core.DependencyInjection; + +/// +/// Provides window-related dependency injection registrations. +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers a window factory and an optional configuration callback. + /// + /// The service collection. + /// Creates the window instance. + /// Configures the created window. + /// The optional registration name. + /// Whether this registration should be used as the default app window. + /// The service collection. + public static IServiceCollection AddWindow( + this IServiceCollection services, + Func factory, + Action? configure = null, + string? name = null, + bool isDefault = false) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(factory); + + services.TryAddSingleton(); + + var registrationName = name ?? $"window-{services.Count(service => service.ServiceType == typeof(WindowRegistration))}"; + services.AddSingleton(new WindowRegistration + { + Name = registrationName, + Factory = factory, + Configure = configure, + IsDefault = isDefault + }); + + return services; + } +} diff --git a/SharpEngine.Core/Engine.cs b/SharpEngine.Core/Engine.cs new file mode 100644 index 0000000..dde1280 --- /dev/null +++ b/SharpEngine.Core/Engine.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Logging; + +using SharpEngine.Core.Handlers; +using SharpEngine.Core.Interfaces; +using SharpEngine.Core.Windowing; +using SharpEngine.Telemetry; + +using System.Threading.Tasks; + +namespace SharpEngine.Core; + +/// +/// Manages the engine's services and provides methods to initialize, register handlers, and shut down asynchronously. +/// +public static class Engine +{ + /// + /// Gets the manager responsible for handling engine services. + /// + public static EngineServiceManager Services { get; private set; } = new(); + + private static bool _initialized = false; + + private readonly static ILogger _logger; + + static Engine() + { + Initialize(); + _logger = LoggingExtensions.CreateLogger(typeof(Engine)); + } + + /// + /// Initializes the engine for use. + /// + public static void Initialize() + { + _logger.LogDebug("Initializing engine..."); + + if (_initialized) + { + _logger.LogWarning("Reinitializing engine."); + Services.StopAllAsync().Wait(); + } + + _initialized = true; + _logger.LogDebug("Engine successfully initialized."); + } + + /// + /// Creates and initializes a new window using the provided context and registers the window handler. + /// + /// The game context provides access to the current scene and camera settings for window initialization. + /// Returns the newly created instance. + public static Window Initialize(Game game) + { + var window = new Window(game); + + Initialize(); + Services.RegisterHandler(new WindowHandler(window)); + + return window; + } + + /// + /// Stops all engine services and shuts down the engine asynchronously. + /// + /// A that completes when shutdown finishes. + public static async Task ShutdownAsync() + { + if (Services == null) + return; + + _logger.LogDebug("Shutting down engine..."); + + await Services.StopAllAsync(); + + _initialized = false; + _logger.LogDebug("Engine successfully shut down."); + } +} \ No newline at end of file diff --git a/SharpEngine.Core/EngineServiceManager.cs b/SharpEngine.Core/EngineServiceManager.cs new file mode 100644 index 0000000..d926a94 --- /dev/null +++ b/SharpEngine.Core/EngineServiceManager.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Logging; + +using SharpEngine.Core.Handlers; +using SharpEngine.Telemetry; + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SharpEngine.Core; + +/// +/// Contains all the engine handlers and manages their lifecycle. +/// +public class EngineServiceManager +{ + private readonly List handlers = []; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of . + /// + /// The logger to use for logging messages. + public EngineServiceManager(ILogger? logger = null) + { + _logger = logger ?? LoggingExtensions.CreateLogger(); + } + + /// + /// Registers a new engine handler and starts its operation. + /// + /// The handler to register. + public void RegisterHandler(EngineHandler handler) + { + _logger.LogDebug("Registering handler: '{Handler}'.", handler.GetType().Name); + + handlers.Add(handler); + handler.Start(); + + _logger.LogDebug("Handler '{Handler}' registered successfully.", handler.GetType().Name); + } + + /// + /// Stops all active handlers asynchronously by calling their StopAsync method. + /// + /// A representing the asynchronous operation. + public async Task StopAllAsync() + { + var stopTasks = handlers.Select(handler => handler.StopAsync()); + await Task.WhenAll(stopTasks); + } +} \ No newline at end of file diff --git a/Engine/SharpEngine.Core/Entities/BezierCurve.cs b/SharpEngine.Core/Entities/BezierCurve.cs similarity index 100% rename from Engine/SharpEngine.Core/Entities/BezierCurve.cs rename to SharpEngine.Core/Entities/BezierCurve.cs diff --git a/SharpEngine.Core/Entities/GameObject.cs b/SharpEngine.Core/Entities/GameObject.cs new file mode 100644 index 0000000..1b52fea --- /dev/null +++ b/SharpEngine.Core/Entities/GameObject.cs @@ -0,0 +1,249 @@ +using SharpEngine.Core.Attributes; +using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Components.Properties; +using SharpEngine.Core.Components.Properties.Meshes; +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 EngineTexture = SharpEngine.Core.Components.Properties.Textures.Texture; + +using Silk.NET.OpenGL; +using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SharpEngine.Core.Entities; + +/// +/// Represents a game object in the scene. +/// +public class GameObject : EmptyNode, IRenderable +{ + private readonly object _modelCacheLock = new(); + private readonly Dictionary _modelByShareGroup = []; + + private readonly string _shaderVertPath; + private readonly string _shaderFragPath; + private readonly string _shaderName; + + /// + /// Initializes a new instance of the . + /// + public GameObject() : base(string.Empty) + { + BoundingBox = BoundingBox.CalculateBoundingBox(Transform); + + _shaderVertPath = _Resources.Default.VertexShader; + _shaderFragPath = _Resources.Default.FragmentShader; + _shaderName = "lighting"; + } + + /// + /// Initializes a new instance of the class with a model. + /// + /// The model. + public GameObject(Model model) : base(string.Empty) + { + Model = model; + BoundingBox = BoundingBox.CalculateBoundingBox(Transform); + + _shaderVertPath = _Resources.Default.VertexShader; + _shaderFragPath = _Resources.Default.FragmentShader; + _shaderName = "lighting"; + } + + /// + /// Initializes a new instance of the with specified textures and shaders. + /// + /// The shader to be used by the game object. + /// The model of the game object. + public GameObject(Shader shader, Model model) : base(string.Empty) + { + Model = model; + BoundingBox = BoundingBox.CalculateBoundingBox(Transform); + Shader = shader; + + _shaderVertPath = shader.VertPath; + _shaderFragPath = shader.FragPath; + _shaderName = shader.Name; + } + + /// + /// Gets or sets the shader of the game object. + /// + public Shader? Shader { get; set; } + + /// + /// Gets or sets the mesh of the game object. + /// + public Model? 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; } + + /// + /// Sets shader uniforms for model, view, and projection matrices. + /// + /// The camera view used to retrieve view and projection matrices. + protected virtual void SetShaderUniforms(CameraView camera) + { + if (Shader is null) + throw new NullReferenceException(nameof(Shader)); + + 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; + + var glInstance = window.GetGL(); + + // Ensure this instance is using a shader compiled for the correct context/share-group. + Shader = ShaderService.Instance.LoadShader(window, _shaderVertPath, _shaderFragPath, _shaderName); + + var modelToRender = GetOrCreateModelForWindow(window, glInstance); + if (modelToRender is null) + return Task.CompletedTask; + + foreach (var mesh in modelToRender.Meshes) + { + mesh.Bind(); + + foreach (var texture in mesh.Textures) + texture.Use(); + + Shader.Use(); + SetShaderUniforms(camera); + + foreach (var material in mesh.Materials) + material.SetUniformValues(Shader); + + if (mesh.Indices.Length > 0) + glInstance.DrawElements(PrimitiveType.Triangles, (uint)mesh.Indices.Length, DrawElementsType.UnsignedInt, []); + else + glInstance.DrawArrays(PrimitiveType.Triangles, 0, (uint)(mesh.Vertices.Length / (VertexData.VerticesSize + VertexData.NormalsSize + VertexData.TexCoordsSize))); + } + + return Task.CompletedTask; + } + + private static object GetShareGroupKey(Window window) + => (object?)window.SharedContext ?? (object?)window.GLContext ?? window; + + private Model? GetOrCreateModelForWindow(Window window, GL gl) + { + if (Model is null) + return null; + + var shareGroupKey = GetShareGroupKey(window); + + lock (_modelCacheLock) + { + if (_modelByShareGroup.TryGetValue(shareGroupKey, out var cachedModel)) + return cachedModel; + + // If this model was created using the same GL instance, we can reuse it for this share group. + // Otherwise, build a copy of the model bound to the GL instance for this window. + var canReuseModel = Model.Meshes.Count > 0 && ReferenceEquals(Model.Meshes[0].GL, gl); + + var modelForWindow = canReuseModel ? Model : CloneModel(gl, Model); + _modelByShareGroup[shareGroupKey] = modelForWindow; + + return modelForWindow; + } + } + + private static Model CloneModel(GL gl, Model template) + { + // Create an empty model and populate it with meshes recreated against the provided GL instance. + // This allows rendering on windows that don't share the original context. + var clone = new Model(gl, string.Empty); + + foreach (var mesh in template.Meshes) + clone.Meshes.Add(CloneMesh(gl, mesh)); + + return clone; + } + + private static Mesh CloneMesh(GL gl, Mesh template) + { + var textures = template.Textures is null ? + new List() : + [.. template.Textures.Select(t => new EngineTexture(gl, t.Path, t.Type))]; + + var clone = new Mesh(gl, template.Vertices, template.Indices, textures) + { + Name = template.Name, + }; + + if (template.Materials is { Count: > 0 }) + clone.Materials = [.. template.Materials.Select(m => CloneMaterial(gl, m))]; + + return clone; + } + + private static Material CloneMaterial(GL gl, Material template) + { + ArgumentNullException.ThrowIfNull(template); + var diffuse = new EngineTexture(gl, template.DiffuseMap!.Path, template.DiffuseMap!.Type); + + EngineTexture? specular = null; + if (template.UseSpecularMap) + specular = new EngineTexture(gl, template.SpecularMap!.Path, template.SpecularMap!.Type); + + var clone = new Material(template.Name, diffuse, specular) + { + Name = template.Name, + DiffuseTextureMap = template.DiffuseTextureMap, + SpecularTextureMap = template.SpecularTextureMap, + Specular = template.Specular, + Shininess = template.Shininess, + AmbientColor = template.AmbientColor, + DiffuseColor = template.DiffuseColor, + SpecularColor = template.SpecularColor, + SpecularCoefficient = template.SpecularCoefficient, + Transparency = template.Transparency, + IlluminationModel = template.IlluminationModel, + AmbientTextureMap = template.AmbientTextureMap, + SpecularHighlightTextureMap = template.SpecularHighlightTextureMap, + BumpMap = template.BumpMap, + DisplacementMap = template.DisplacementMap, + StencilDecalMap = template.StencilDecalMap, + AlphaTextureMap = template.AlphaTextureMap, + }; + + return clone; + } +} diff --git a/Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs b/SharpEngine.Core/Entities/Lights/DirectionalLight.cs similarity index 81% rename from Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs rename to SharpEngine.Core/Entities/Lights/DirectionalLight.cs index c7c0701..b37be9a 100644 --- a/Engine/SharpEngine.Core/Entities/Lights/DirectionalLight.cs +++ b/SharpEngine.Core/Entities/Lights/DirectionalLight.cs @@ -1,9 +1,8 @@ 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; @@ -26,13 +25,12 @@ public DirectionalLight() { Diffuse = new Vector3(0.4f, 0.4f, 0.4f); Specular = new Vector3(0.5f, 0.5f, 0.5f); - - Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.FragmentShader, "lighting"); } /// - public Task Render(CameraView camera, Window window) + public override Task Render(CameraView camera, Window window) { + Shader = ShaderService.Instance.LoadShader(window, Default.VertexShader, Default.FragmentShader, "lighting"); Shader.SetVector3("dirLight.direction", Direction); Shader.SetVector3("dirLight.ambient", Ambient); Shader.SetVector3("dirLight.diffuse", Diffuse); diff --git a/Engine/SharpEngine.Core/Entities/Lights/Light.cs b/SharpEngine.Core/Entities/Lights/Light.cs similarity index 100% rename from Engine/SharpEngine.Core/Entities/Lights/Light.cs rename to SharpEngine.Core/Entities/Lights/Light.cs diff --git a/Engine/SharpEngine.Core/Entities/Lights/PointLight.cs b/SharpEngine.Core/Entities/Lights/PointLight.cs similarity index 83% rename from Engine/SharpEngine.Core/Entities/Lights/PointLight.cs rename to SharpEngine.Core/Entities/Lights/PointLight.cs index 0522f97..0b6ef21 100644 --- a/Engine/SharpEngine.Core/Entities/Lights/PointLight.cs +++ b/SharpEngine.Core/Entities/Lights/PointLight.cs @@ -1,8 +1,9 @@ using SharpEngine.Core._Resources; using SharpEngine.Core.Entities.Views; -using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; + +using System; using System.Numerics; using System.Threading.Tasks; @@ -28,7 +29,7 @@ public PointLight(Vector3 position, int index) _index = index; - LampShader = new LampShader(); + LampShader = new LampShader(Window.SharedGL); } @@ -51,8 +52,13 @@ public PointLight(Vector3 position, int index) /// public float Quadratic { get; set; } + /// + /// protected override void SetShaderUniforms(CameraView camera) { + if (Shader == null) + throw new NullReferenceException(nameof(Shader)); + Shader.SetVector3($"pointLights[{_index}].position", (Vector3)Transform.Position); Shader.SetVector3($"pointLights[{_index}].ambient", Ambient); Shader.SetVector3($"pointLights[{_index}].diffuse", Diffuse); @@ -65,8 +71,9 @@ protected override void SetShaderUniforms(CameraView camera) } /// - public Task Render(CameraView camera, Window window) + public override Task Render(CameraView camera, Window window) { + Shader = ShaderService.Instance.LoadShader(window, Default.VertexShader, Default.FragmentShader, "lighting"); SetShaderUniforms(camera); return Task.CompletedTask; diff --git a/Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs b/SharpEngine.Core/Entities/Lights/SpotLight.cs similarity index 88% rename from Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs rename to SharpEngine.Core/Entities/Lights/SpotLight.cs index 59ad0fe..1da0b41 100644 --- a/Engine/SharpEngine.Core/Entities/Lights/SpotLight.cs +++ b/SharpEngine.Core/Entities/Lights/SpotLight.cs @@ -1,8 +1,9 @@ using SharpEngine.Core._Resources; using SharpEngine.Core.Entities.Views; -using SharpEngine.Core.Extensions; using SharpEngine.Core.Shaders; using SharpEngine.Core.Windowing; + +using System; using System.Numerics; using System.Threading.Tasks; @@ -25,7 +26,6 @@ public SpotLight() Linear = 0.09f; Quadratic = 0.032f; - Shader = ShaderService.Instance.LoadShader(Default.VertexShader, Default.FragmentShader, "lighting"); } /// @@ -58,8 +58,13 @@ public SpotLight() /// public float Quadratic { get; set; } + /// + /// protected override void SetShaderUniforms(CameraView camera) { + if (Shader == null) + throw new NullReferenceException(nameof(Shader)); + Shader.SetVector3("spotLight.position", (Vector3)Transform.Position); Shader.SetVector3("spotLight.direction", Direction); Shader.SetVector3("spotLight.ambient", Ambient); @@ -77,6 +82,7 @@ protected override void SetShaderUniforms(CameraView camera) /// public override Task Render(CameraView camera, Window window) { + Shader = ShaderService.Instance.LoadShader(window, Default.VertexShader, Default.FragmentShader, "lighting"); SetShaderUniforms(camera); return Task.CompletedTask; diff --git a/SharpEngine.Core/Entities/MeshService.cs b/SharpEngine.Core/Entities/MeshService.cs new file mode 100644 index 0000000..ea51e4d --- /dev/null +++ b/SharpEngine.Core/Entities/MeshService.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace SharpEngine.Core.Entities.Properties.Meshes; + +/// +/// Service responsible for caching and providing meshes bound to GPU resources. +/// +public class MeshService : IMeshService +{ + /// A global instance of the service. + public static readonly MeshService Instance = new(); + + private readonly Dictionary Meshes = []; + + private MeshService() { } + + /// + public Mesh LoadMesh(string identifier, Mesh mesh) + { + if (Meshes.TryGetValue(identifier, out var cachedMesh)) + return cachedMesh; + + Meshes.Add(identifier, mesh); + return mesh; + } +} + +/// +/// Provides methods to load and retrieve Mesh instances from a cache keyed by an identifier. +/// +public interface IMeshService +{ + /// + /// Loads a mesh into the mesh cache or returns an existing cached instance for the identifier. + /// + /// The cache key used to identify the mesh. + /// The mesh to cache if not already present. + /// The cached or newly stored instance. + Mesh LoadMesh(string identifier, Mesh mesh); +} \ No newline at end of file diff --git a/SharpEngine.Core/Entities/Particle.cs b/SharpEngine.Core/Entities/Particle.cs new file mode 100644 index 0000000..542d126 --- /dev/null +++ b/SharpEngine.Core/Entities/Particle.cs @@ -0,0 +1,35 @@ +using System; + +namespace SharpEngine.Core.Entities; + +/// +/// Represents a particle emitted by a particle system. +/// +public class Particle : GameObject +{ + /// + /// Initializes a new instance of with a specified lifetime. + /// + /// Determines how long the particle should stay on the screen. + public Particle(int lifeTimeMilliseconds) + { + LifeTimeMilliseconds = lifeTimeMilliseconds; + StartTimeTicks = DateTime.UtcNow.Ticks; + } + + /// + /// Gets or sets the time when the particle was emitted, represented in ticks. + /// + /// + /// This value is used to determine when the particle should be removed based on its lifetime. + /// + public long StartTimeTicks { get; private set; } + + /// + /// Gets or sets the lifetime of the particle in milliseconds. + /// + /// + /// This value is used to determine when the particle should be removed based on its start time. + /// + public int LifeTimeMilliseconds { get; private set; } +} diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs b/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs similarity index 97% rename from Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs rename to SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs index b0dded0..373fda3 100644 --- a/Engine/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs +++ b/SharpEngine.Core/Entities/UI/Layouts/FlexLayout.cs @@ -1,7 +1,6 @@ using SharpEngine.Core.Numerics; using SharpEngine.Core.Scenes; using System; -using System.Numerics; namespace SharpEngine.Core.Entities.UI.Layouts; diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs b/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs similarity index 96% rename from Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs rename to SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs index 452cc3e..3f7a339 100644 --- a/Engine/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs +++ b/SharpEngine.Core/Entities/UI/Layouts/GridLayout.cs @@ -1,6 +1,4 @@ -using SharpEngine.Core.Entities.Properties; -using SharpEngine.Core.Numerics; -using SharpEngine.Core.Scenes; +using SharpEngine.Core.Scenes; using System; namespace SharpEngine.Core.Entities.UI.Layouts; diff --git a/Engine/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs b/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs similarity index 100% rename from Engine/SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs rename to SharpEngine.Core/Entities/UI/Layouts/LayoutBase.cs diff --git a/SharpEngine.Core/Entities/UI/UIElement.cs b/SharpEngine.Core/Entities/UI/UIElement.cs new file mode 100644 index 0000000..50d498a --- /dev/null +++ b/SharpEngine.Core/Entities/UI/UIElement.cs @@ -0,0 +1,162 @@ +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.Textures; +using SharpEngine.Core.Windowing; +using SharpEngine.Core._Resources; +using Vector2 = SharpEngine.Core.Numerics.Vector2; +using Texture = SharpEngine.Core.Components.Properties.Textures.Texture; + +using Silk.NET.OpenGL; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; + +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); + } + + private readonly UIShader _uiShader = new(); + private readonly Texture _texture = TextureService.Instance.LoadTexture(Default.DebugTexture); + + /// 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; } + + /// + /// Gets the most recently used VAO for this element. + /// + /// + /// UIElement maintains VAOs per OpenGL context. This property is updated on render. + /// + public uint VAO { get; private set; } + + private sealed record SharedBuffers(uint Vbo, uint Ebo, int IndexCount); + private sealed record ContextState(uint Vao); + + private readonly object _gpuLock = new(); + private readonly Dictionary _sharedBuffersByShareGroup = []; + private readonly Dictionary _contextStateByContext = []; + + private static object GetShareGroupKey(Window window) + => (object?)window.SharedContext ?? (object?)window.GLContext ?? (object)window; + + private static object GetContextKey(Window window) + => (object?)window.GLContext ?? (object)window; + + private SharedBuffers EnsureSharedBuffers(GL gl, Window window) + { + var shareGroupKey = GetShareGroupKey(window); + + lock (_gpuLock) + { + if (_sharedBuffersByShareGroup.TryGetValue(shareGroupKey, out var buffers)) + return buffers; + + // Buffers are shareable across contexts in the same share group. + var vbo = gl.GenBuffer(); + gl.BindBuffer(GLEnum.ArrayBuffer, vbo); + gl.BufferData(GLEnum.ArrayBuffer, Mesh.GetVertices(), GLEnum.StaticDraw); + + var ebo = gl.GenBuffer(); + gl.BindBuffer(GLEnum.ElementArrayBuffer, ebo); + gl.BufferData(GLEnum.ElementArrayBuffer, Mesh.Indices, GLEnum.StaticDraw); + + buffers = new SharedBuffers(vbo, ebo, Mesh.Indices.Length); + _sharedBuffersByShareGroup[shareGroupKey] = buffers; + + return buffers; + } + } + + private ContextState EnsureContextState(GL gl, Window window, SharedBuffers sharedBuffers) + { + var contextKey = GetContextKey(window); + + lock (_gpuLock) + { + if (_contextStateByContext.TryGetValue(contextKey, out var state)) + return state; + + // VAOs are generally not shared between contexts. + var vao = gl.GenVertexArray(); + gl.BindVertexArray(vao); + + gl.BindBuffer(GLEnum.ArrayBuffer, sharedBuffers.Vbo); + gl.BindBuffer(GLEnum.ElementArrayBuffer, sharedBuffers.Ebo); + + _uiShader.EnsureInitialized(window); + _uiShader.Shader!.Use(); + _uiShader.SetAttributes(gl); + + state = new ContextState(vao); + _contextStateByContext[contextKey] = state; + + return state; + } + } + + Matrix4x4 OrthoMatrix = Matrix4x4.CreateOrthographicOffCenter(-1, 1, -1, 1, -1, 1); + + /// + /// Render the UI element. + /// + public override Task Render(CameraView camera, Window window) + { + var gl = window.GetGL(); + + _uiShader.EnsureInitialized(window); + + var sharedBuffers = EnsureSharedBuffers(gl, window); + var contextState = EnsureContextState(gl, window, sharedBuffers); + + _uiShader.Shader!.Use(); + gl.BindVertexArray(contextState.Vao); + _texture.Use(TextureUnit.Texture0); + + VAO = contextState.Vao; + + // 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!.SetInt("texture1", 0); + _uiShader.Shader!.SetMatrix4(ShaderAttributes.Model, Transform.ModelMatrix); + _uiShader.Shader!.SetMatrix4("orthoMatrix", OrthoMatrix); // Pass the orthographic matrix to the shader + + gl.DrawElements(PrimitiveType.Triangles, (uint)sharedBuffers.IndexCount, DrawElementsType.UnsignedInt, []); + + return Task.CompletedTask; + } +} diff --git a/Engine/SharpEngine.Core/Entities/Views/CameraView.cs b/SharpEngine.Core/Entities/Views/CameraView.cs similarity index 92% rename from Engine/SharpEngine.Core/Entities/Views/CameraView.cs rename to SharpEngine.Core/Entities/Views/CameraView.cs index d98a3ff..16ea0fb 100644 --- a/Engine/SharpEngine.Core/Entities/Views/CameraView.cs +++ b/SharpEngine.Core/Entities/Views/CameraView.cs @@ -1,4 +1,3 @@ -using SharpEngine.Core.Components.Properties; using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Shaders; using System.Numerics; @@ -25,6 +24,16 @@ public CameraView(Vector3 position, IViewSettings settings) : base(settings) AspectRatio = settings.WindowOptions.Size.X / (float)settings.WindowOptions.Size.Y; } + /// + /// Creates a default camera view with the specified position and settings, or default values if not provided. + /// + /// The initial position of the camera. If not provided, defaults to (0,0,0). + /// The settings for the camera. If not provided, defaults to . + /// A new instance of . + public static CameraView CreateDefault(Vector3? position = null, IViewSettings? settings = null) + => new(position ?? new Vector3(0), + settings ?? new DefaultViewSettings()); + // Rotation around the X axis (radians) private float _pitch; diff --git a/Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs b/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs similarity index 99% rename from Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs rename to SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs index cef7852..03d03f7 100644 --- a/Engine/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs +++ b/SharpEngine.Core/Entities/Views/Settings/DefaultViewSettings.cs @@ -1,4 +1,5 @@ using SharpEngine.Core.Renderers; + using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.Windowing; diff --git a/Engine/SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs b/SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs similarity index 100% rename from Engine/SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs rename to SharpEngine.Core/Entities/Views/Settings/IViewSettings.cs diff --git a/Engine/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs b/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs similarity index 100% rename from Engine/SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs rename to SharpEngine.Core/Entities/Views/Settings/ViewSettings.cs diff --git a/Engine/SharpEngine.Core/Entities/Views/View.cs b/SharpEngine.Core/Entities/Views/View.cs similarity index 93% rename from Engine/SharpEngine.Core/Entities/Views/View.cs rename to SharpEngine.Core/Entities/Views/View.cs index 02aecba..b037f4e 100644 --- a/Engine/SharpEngine.Core/Entities/Views/View.cs +++ b/SharpEngine.Core/Entities/Views/View.cs @@ -1,9 +1,8 @@ -using SharpEngine.Core.Components.Properties; -using SharpEngine.Core.Entities.Views.Settings; +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; @@ -22,8 +21,13 @@ public View(IViewSettings settings) Settings = settings; } + /// Gets the front vector of the camera. protected Vector3 _front = -Vector3.UnitZ; + + /// Gets the up vector of the camera. protected Vector3 _up = Vector3.UnitY; + + /// Gets the right vector of the camera. protected Vector3 _right = Vector3.UnitX; /// Gets the front vector of the camera. diff --git a/Engine/SharpEngine.Core/Enums/MouseWheelScrollDirection.cs b/SharpEngine.Core/Enums/MouseWheelScrollDirection.cs similarity index 100% rename from Engine/SharpEngine.Core/Enums/MouseWheelScrollDirection.cs rename to SharpEngine.Core/Enums/MouseWheelScrollDirection.cs diff --git a/SharpEngine.Core/Extensions/AssemblyExtensions.cs b/SharpEngine.Core/Extensions/AssemblyExtensions.cs new file mode 100644 index 0000000..39a3315 --- /dev/null +++ b/SharpEngine.Core/Extensions/AssemblyExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Reflection; + +namespace SharpEngine.Core.Extensions; + +/// +/// Contains extension methods for the class, providing additional functionality related to assembly metadata and versioning. +/// +public static class AssemblyExtensions +{ + /// + /// Gets the version of the assembly as specified in its metadata. + /// + /// The assembly to get the version for. + /// The version of the assembly. + public static Version GetVersion(this Assembly assembly) + => assembly.GetName().Version!; +} diff --git a/Engine/SharpEngine.Core/Extensions/CollectionExtensions.cs b/SharpEngine.Core/Extensions/CollectionExtensions.cs similarity index 100% rename from Engine/SharpEngine.Core/Extensions/CollectionExtensions.cs rename to SharpEngine.Core/Extensions/CollectionExtensions.cs diff --git a/Engine/SharpEngine.Core/Extensions/PathExtensions.cs b/SharpEngine.Core/Extensions/PathExtensions.cs similarity index 100% rename from Engine/SharpEngine.Core/Extensions/PathExtensions.cs rename to SharpEngine.Core/Extensions/PathExtensions.cs diff --git a/SharpEngine.Core/Handlers/EngineHandler.cs b/SharpEngine.Core/Handlers/EngineHandler.cs new file mode 100644 index 0000000..212c54c --- /dev/null +++ b/SharpEngine.Core/Handlers/EngineHandler.cs @@ -0,0 +1,161 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Handlers +{ + + /// + /// Base class for long-running engine handlers that run asynchronously. + /// + /// + /// Implementations should override to perform the handler's work. + /// The handler can be started with and stopped with . + /// + /// Consumers can await to observe when the handler has finished executing. + /// + public abstract class EngineHandler : IAsyncDisposable + { + private Task? _runner; + private CancellationTokenSource? _cts; + private readonly TaskCompletionSource _completionSource = new(); + private DateTime _startTime; + + /// + /// Logger for recording diagnostic and operational messages for the handler. + /// + /// + /// Provides structured, category-specific logging for EngineHandler and its derived classes. + /// + protected readonly ILogger Logger; + + /// + /// Initializes a new instance of . + /// + /// A logger for logging handler events. + protected EngineHandler(ILogger logger) + { + Logger = logger; + } + + /// + /// Gets the current lifecycle state of the handler. + /// + public EngineHandlerState State { get; private set; } = EngineHandlerState.NotStarted; + + /// + /// Gets the total execution duration of the handler once it has stopped or faulted. + /// + public TimeSpan ExecutionDuration => State is EngineHandlerState.Stopped or EngineHandlerState.Faulted ? + DateTime.UtcNow - _startTime : + TimeSpan.Zero; + + /// + /// Starts the handler and begins executing on a background task. + /// + /// Thrown if the handler has already been started. + public void Start() + { + if (State != EngineHandlerState.NotStarted) + throw new InvalidOperationException("Handler already started."); + + _cts = new CancellationTokenSource(); + _startTime = DateTime.UtcNow; + _runner = Task.Run(() => RunInternalAsync(_cts.Token)); + } + + /// + /// Requests cancellation and waits for the handler to stop. + /// + /// + /// If the handler has not been started or is already stopped, this method returns immediately. + /// + public async Task StopAsync() + { + if (_cts == null || _runner == null || State == EngineHandlerState.Stopped) + { + Logger.LogDebug("[EngineHandler] '{Name}' is not running.", GetType().Name); + return; + } + + await _cts.CancelAsync(); + + try + { + await _runner; + } + catch (OperationCanceledException) + { + Logger.LogDebug("[EngineHandler] '{Name}' was canceled.", GetType().Name); + } + + await OnStopAsync(); + State = EngineHandlerState.Stopped; + + Logger.LogDebug("[EngineHandler] '{Name}' stopped.", GetType().Name); + } + + /// + /// Asynchronously waits for the handler to complete execution, either by stopping normally, + /// being cancelled, or faulting. + /// + /// A task that completes when the handler has finished. + public async Task WaitForCompletionAsync() => await _completionSource.Task; + + /// + /// Executes the handler's work. Implementations should honor the provided + /// and return promptly when it signals cancellation. + /// + /// The cancellation token that is signaled when the handler should stop. + protected abstract Task ExecuteAsync(CancellationToken token); + + /// + /// Optional override called before the execution of the handler. + /// + protected virtual Task OnInitializedAsync() => Task.CompletedTask; + + /// + /// Optional override called after the handler has stopped or been cancelled. + /// + protected virtual Task OnStopAsync() => Task.CompletedTask; + + private async Task RunInternalAsync(CancellationToken token) + { + try + { + State = EngineHandlerState.Running; + await OnInitializedAsync(); + await ExecuteAsync(token); + State = EngineHandlerState.Stopped; + } + catch (OperationCanceledException) + { + State = EngineHandlerState.Stopped; + Logger.LogDebug("[EngineHandler] '{Name}' was canceled.", GetType().Name); + } + catch (Exception ex) + { + State = EngineHandlerState.Faulted; + Logger.LogError(ex, "[EngineHandler] Unhandled exception in '{Name}': {Exception}", GetType().Name, ex.Message); + } + finally + { + _completionSource.TrySetResult(); + } + } + + /// + /// + /// Disposes internal resources used by the handler. Implementations that + /// require asynchronous disposal should override this method. + /// + public virtual ValueTask DisposeAsync() + { + _cts?.Dispose(); + GC.SuppressFinalize(this); + return ValueTask.CompletedTask; + } + } +} + \ No newline at end of file diff --git a/SharpEngine.Core/Handlers/EngineHandlerState.cs b/SharpEngine.Core/Handlers/EngineHandlerState.cs new file mode 100644 index 0000000..44153c4 --- /dev/null +++ b/SharpEngine.Core/Handlers/EngineHandlerState.cs @@ -0,0 +1,25 @@ +/// +/// Represents the lifecycle state of an engine handler. +/// +public enum EngineHandlerState +{ + /// + /// The engine handler has not been started yet. + /// + NotStarted, + + /// + /// The engine handler is currently running. + /// + Running, + + /// + /// The engine handler has been stopped gracefully. + /// + Stopped, + + /// + /// The engine handler has encountered an error and is in a faulted state. + /// + Faulted +} diff --git a/SharpEngine.Core/Handlers/ParticleEmitter.cs b/SharpEngine.Core/Handlers/ParticleEmitter.cs new file mode 100644 index 0000000..3d62aaf --- /dev/null +++ b/SharpEngine.Core/Handlers/ParticleEmitter.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using SharpEngine.Core.Entities; +using System; +using System.Collections.Generic; + +using System.Threading; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Handlers; + +internal class ParticleEmitter : EngineHandler +{ + public int EmissionRateMilliseconds { get; set; } + public int ParticleLifeTimeMilliseconds { get; set; } + + private readonly List _particles = new(); + private readonly object _particlesLock = new(); + + private readonly SemaphoreSlim _pauseSemaphore = new(1, 1); + private bool _isPaused = false; + + /// + /// Initializes a new instance of . + /// + /// The logger to use for logging events. + public ParticleEmitter(ILogger logger) : base(logger) { } + + /// + protected override async Task ExecuteAsync(CancellationToken token) + { + // Run disposer and emitter concurrently so particles are emitted and + // disposed in parallel while respecting pause behavior. + await Task.WhenAll(StartDisposer(token), StartEmitter(token)); + } + + private async Task StartEmitter(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + // Wait if emitting is paused + await _pauseSemaphore.WaitAsync(token); + _pauseSemaphore.Release(); + + EmitParticle(); + + // Wait for the next emission + await Task.Delay(EmissionRateMilliseconds, token); + } + } + + private async Task StartDisposer(CancellationToken token) + { + // Periodically remove expired particles. This loop honors the same + // pause semaphore so disposal pauses when emitter is paused. + while (!token.IsCancellationRequested) + { + // Wait if emitting is paused + await _pauseSemaphore.WaitAsync(token); + _pauseSemaphore.Release(); + + // Remove expired particles + var nowTicks = DateTime.UtcNow.Ticks; + lock (_particlesLock) + { + _particles.RemoveAll(p => (nowTicks - p.StartTimeTicks) / TimeSpan.TicksPerMillisecond >= p.LifeTimeMilliseconds); + } + + // Avoid a tight loop — check disposal every 50ms + await Task.Delay(50, token); + } + } + + private void EmitParticle() + { + var particle = new Particle(ParticleLifeTimeMilliseconds); + lock (_particlesLock) + { + _particles.Add(particle); + } + } + + /// + /// Pauses the emission of particles. + /// + /// + /// If already paused, this method does nothing. + /// + public void Pause() + { + if (_isPaused) + return; + + _isPaused = true; + _pauseSemaphore.Wait(); + } + + /// + /// Resumes the emission of particles. + /// + /// + /// If not paused, this method does nothing. + /// + public void Resume() + { + if (!_isPaused) + return; + + _pauseSemaphore.Release(); + _isPaused = false; + } +} \ No newline at end of file diff --git a/SharpEngine.Core/Handlers/WindowHandler.cs b/SharpEngine.Core/Handlers/WindowHandler.cs new file mode 100644 index 0000000..ef2d1ec --- /dev/null +++ b/SharpEngine.Core/Handlers/WindowHandler.cs @@ -0,0 +1,244 @@ +using Microsoft.Extensions.Logging; + +using SharpEngine.Core.Entities.Views.Settings; +using SharpEngine.Telemetry; +using SharpEngine.Core.Windowing; + +using Silk.NET.Input; +using Silk.NET.Maths; +using Silk.NET.Windowing; + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Handlers; + +/// +/// Manages application windows and their input contexts. +/// +/// This handler maintains queue of window creation requests, starts a background task to process that queue, +/// and runs the per-frame update/render loop for all active windows until cancellation is requested. +/// +/// +/// Windows are created and enqueued on a background task. +/// +/// The handler will call DoEvents, DoUpdate and DoRender on each managed window every loop iteration. +/// When a window is closing it will be disposed and removed from the managed list. +/// +public class WindowHandler : EngineHandler +{ + private static readonly List _windows = []; + private static readonly List _inputContexts = []; + private static readonly ConcurrentQueue _windowQueue = []; + private static readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly ILogger _logger; + + private SilkWindow? _mainWindow; + + /// + /// Gets the main window registered with this handler, if any. + /// + public SilkWindow? MainWindow => _mainWindow; + + /// + /// Initializes a new instance of the + /// + /// + /// Starts the window queue as a background task. + /// + /// Optional logger instance; if null a default logger will be created. + public WindowHandler(ILogger? logger = null) : base(logger ?? LoggingExtensions.CreateLogger()) + { + _logger = logger ?? LoggingExtensions.CreateLogger(); + + // Enqueue a default window creation request when no explicit window is provided. + StartWindowQueueTask(WindowOptions.Default); + } + + /// + /// Initializes a new instance of the + /// + /// + /// Starts the window queue as a background task. + /// + /// An existing window instance. + /// Optional logger instance; if null a default logger will be created. + public WindowHandler(SilkWindow window, ILogger? logger = null) : base(logger ?? LoggingExtensions.CreateLogger()) + { + _logger = logger ?? LoggingExtensions.CreateLogger(); + + // Register the provided window as the main window so it is managed immediately. + RegisterWindow(window, isMain: true); + + // Start the queue task but do not enqueue an additional default window. + StartWindowQueueTask(window.Settings.WindowOptions); + } + + /// + /// Starts a background task that enqueues an initial and runs until the shared cancellation token is requested. + /// + /// Optional window options to enqueue; if null is used. + private void StartWindowQueueTask(WindowOptions? options = null) + { + Task.Run(async () => + { + // Only enqueue when an explicit options value is provided. The caller + // controls whether a default window should be queued. + if (options is not null) + _windowQueue.Enqueue(options.Value); + + while (!_cancellationTokenSource.IsCancellationRequested) + { + await Task.Delay(1000); + _logger.LogDebug("Running loop on background thread..."); + } + }); + } + + /// + /// + /// Runs the main update loop for managed windows. + /// + /// + /// This method will continue to run until the provided cancellation is cancelled. + /// + /// A that, when cancelled, ends the loop. + protected override Task ExecuteAsync(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + for (int i = 0; i < _windows.Count; i++) + UpdateWindow(ref i); + + DequeueWindows(); + } + + return Task.FromCanceled(token); + } + + /// + /// Advances a single window through its event, update and render phases. + /// + /// + /// If the window is closing it will be disposed and removed from the managed list. + /// + /// + /// Reference to the index of the window in the internal list; + /// this method may decrement the index if the window is removed. + /// + private static void UpdateWindow(ref int i) + { + var window = _windows[i]; + if (window is null) + return; + + window.DoEvents(); + window.DoUpdate(); + window.DoRender(); + + if (window.IsClosing) + { + window.Reset(); + window.Dispose(); + + _windows.RemoveAt(i); + i--; // Adjust the index to account for the removed item + + if (_windows.Count == 0) + _cancellationTokenSource.Cancel(); + } + } + + /// + /// Dequeues any pending window creation requests and enqueues the resulting windows for management. + /// No action is taken if cancellation has been requested. + /// + private static void DequeueWindows() + { + if (_cancellationTokenSource.IsCancellationRequested) + return; + + while (_windowQueue.TryDequeue(out var options)) + EnqueueWindow(options); + } + + /// + /// Creates and initializes a new instance using the provided . + /// + /// The created window's position will be offset based on the current number of managed windows to help avoid overlap. + /// + /// Options to use when creating the window. + /// A newly initialized . + private static Windowing.Window CreateWindow(WindowOptions options) + { + var viewSettings = new DefaultViewSettings() with + { + WindowOptions = options with + { + Title = options.Title ?? "Window" + _windows.Count, + + // This is to make sure the windows don't overlap + Position = new Vector2D( + x: 500 + (50 * _windows.Count), + y: 400 + (50 * _windows.Count)) + } + }; + + var window = new Windowing.Window(new(), viewSettings, LoggingExtensions.CreateLogger()); + window.Initialize(); + + return window; + } + + /// + /// Creates a new window from the provided options, wires up mouse click handlers, and begins managing the window and its input context. + /// + /// Options used to create the new window. + private static void EnqueueWindow(WindowOptions options) + { + var window = CreateWindow(options); + foreach (var mouse in window!.Input!.Mice) + mouse.Click += Mouse_Click; + + _inputContexts.Add(window.Input); + _windows.Add(window); + } + + /// + /// Registers an existing window instance with the handler and optionally marks it as the main window. + /// + /// The window instance to register. + /// Whether this window should be considered the main window. + private void RegisterWindow(SilkWindow window, bool isMain = false) + { + if (window is null) + return; + + if (window.Input is not null) + { + foreach (var mouse in window.Input.Mice) + mouse.Click += Mouse_Click; + + _inputContexts.Add(window.Input); + } + + _windows.Add(window); + + if (isMain) + _mainWindow = window; + } + + /// + /// Global mouse click handler that enqueues a request to create a new default window. + /// + /// The mouse instance that raised the click event. + /// The mouse button that was clicked. + /// The pointer position when the click occurred. + private static void Mouse_Click(IMouse args1, Silk.NET.Input.MouseButton arg2, System.Numerics.Vector2 arg3) + { + _windowQueue.Enqueue(WindowOptions.Default); + } +} diff --git a/SharpEngine.Core/Primitives/Cube.cs b/SharpEngine.Core/Primitives/Cube.cs new file mode 100644 index 0000000..76ac6ae --- /dev/null +++ b/SharpEngine.Core/Primitives/Cube.cs @@ -0,0 +1,265 @@ +using SharpEngine.Core._Resources; +using SharpEngine.Core.Components.Properties; +using SharpEngine.Core.Components.Properties.Meshes; +using SharpEngine.Core.Components.Properties.Textures; +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Textures; +using SharpEngine.Core.Windowing; + +using System.Collections.Generic; + +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.SharedGL) + { + Vertices = [.. Vertices], + Normals = [.. Normals], + TextureCoordinates = [.. TextureCoordinates], + Indices = [.. Indices], + Textures = [defaultTexture], + // Materials = [MaterialService.Instance.LoadMaterial(Default.DebugMaterial)], + Materials = [new("Debug", defaultTexture)] + }; + + Mesh = MeshService.Instance.LoadMesh(nameof(Cube), mesh); + Model = new(Window.SharedGL, string.Empty, [Mesh]); + + _loaded = true; + } + + private readonly static bool _loaded; + + /// The loaded model of the cube. + public static Model Model { get; private set; } = null!; + + /// The cube mesh. + public static readonly Mesh Mesh = null!; + + private static readonly 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, + ]; + + private readonly 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, + ]; + + private readonly 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 + ]; + + private readonly 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 + ]; + + /// + /// Creates a model with a mesh containing diffuse and optional specular texture maps. + /// + /// The file path to the diffuse texture map. + /// The file path to the specular texture map, or to omit specular mapping. + /// A new model instance containing a mesh with the specified texture maps. + public static Model CreateModel(string diffuseMapFile, string? specularMapFile = null) + { + var diffuseTexture = TextureService.Instance.LoadTexture(diffuseMapFile, TextureType.Diffuse); + var specularTexture = string.IsNullOrWhiteSpace(specularMapFile) ? + null : TextureService.Instance.LoadTexture(specularMapFile, TextureType.Specular); + + var material = new Material("Debug", diffuseTexture, specularTexture); + var textures = new List { diffuseTexture }; + + if (specularTexture is not null && specularTexture.Handle != diffuseTexture.Handle) + textures.Add(specularTexture); + + var mesh = new Mesh(Window.SharedGL, BuildVertices(), [], textures) + { + Name = Mesh.Name, + Materials = [material] + }; + + var model = new Model(Window.SharedGL, string.Empty); + model.Meshes.Add(mesh); + + return model; + } + + private static float[] BuildVertices() + { + var vertices = new List(Vertices.Length + Normals.Length + TextureCoordinates.Length); + + for (int i = 0; i < Vertices.Length / 3; i++) + { + var vertexIndex = i * 3; + var texCoordIndex = i * 2; + + vertices.Add(Vertices[vertexIndex]); + vertices.Add(Vertices[vertexIndex + 1]); + vertices.Add(Vertices[vertexIndex + 2]); + + vertices.Add(Normals[vertexIndex]); + vertices.Add(Normals[vertexIndex + 1]); + vertices.Add(Normals[vertexIndex + 2]); + + vertices.Add(TextureCoordinates[texCoordIndex]); + vertices.Add(TextureCoordinates[texCoordIndex + 1]); + } + + return [.. vertices]; + } +} diff --git a/Engine/SharpEngine.Core/Primitives/Plane.cs b/SharpEngine.Core/Primitives/Plane.cs similarity index 93% rename from Engine/SharpEngine.Core/Primitives/Plane.cs rename to SharpEngine.Core/Primitives/Plane.cs index ba046a5..854ae7a 100644 --- a/Engine/SharpEngine.Core/Primitives/Plane.cs +++ b/SharpEngine.Core/Primitives/Plane.cs @@ -9,7 +9,7 @@ namespace SharpEngine.Core.Primitives; public static class Plane { /// The plane mesh. - public static Mesh Mesh { get; } = new(Window.GL) + public static Mesh Mesh { get; } = new(Window.SharedGL) { Vertices = [ diff --git a/Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs b/SharpEngine.Core/Primitives/PrimitiveFactory.cs similarity index 77% rename from Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs rename to SharpEngine.Core/Primitives/PrimitiveFactory.cs index 633580d..1148e59 100644 --- a/Engine/SharpEngine.Core/Primitives/PrimitiveFactory.cs +++ b/SharpEngine.Core/Primitives/PrimitiveFactory.cs @@ -1,11 +1,9 @@ -using SharpEngine.Core.Entities; +using SharpEngine.Core.Components.Properties.Meshes; +using SharpEngine.Core.Entities; using SharpEngine.Core.Entities.Properties; -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; @@ -33,18 +31,17 @@ public static GameObject Create(PrimitiveType primitiveType, Vector3 position) /// 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) { - // var material = diffuseMapFile, specularMapFile, - - Model_Old model = primitiveType switch + Model model = primitiveType switch { - PrimitiveType.Cube => Cube.Model, + PrimitiveType.Cube => Cube.CreateModel(diffuseMapFile, specularMapFile), // 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) + // Shader compilation is context-dependent; the GameObject will acquire the correct shader per-window during rendering. + return new GameObject() { + Model = model, Transform = new Transform((Numerics.Vector3)position), }; } diff --git a/Engine/SharpEngine.Core/Primitives/PrimitiveType.cs b/SharpEngine.Core/Primitives/PrimitiveType.cs similarity index 100% rename from Engine/SharpEngine.Core/Primitives/PrimitiveType.cs rename to SharpEngine.Core/Primitives/PrimitiveType.cs diff --git a/Engine/SharpEngine.Core/Ray.cs b/SharpEngine.Core/Ray.cs similarity index 100% rename from Engine/SharpEngine.Core/Ray.cs rename to SharpEngine.Core/Ray.cs diff --git a/SharpEngine.Core/Renderers/ParticleRenderer.cs b/SharpEngine.Core/Renderers/ParticleRenderer.cs new file mode 100644 index 0000000..53f6d93 --- /dev/null +++ b/SharpEngine.Core/Renderers/ParticleRenderer.cs @@ -0,0 +1,25 @@ +using SharpEngine.Core.Interfaces; +using System; +using System.Threading.Tasks; + +namespace SharpEngine.Core.Renderers; + +/// +/// Renders particle effects. +/// +public class ParticleRenderer : RendererBase +{ + /// + /// Initializes a new instance of the . + /// + /// The settings for the particle renderer. + public ParticleRenderer(ISettings settings) : base(settings) + { + } + + /// + public override RenderFlags RenderFlag => RenderFlags.All; + + /// + public override Task Render() => throw new NotImplementedException(); +} diff --git a/Engine/SharpEngine.Core/Renderers/RenderFlags.cs b/SharpEngine.Core/Renderers/RenderFlags.cs similarity index 100% rename from Engine/SharpEngine.Core/Renderers/RenderFlags.cs rename to SharpEngine.Core/Renderers/RenderFlags.cs diff --git a/Engine/SharpEngine.Core/Renderers/Renderer.cs b/SharpEngine.Core/Renderers/Renderer.cs similarity index 69% rename from Engine/SharpEngine.Core/Renderers/Renderer.cs rename to SharpEngine.Core/Renderers/Renderer.cs index 91f4417..873a359 100644 --- a/Engine/SharpEngine.Core/Renderers/Renderer.cs +++ b/SharpEngine.Core/Renderers/Renderer.cs @@ -1,11 +1,13 @@ using SharpEngine.Core.Entities; +using SharpEngine.Core.Entities.Lights; using SharpEngine.Core.Entities.Properties; 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 Microsoft.Extensions.Logging; using Silk.NET.OpenGL; using System; @@ -19,12 +21,14 @@ namespace SharpEngine.Core.Renderers; /// public class Renderer : RendererBase { - private readonly LampShader _lampShader; - private readonly LightingShader _lightingShader; + private LampShader _lampShader = null!; + private LightingShader _lightingShader = null!; private readonly CameraView _camera; private readonly Scene _scene; - private readonly Window _window; + private readonly ILogger _logger; + + private GL _gl = null!; // TODO: #7 Property for specific type of objects // No heavy iteration reads for filtering, @@ -40,18 +44,24 @@ public class Renderer : RendererBase /// Initializes a new instance of . /// /// The game the renderer is being used for. - /// The window executing the renderer. /// The settings for the renderer. /// The game scene to be rendered. - public Renderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) + /// The logger for the renderer. + public Renderer(CameraView camera, ISettings settings, Scene scene, ILogger logger) : base(settings) { _camera = camera; _scene = scene; - _window = window; + _logger = logger; + } + + /// + protected override void OnWindowAttached(Window window) + { + _gl = window.GetGL(); // TODO: #5 These should be refactored out. The minimum build shouldn't need to use these. - _lightingShader = new LightingShader(); - _lampShader = new LampShader(); + _lightingShader = new LightingShader(_gl); + _lampShader = new LampShader(_gl); } /// @@ -62,33 +72,37 @@ public override Task Render() try { - Window.GL.Enable(EnableCap.DepthTest); + _gl.Enable(EnableCap.DepthTest); // Enable image transparency. // TODO: #62 Needs testing. - Window.GL.Enable(EnableCap.Blend); - Window.GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + _gl.Enable(EnableCap.Blend); + _gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); _camera.SetShaderUniforms(_lightingShader.Shader!); - Window.GL.BindVertexArray(_lightingShader.Vao); + _gl.BindVertexArray(_lightingShader.Vao); + var lightRenderTasks = _scene.IterateAsync(_scene.Root.Children, RenderLight); + Task.WaitAll([.. lightRenderTasks]); + + // TODO: Streamline this part where the game objects are rendered var gameObjectRenderTasks = _scene.IterateAsync(_scene.Root.Children, RenderGameObject); var renderTask = Task.WhenAll(gameObjectRenderTasks); - Window.GL.BindVertexArray(_lampShader.Vao); + _gl.BindVertexArray(_lampShader.Vao); return renderTask; } catch (Exception ex) { - Debug.Log.Error(ex, "{Message}", ex.Message); + _logger.LogError(ex, "{Message}", ex.Message); return Task.FromException(ex); } } private Task RenderGameObject(SceneNode node) { - if (node is not GameObject gameObject) + if (node is not GameObject gameObject || node is Light) return Task.CompletedTask; // TODO: #7 Fix culling for blocks that are partially in view @@ -97,8 +111,16 @@ private Task RenderGameObject(SceneNode node) return Task.CompletedTask; // TODO: #7 Skip blocks that are behind others relative to the camera - return gameObject.Render(_camera, _window); - // gameObject.Render(_camera, _window); + return gameObject.Render(_camera, Window); + // gameObject.Render(_camera, Window); + } + + private Task RenderLight(SceneNode node) + { + if (node is not Light light) + return Task.CompletedTask; + + return light.Render(_camera, Window); } private static bool IsInViewFrustum(BoundingBox boundingBox, CameraView camera) diff --git a/Engine/SharpEngine.Core/Renderers/RendererBase.cs b/SharpEngine.Core/Renderers/RendererBase.cs similarity index 64% rename from Engine/SharpEngine.Core/Renderers/RendererBase.cs rename to SharpEngine.Core/Renderers/RendererBase.cs index 1491fab..ea99872 100644 --- a/Engine/SharpEngine.Core/Renderers/RendererBase.cs +++ b/SharpEngine.Core/Renderers/RendererBase.cs @@ -1,5 +1,6 @@ -using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Interfaces; +using SharpEngine.Core.Windowing; + using System; using System.Threading.Tasks; @@ -13,6 +14,9 @@ public abstract class RendererBase : IDisposable /// Gets or sets the settings for the renderer. protected ISettings Settings; + /// Gets the window attached to the renderer. + protected Window Window { get; private set; } = null!; + /// /// Initializes a new instance of . /// @@ -27,6 +31,24 @@ protected RendererBase(ISettings settings) /// public abstract RenderFlags RenderFlag { get; } + /// + /// Attaches the renderer to a window after both have been constructed. + /// + /// The window that owns the renderer. + public void AttachWindow(Window window) + { + ArgumentNullException.ThrowIfNull(window); + + Window = window; + OnWindowAttached(window); + } + + /// + /// Allows derived renderers to react when a window is attached. + /// + /// The attached window. + protected virtual void OnWindowAttached(Window window) { } + /// /// Initializes the renderer. /// diff --git a/SharpEngine.Core/Renderers/TextRenderer.cs b/SharpEngine.Core/Renderers/TextRenderer.cs new file mode 100644 index 0000000..15670c8 --- /dev/null +++ b/SharpEngine.Core/Renderers/TextRenderer.cs @@ -0,0 +1,31 @@ +using SharpEngine.Core.Entities.Views; +using SharpEngine.Core.Interfaces; +using SharpEngine.Core.Scenes; + +using System.Threading.Tasks; + +namespace SharpEngine.Core.Renderers; + +/// +/// A renderer responsible for rendering text in the scene. +/// +internal class TextRenderer : RendererBase +{ + /// + /// Initializes a new instance of the TextRenderer class with the specified camera, settings, and scene. + /// + /// CameraView that provides view and projection information for rendering. + /// ISettings that configures renderer behavior. + /// Scene that contains renderable text elements. + public TextRenderer(CameraView camera, ISettings settings, Scene scene) : base(settings) + { + } + + public override RenderFlags RenderFlag => RenderFlags.Text; + + /// + public override Task Render() + { + return Task.CompletedTask; + } +} diff --git a/Engine/SharpEngine.Core/Renderers/UIRenderer.cs b/SharpEngine.Core/Renderers/UIRenderer.cs similarity index 56% rename from Engine/SharpEngine.Core/Renderers/UIRenderer.cs rename to SharpEngine.Core/Renderers/UIRenderer.cs index 54ee5da..931b0c5 100644 --- a/Engine/SharpEngine.Core/Renderers/UIRenderer.cs +++ b/SharpEngine.Core/Renderers/UIRenderer.cs @@ -2,9 +2,9 @@ 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 Microsoft.Extensions.Logging; using Silk.NET.OpenGL; using System; @@ -19,7 +19,9 @@ public class UIRenderer : RendererBase { private readonly Scene _scene; private readonly CameraView _camera; - private readonly Window _window; + private readonly ILogger _logger; + + private GL _gl = null!; /// public override RenderFlags RenderFlag => RenderFlags.UIRenderer; @@ -27,11 +29,25 @@ public class UIRenderer : RendererBase /// /// Initializes a new instance of . /// - public UIRenderer(CameraView camera, Window window, ISettings settings, Scene scene) : base(settings) + public UIRenderer(CameraView camera, ISettings settings, Scene scene) + : this(camera, settings, scene, LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger()) + { + } + + /// + /// Initializes a new instance of . + /// + public UIRenderer(CameraView camera, ISettings settings, Scene scene, ILogger logger) : base(settings) { _scene = scene; _camera = camera; - _window = window; + _logger = logger; + } + + /// + protected override void OnWindowAttached(Window window) + { + _gl = window.GetGL(); } /// @@ -42,19 +58,19 @@ public override Task Render() try { - Window.GL.Enable(EnableCap.DepthTest); - Window.GL.DepthFunc(DepthFunction.Less); + _gl.Enable(EnableCap.DepthTest); + _gl.DepthFunc(DepthFunction.Less); // Disable face culling to render both sides of the quad - Window.GL.Disable(EnableCap.CullFace); + _gl.Disable(EnableCap.CullFace); - var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera, _window)); + var uiElementRenderTasks = _scene.IterateAsync(_scene.UIElements, elem => elem.Render(_camera, Window)); return Task.WhenAll(uiElementRenderTasks); } catch (Exception ex) { - Debug.Log.Error(ex, "{Message}", ex.Message); + _logger.LogError(ex, "{Message}", ex.Message); return Task.FromException(ex); } } diff --git a/Engine/SharpEngine.Core/Scenes/Scene.cs b/SharpEngine.Core/Scenes/Scene.cs similarity index 97% rename from Engine/SharpEngine.Core/Scenes/Scene.cs rename to SharpEngine.Core/Scenes/Scene.cs index e53d524..ff4db26 100644 --- a/Engine/SharpEngine.Core/Scenes/Scene.cs +++ b/SharpEngine.Core/Scenes/Scene.cs @@ -1,7 +1,6 @@ -using SharpEngine.Core.Entities; +using Microsoft.Extensions.Logging; using SharpEngine.Core.Entities.Properties; -using SharpEngine.Core.Entities.UI; -using SharpEngine.Shared; + using System; using System.Collections.Generic; using System.Linq; @@ -15,6 +14,8 @@ namespace SharpEngine.Core.Scenes; /// public class Scene { + private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + /// The file extension by which saved scenes are associated with. public const string SceneFileExtension = "sharpscene"; @@ -183,7 +184,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) { - Debug.Log.Debug(sceneFile); + Logger.LogDebug("Loading scene from {SceneFile}", sceneFile); var loadedScene = JsonSerializer.Deserialize(sceneFile); diff --git a/Engine/SharpEngine.Core/Scenes/SceneNode.cs b/SharpEngine.Core/Scenes/SceneNode.cs similarity index 100% rename from Engine/SharpEngine.Core/Scenes/SceneNode.cs rename to SharpEngine.Core/Scenes/SceneNode.cs diff --git a/SharpEngine.Core/Shaders/LampShader.cs b/SharpEngine.Core/Shaders/LampShader.cs new file mode 100644 index 0000000..f7ce4e1 --- /dev/null +++ b/SharpEngine.Core/Shaders/LampShader.cs @@ -0,0 +1,43 @@ +using SharpEngine.Core._Resources; +using SharpEngine.Core.Entities.Properties.Meshes; + +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Shaders; + +internal class LampShader : ShaderBase +{ + private readonly GL _gl; + + /// + /// Initializes a new instance of using the provided OpenGL context. + /// + /// The OpenGL context to use for shader and VAO creation. + public LampShader(GL gl) + { + _gl = gl; + + Shader = ShaderService.Instance.LoadShader(_gl, Default.VertexShader, Default.LightShader, "lamp"); + + Vao = _gl.GenVertexArray(); + _gl.BindVertexArray(Vao); + + SetAttributes(_gl); + } + + /// + public override bool SetAttributes(GL gl) + { + if (!base.SetAttributes(gl)) + return false; + + if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation)) + return false; + + var positionLocationUint = (uint)positionLocation; + _gl.EnableVertexAttribArray(positionLocationUint); + _gl.VertexAttribPointer(positionLocationUint, 3, VertexAttribPointerType.Float, false, VertexData.Stride, 0); + + return true; + } +} diff --git a/SharpEngine.Core/Shaders/LightingShader.cs b/SharpEngine.Core/Shaders/LightingShader.cs new file mode 100644 index 0000000..55b5e5e --- /dev/null +++ b/SharpEngine.Core/Shaders/LightingShader.cs @@ -0,0 +1,56 @@ +using SharpEngine.Core._Resources; + +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Shaders; + +internal class LightingShader : ShaderBase +{ + private readonly GL _gl; + + /// + /// Initializes a new instance of . + /// + /// The OpenGL context used to create and configure the shader program. + public LightingShader(GL gl) + { + _gl = gl; + + Shader = ShaderService.Instance.LoadShader(_gl, Default.VertexShader, Default.FragmentShader, "lighting"); + + Vao = _gl.GenVertexArray(); + _gl.BindVertexArray(Vao); + + SetAttributes(_gl); + } + + /// + public override bool SetAttributes(GL gl) + { + if (!base.SetAttributes(gl)) + return false; + + /*if (!Shader!.TryGetAttribLocation(ShaderAttributes.Pos, out int positionLocation)) + return false; + + var positionLocationUint = (uint)positionLocation; + _gl.EnableVertexAttribArray(positionLocationUint); + _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; + _gl.EnableVertexAttribArray(normalLocationUint); + _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; + _gl.EnableVertexAttribArray(texCoordLocationUint); + _gl.VertexAttribPointer(texCoordLocationUint, VertexData.TexCoordsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.TexCoordsOffset); + */ + return true; + } +} diff --git a/Engine/SharpEngine.Core/Shaders/ShaderBase.cs b/SharpEngine.Core/Shaders/ShaderBase.cs similarity index 69% rename from Engine/SharpEngine.Core/Shaders/ShaderBase.cs rename to SharpEngine.Core/Shaders/ShaderBase.cs index e7fd389..492217a 100644 --- a/Engine/SharpEngine.Core/Shaders/ShaderBase.cs +++ b/SharpEngine.Core/Shaders/ShaderBase.cs @@ -1,6 +1,5 @@ -using SharpEngine.Core.Components.Properties; -using SharpEngine.Shared; -using System; +using Microsoft.Extensions.Logging; +using Silk.NET.OpenGL; namespace SharpEngine.Core.Shaders; @@ -9,6 +8,8 @@ namespace SharpEngine.Core.Shaders; /// public abstract class ShaderBase { + private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + /// Gets the shader. public Shader? Shader { get; protected set; } @@ -21,11 +22,11 @@ public abstract class ShaderBase /// /// if the attributes were set successfully; otherwise . /// - public virtual bool SetAttributes() + public virtual bool SetAttributes(GL gl) { if (Shader is null) { - Debug.Log.Error("Unable to set shader attributes, shader not found."); + Logger.LogError("Unable to set shader attributes, shader not found."); return false; } diff --git a/SharpEngine.Core/Shaders/ShaderService.cs b/SharpEngine.Core/Shaders/ShaderService.cs new file mode 100644 index 0000000..74fee61 --- /dev/null +++ b/SharpEngine.Core/Shaders/ShaderService.cs @@ -0,0 +1,134 @@ +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.IO; + +using SharpEngine.Core.Windowing; +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Shaders; + +/// +/// Contains all the shaders used in the game. +/// +public class ShaderService +{ + private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + + /// + /// Gets the singleton instance of the . + /// + public static ShaderService Instance { get; } = new ShaderService(); + + private readonly Dictionary _shaderCache = []; + + private readonly record struct ShaderCacheKey(string Name, object ShareGroupKey); + + /// + /// Gets or sets whether there are shaders to load. + /// + public bool HasShadersToLoad { get; set; } = true; + + /// + /// Private constructor to prevent instantiation. + /// + private ShaderService() { } + + /// + /// Gets all the shaders in the cache. + /// + /// All the shaders found from the cache. + public List GetAll() + { + HasShadersToLoad = false; + return [.. _shaderCache.Values]; + } + + /// + /// Gets a shader by its name. + /// + /// The name of the shader to be found. + /// The found shader. + /// + /// Thrown if a shader by that is not found. + /// This exception is thrown to make sure there are no unexpected issues made by the developer. + /// + public Shader GetByName(string name) + { + // Legacy API: return first shader found with this name. + foreach (var kvp in _shaderCache) + if (kvp.Key.Name == name) + return kvp.Value; + + throw new KeyNotFoundException($"Shader with name {name} not found in cache."); + } + + /// + /// Loads a shader from the specified vertex and fragment paths.
+ /// If the shader is loaded already, adds it to the cache. + ///
+ /// The game window. + /// The vertex shader full path. + /// The fragment shader full path. + /// A name identifier for the shader. + /// A shader with the given name. + /// Thrown when either the vertex or fragment shader is not found. + public Shader LoadShader(Window window, string vertPath, string fragPath, string name) + => LoadShader(window.GetGL(), GetShareGroupKey(window), vertPath, fragPath, name); + + /// + /// Loads a shader from the specified vertex and fragment paths.
+ /// If the shader is loaded already, adds it to the cache. + ///
+ /// The OpenGL context. + /// The vertex shader full path. + /// The fragment shader full path. + /// A name identifier for the shader. + /// A shader with the given name. + /// Thrown when either the vertex or fragment shader is not found. + public Shader LoadShader(GL gl, string vertPath, string fragPath, string name) + => LoadShader(gl, shareGroupKey: gl, vertPath, fragPath, name); + + /// + /// Loads a shader from the specified vertex and fragment paths.
+ /// If the shader is loaded already, adds it to the cache. + ///
+ /// The OpenGL context. + /// A key representing the share group for the shader. + /// The vertex shader full path. + /// The fragment shader full path. + /// A name identifier for the shader. + /// A shader with the given name. + /// Thrown when either the vertex or fragment shader is not found. + public Shader LoadShader(GL gl, object shareGroupKey, string vertPath, string fragPath, string name) + { + var cacheKey = new ShaderCacheKey(name, shareGroupKey); + + // Check if the shader is already in the cache for this share group. + if (_shaderCache.TryGetValue(cacheKey, out var cachedShader)) + return cachedShader; + + if (!File.Exists(vertPath)) + { + Logger.LogInformation("Vertex shader file not found: {VertPath}", vertPath); + throw new FileNotFoundException($"Vertex shader file not found: {vertPath}"); + } + + if (!File.Exists(fragPath)) + { + Logger.LogInformation("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. + // Shader program objects are shareable across contexts *only* when those contexts share. + var shader = new Shader(gl, vertPath, fragPath, name).Initialize(); + _shaderCache[cacheKey] = shader; + + HasShadersToLoad = true; + + return shader; + } + + private static object GetShareGroupKey(Window window) + => (object?)window.SharedContext ?? (object?)window.GLContext ?? (object)window; +} diff --git a/SharpEngine.Core/Shaders/UIShader.cs b/SharpEngine.Core/Shaders/UIShader.cs new file mode 100644 index 0000000..50e80a6 --- /dev/null +++ b/SharpEngine.Core/Shaders/UIShader.cs @@ -0,0 +1,50 @@ +using SharpEngine.Core.Entities.Properties.Meshes; +using SharpEngine.Core.Windowing; +using SharpEngine.Core._Resources; + +using Silk.NET.OpenGL; + +namespace SharpEngine.Core.Shaders; + +/// +/// Represents a shader used for rendering UI elements. +/// +/// +/// This shader is responsible for rendering 2D UI components on the screen, such as buttons, panels, and other interface elements. +/// It is designed to work with the specific vertex and fragment shaders defined for UI rendering in the game engine. +/// +internal class UIShader : ShaderBase +{ + /// + /// Ensures that the shader is initialized. + /// + /// + /// This method should be called before using the shader to ensure that it is properly loaded and ready for use. + /// + /// The window where the shader will be used. + public void EnsureInitialized(Window window) + { + if (Shader is not null) + return; + + Shader = ShaderService.Instance.LoadShader(window, Default.UIVertexShader, Default.UIFragmentShader, nameof(UIShader)); + } + + /// + public override bool SetAttributes(GL gl) + { + if (!base.SetAttributes(gl)) + return false; + + gl.EnableVertexAttribArray(0); + gl.VertexAttribPointer(0, VertexData.VerticesSize, VertexAttribPointerType.Float, false, VertexData.Stride, 0); + + gl.EnableVertexAttribArray(1); + gl.VertexAttribPointer(1, VertexData.NormalsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.NormalsOffset); + + gl.EnableVertexAttribArray(2); + gl.VertexAttribPointer(2, VertexData.TexCoordsSize, VertexAttribPointerType.Float, false, VertexData.Stride, VertexData.TexCoordsOffset); + + return true; + } +} diff --git a/Engine/SharpEngine.Core/SharpEngine.Core.csproj b/SharpEngine.Core/SharpEngine.Core.csproj similarity index 75% rename from Engine/SharpEngine.Core/SharpEngine.Core.csproj rename to SharpEngine.Core/SharpEngine.Core.csproj index 8633518..04fb6cb 100644 --- a/Engine/SharpEngine.Core/SharpEngine.Core.csproj +++ b/SharpEngine.Core/SharpEngine.Core.csproj @@ -1,6 +1,8 @@  + $(DotNetTargetFramework) + Everything needed to build games using a purely C# game engine. AnyCPU;x64;x86 @@ -19,20 +21,24 @@ - - - - - + + + + + - + + + + + + - diff --git a/Engine/SharpEngine.Core/Textures/TextureService.cs b/SharpEngine.Core/Textures/TextureService.cs similarity index 88% rename from Engine/SharpEngine.Core/Textures/TextureService.cs rename to SharpEngine.Core/Textures/TextureService.cs index 4c40568..2f25e90 100644 --- a/Engine/SharpEngine.Core/Textures/TextureService.cs +++ b/SharpEngine.Core/Textures/TextureService.cs @@ -1,9 +1,9 @@ using System.IO; using System.Collections.Generic; +using SharpEngine.Core.Components.Properties.Textures; using SharpEngine.Core.Windowing; using Texture = SharpEngine.Core.Components.Properties.Textures.Texture; -using TextureType = Silk.NET.Assimp.TextureType; namespace SharpEngine.Core.Textures; @@ -25,6 +25,7 @@ private TextureService() { } /// Loads a texture from the specified path. /// /// the full path to the texture. + /// The type of the texture. /// The loaded texture program. public Texture LoadTexture(string path, TextureType textureType = TextureType.Diffuse) { @@ -39,7 +40,7 @@ public Texture LoadTexture(string path, TextureType textureType = TextureType.Di return cachedTexture; // Generate handle - var texture = new Texture(Window.GL, path, textureType); + var texture = new Texture(Window.SharedGL, path, textureType); // Add it to the cache _textureCache[path] = texture; diff --git a/Engine/SharpEngine.Core/Windowing/Frame.cs b/SharpEngine.Core/Windowing/Frame.cs similarity index 100% rename from Engine/SharpEngine.Core/Windowing/Frame.cs rename to SharpEngine.Core/Windowing/Frame.cs diff --git a/SharpEngine.Core/Windowing/IWindowFactory.cs b/SharpEngine.Core/Windowing/IWindowFactory.cs new file mode 100644 index 0000000..881042c --- /dev/null +++ b/SharpEngine.Core/Windowing/IWindowFactory.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace SharpEngine.Core.Windowing; + +/// +/// Creates configured window instances from DI registrations. +/// +public interface IWindowFactory +{ + /// + /// Gets the registered window names. + /// + IReadOnlyList RegisteredWindows { get; } + + /// + /// Creates a configured window instance. + /// + /// The optional window registration name. + /// A configured . + Window CreateWindow(string? name = null); + + /// + /// Creates one window for each registration. + /// + /// The configured windows. + IReadOnlyList CreateAllWindows(); +} diff --git a/Engine/SharpEngine.Core/Windowing/SilkWindow.cs b/SharpEngine.Core/Windowing/SilkWindow.cs similarity index 95% rename from Engine/SharpEngine.Core/Windowing/SilkWindow.cs rename to SharpEngine.Core/Windowing/SilkWindow.cs index 4301fa0..1f5e1dd 100644 --- a/Engine/SharpEngine.Core/Windowing/SilkWindow.cs +++ b/SharpEngine.Core/Windowing/SilkWindow.cs @@ -1,3 +1,5 @@ +using SharpEngine.Core.Entities.Views.Settings; + using Silk.NET.Core; using Silk.NET.Core.Contexts; using Silk.NET.Input; @@ -13,6 +15,9 @@ namespace SharpEngine.Core.Windowing; +// TODO: The warnings produced by enabling this need to be implemented. +#pragma warning disable CS0067 // Event is required by IWindow but not used by this implementation + /// /// Represents an abstraction for the interface. /// @@ -38,6 +43,11 @@ public bool IsClosing set => CurrentWindow.IsClosing = value; } + /// + /// Gets the settings for the current window. + /// + public IViewSettings Settings { get; protected set; } = ViewSettings.Default; + /// Gets or sets the input context for the window. public IInputContext? Input { get; protected set; } @@ -159,6 +169,7 @@ public bool VSync set => CurrentWindow.ShouldSwapAutomatically = value; } + /// Gets a value indicating whether the window is focused. public bool IsFocused { get; } /// diff --git a/Engine/SharpEngine.Core/Windowing/Window.cs b/SharpEngine.Core/Windowing/Window.cs similarity index 57% rename from Engine/SharpEngine.Core/Windowing/Window.cs rename to SharpEngine.Core/Windowing/Window.cs index c566254..903f3df 100644 --- a/Engine/SharpEngine.Core/Windowing/Window.cs +++ b/SharpEngine.Core/Windowing/Window.cs @@ -1,4 +1,3 @@ -using SharpEngine.Core.Entities.Properties.Meshes; using SharpEngine.Core.Entities.Views; using SharpEngine.Core.Entities.Views.Settings; using SharpEngine.Core.Enums; @@ -6,6 +5,12 @@ using SharpEngine.Core.Renderers; using SharpEngine.Core.Scenes; using SharpEngine.Core.Shaders; +using SharpEngine.Core.Interfaces; +using Shader = SharpEngine.Core.Shaders.Shader; + +using SharpEngine.Shared.Dto; + +using Microsoft.Extensions.Logging; using Silk.NET.Input; using Silk.NET.Maths; @@ -18,10 +23,6 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; -using System.Threading.Tasks; -using Shader = SharpEngine.Core.Shaders.Shader; -using Silk.NET.GLFW; -using SharpEngine.Shared; namespace SharpEngine.Core.Windowing; @@ -30,8 +31,11 @@ namespace SharpEngine.Core.Windowing; /// public class Window : SilkWindow { + private const string _iconPath = "_Resources/icon.png"; private bool _windowInitialized; private bool _initialized; + private readonly RendererBase[] _registeredRenderers; + private readonly ILogger _logger; private IEnumerable _renderers = []; private ImGuiController? _imGuiController; @@ -41,11 +45,6 @@ public class Window : SilkWindow /// 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; @@ -67,29 +66,84 @@ public class Window : SilkWindow protected Scene Scene { get; private set; } /// The OpenGL context. - public static GL GL; + private GL _gl = null!; + + private static GL? _sharedGl; + + /// + /// Gets the shared OpenGL instance used for resource creation when windows share an OpenGL context. + /// + /// + /// This is primarily for backward compatibility with code that assumed a single global GL instance. + /// For multi-window support, prefer using and ensuring the correct context is current. + /// + public static GL SharedGL + => _sharedGl ?? throw new InvalidOperationException("No OpenGL context has been created yet."); + + /// + /// Backward compatible alias for . + /// + public static GL GL => SharedGL; - // 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; - + public GL GetGL() => _gl; + private void SetGL(GL gl) => _gl = gl; + + /// + /// Initializes a new instance of . + /// + /// The camera the window should render from. + /// Contains the game scene. + /// The settings for the window. + public Window(CameraView camera, Scene scene, IViewSettings settings) : + this(camera, scene, settings, LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger()) { } + /// /// Initializes a new instance of . /// /// The camera the window should render from. /// Contains the game scene. /// The settings for the window. - public Window(CameraView camera, Scene scene, IViewSettings settings) + /// The logger for the window. + /// The renderers for the window. + public Window(CameraView camera, Scene scene, IViewSettings settings, ILogger logger, IEnumerable? renderers = null) { + // TODO: + // Should the developer need to call for a window initialization? + // Meaning should should we move this project loading part to a separate function? + Scene = scene; Settings = settings; Camera = camera; + _registeredRenderers = renderers?.ToArray() ?? []; + _logger = logger; + + CheckEngineVersion(); + + // NOTE: Window initialization is intentionally not performed automatically + // here. Call InitializeWindow() explicitly when ready to create the underlying + // native window and load resources. See the TODO in the project for reasoning. } + private void CheckEngineVersion() + { + var project = new Project(); + var currentAssemlyVersion = typeof(Window).Assembly.GetVersion(); + + if (currentAssemlyVersion != project.EngineVersion) + _logger.LogWarning("The current engine version ({CurrentVersion}) does not match the project engine version ({ProjectVersion}). This may lead to unexpected behavior.", currentAssemlyVersion, project.EngineVersion); + } + + /// + /// Initializes a new instance of . + /// + /// The game instance. + public Window(Game game) + : this(game.Scene, game.Camera.Settings, LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger()) { } + /// /// Initializes a new instance of . /// @@ -98,17 +152,22 @@ public Window(CameraView camera, Scene scene, IViewSettings settings) /// /// Contains the game scene. /// The settings for the window. - public Window(Scene scene, IViewSettings settings) + /// The logger for the window. + /// The renderers for the window. + public Window(Scene scene, IViewSettings settings, ILogger logger, IEnumerable? renderers = null) { Scene = scene; Settings = settings; Camera = new(Vector3.One, settings); + _registeredRenderers = renderers?.ToArray() ?? []; + _logger = logger; + + // Initialization is deferred. Call Initialize() when you want the + // native window to be created and resources to be loaded. } - /// - /// Initializes the ga - /// - public void InitializeWindow() + /// + public override void Initialize() { if (_initialized) return; @@ -136,7 +195,7 @@ public override void Run(Action onFrame) } catch (Exception ex) { - Debug.Log.Error(ex, "Error running window: {Message}", ex.Message); + _logger.LogError(ex, "Error running window: {Message}", ex.Message); } } @@ -148,43 +207,36 @@ public override void OnLoad() var context = CurrentWindow.CreateOpenGL(); SetGL(context); + // Capture the first created GL as the shared GL. This enables resource caches to work across windows + // when those windows are created with a shared OpenGL context. + _sharedGl ??= context; + Input = CurrentWindow.CreateInput(); + + // TODO: Skip calling this for secondary windows? CurrentWindow.MakeCurrent(); - SetWindowIcon(PathExtensions.GetAssemblyPath("_Resources/icon.png")); + SetWindowIcon(PathExtensions.GetAssemblyPath(_iconPath)); AssignInputEvents(); - GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); + _gl.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); - // Load all meshes from the mesh cache - // MeshService.Instance.LoadMesh("cube", Primitives.Cube.Mesh); - - // Using reflection, find all renderers that implement the RendererBase. - var rendererTypes = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsSubclassOf(typeof(RendererBase)) && !type.IsAbstract); - - foreach (var type in rendererTypes) - { - // Make sure the renderer has the correct constructor parameters! - // 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); - } + _renderers = CreateRenderers(); foreach (var renderer in _renderers) + { + renderer.AttachWindow(this); renderer.Initialize(); + } - _imGuiController = new ImGuiController(GL, CurrentWindow, Input); + _imGuiController = new ImGuiController(_gl, CurrentWindow, Input); _initialized = true; } catch (Exception ex) { - Debug.Log.Information(ex, "Error loading window: {Message}", ex.Message); + _logger.LogInformation(ex, "Error loading window: {Message}", ex.Message); } base.OnLoad(); @@ -207,17 +259,18 @@ protected void RenderFrame(Frame frame) _imGuiController?.Update((float)frame.FrameTime); - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + _gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); ToggleWireFrame(Settings.UseWireFrame); UseShaders(); - var renderTasks = _renderers.Where(renderer => Settings.RendererFlags.HasFlag(renderer.RenderFlag)) - .Select(renderer => renderer.Render()) - .ToList(); + var activeRenderers = _renderers.Where(renderer => Settings.RendererFlags.HasFlag(renderer.RenderFlag)) + .OrderBy(renderer => renderer.RenderFlag) + .ToList(); - Task.WaitAll([.. renderTasks]); + foreach (var renderer in activeRenderers) + renderer.Render().GetAwaiter().GetResult(); AfterRender(frame); @@ -225,16 +278,35 @@ protected void RenderFrame(Frame frame) } catch (Exception ex) { - Debug.Log.Information(ex.Message); + _logger.LogInformation("{Message}", ex.Message); } } + private RendererBase[] CreateRenderers() + { + // Return the renderers that have been registered + if (_registeredRenderers.Length > 0) + return _registeredRenderers; + + // When no renderers have been registered, fall back to use all that are discoverable. + var rendererTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsSubclassOf(typeof(RendererBase)) && !type.IsAbstract); + + return [.. rendererTypes + .Select(type => + { + var requiredArguments = new object[] { Camera, Settings, Scene }; + return (RendererBase)Activator.CreateInstance(type, requiredArguments)!; + })]; + } + /// /// Toggles the renderer between wireframe and fill mode. /// /// Determines whether objects should be rendered in wireframe. - private static void ToggleWireFrame(bool useWireFrame) - => GL.PolygonMode(GLEnum.FrontAndBack, useWireFrame ? PolygonMode.Line : PolygonMode.Fill); + private void ToggleWireFrame(bool useWireFrame) + => _gl.PolygonMode(GLEnum.FrontAndBack, useWireFrame ? PolygonMode.Line : PolygonMode.Fill); private List _shaders = []; @@ -255,7 +327,7 @@ protected void OnUpdateFrame(Frame frame) } if (Settings.PrintFrameRate) - Debug.Log.Information($"FPS: {frame.FrameRate}"); + _logger.LogInformation("FPS: {FrameRate}", frame.FrameRate); // TODO: #21 Handle multiple mice? var mouse = Input?.Mice[0]; @@ -283,7 +355,7 @@ private void AssignInputEvents() { if (Input is null) { - Debug.Log.Information("Input is null. No input events will be assigned."); + _logger.LogInformation("Input is null. No input events will be assigned."); return; } @@ -325,7 +397,7 @@ protected void OnMouseWheel(IMouse mouse, ScrollWheel sw) /// protected void OnResize(Vector2D size) { - GL.Viewport(size); + _gl.Viewport(size); if (size != Vector2D.Zero) Camera.AspectRatio = (float)size.X / size.Y; diff --git a/SharpEngine.Core/Windowing/WindowFactory.cs b/SharpEngine.Core/Windowing/WindowFactory.cs new file mode 100644 index 0000000..f2774ea --- /dev/null +++ b/SharpEngine.Core/Windowing/WindowFactory.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SharpEngine.Core.Windowing; + +/// +/// Creates window instances from a collection of entries. +/// +/// The service provider used to resolve window dependencies. +/// The registrations that describe available windows. +internal sealed class WindowFactory(IServiceProvider serviceProvider, IEnumerable registrations) : IWindowFactory +{ + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly IReadOnlyList _registrations = registrations.ToArray(); + + /// + /// Gets the names of all registered windows. + /// + public IReadOnlyList RegisteredWindows => [.. _registrations.Select(registration => registration.Name)]; + + /// + /// Creates a window by its registration name. + /// + /// + /// If is null, the default registration (or the first registration if none are marked default) is used. + /// + /// Optional name of the window registration to create. + /// The created instance. + /// Thrown when no registrations exist or when a named registration cannot be found. + public Window CreateWindow(string? name = null) + { + var registration = ResolveRegistration(name); + var window = registration.Factory(_serviceProvider); + registration.Configure?.Invoke(_serviceProvider, window); + return window; + } + + /// + /// Creates instances for all registered windows in the order they were registered. + /// + /// A read-only list containing all created window instances. + public IReadOnlyList CreateAllWindows() + => [.. _registrations.Select(registration => CreateWindow(registration.Name))]; + + /// + /// Resolves the appropriate registration for the given name. + /// + /// The optional registration name to resolve. + /// + /// The matching or returns the default registration when is null. + /// + /// Thrown when no registrations exist or when a named registration cannot be found. + private WindowRegistration ResolveRegistration(string? name) + { + if (_registrations.Count == 0) + throw new InvalidOperationException("No windows have been registered. Call AddWindow during service configuration."); + + if (name is not null) + { + var namedRegistration = _registrations.FirstOrDefault(registration => string.Equals(registration.Name, name, StringComparison.OrdinalIgnoreCase)); + return namedRegistration ?? throw new InvalidOperationException($"No window named '{name}' has been registered."); + } + + return _registrations.FirstOrDefault(registration => registration.IsDefault) ?? _registrations[0]; + } +} diff --git a/SharpEngine.Core/Windowing/WindowRegistration.cs b/SharpEngine.Core/Windowing/WindowRegistration.cs new file mode 100644 index 0000000..f6c64c4 --- /dev/null +++ b/SharpEngine.Core/Windowing/WindowRegistration.cs @@ -0,0 +1,22 @@ +using System; + +namespace SharpEngine.Core.Windowing; + +/// +/// Holds registration information for a window type. +/// +internal sealed class WindowRegistration +{ + /// Gets or initializes the name for the window. + public required string Name { get; init; } + + /// Gets or initializes the factory delegate used to create the window instance. + /// The service provider is supplied to allow resolving dependencies required by the window. + public required Func Factory { get; init; } + + /// Gets or initializes the optional configuration action invoked immediately after the window is created. + public Action? Configure { get; init; } + + /// Gets or initializes a value indicating whether this registration should be used as the default when no specific window name is requested. + public bool IsDefault { get; init; } +} diff --git a/Engine/SharpEngine.Core/_Resources/Default.cs b/SharpEngine.Core/_Resources/Default.cs similarity index 77% rename from Engine/SharpEngine.Core/_Resources/Default.cs rename to SharpEngine.Core/_Resources/Default.cs index ef3e138..88fc100 100644 --- a/Engine/SharpEngine.Core/_Resources/Default.cs +++ b/SharpEngine.Core/_Resources/Default.cs @@ -16,8 +16,12 @@ public static class Default /// Gets the path to the lighting fragment shader. public static string FragmentShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\lighting.frag"); + /// Gets the path to the default shader used for rendering light sources. public static string LightShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\shader.frag"); - + + /// Gets the path to the default vertex shader used for rendering UI elements. public static string UIVertexShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\uiShader.vert"); + + /// Gets the path to the default fragment shader used for rendering UI elements. public static string UIFragmentShader => PathExtensions.GetAssemblyPath("_Resources\\Shaders\\uiShader.frag"); } diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl b/SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl rename to SharpEngine.Core/_Resources/Shaders/Light/DirectionalLight.glsl diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl b/SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl rename to SharpEngine.Core/_Resources/Shaders/Light/PointLight.glsl diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl b/SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl rename to SharpEngine.Core/_Resources/Shaders/Light/SpotLight.glsl diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/Material.glsl b/SharpEngine.Core/_Resources/Shaders/Material.glsl similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/Material.glsl rename to SharpEngine.Core/_Resources/Shaders/Material.glsl diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/lighting.frag b/SharpEngine.Core/_Resources/Shaders/lighting.frag similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/lighting.frag rename to SharpEngine.Core/_Resources/Shaders/lighting.frag diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/shader.frag b/SharpEngine.Core/_Resources/Shaders/shader.frag similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/shader.frag rename to SharpEngine.Core/_Resources/Shaders/shader.frag diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/shader.vert b/SharpEngine.Core/_Resources/Shaders/shader.vert similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/shader.vert rename to SharpEngine.Core/_Resources/Shaders/shader.vert diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/shader2.vert b/SharpEngine.Core/_Resources/Shaders/shader2.vert similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/shader2.vert rename to SharpEngine.Core/_Resources/Shaders/shader2.vert diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.frag b/SharpEngine.Core/_Resources/Shaders/uiShader.frag similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/uiShader.frag rename to SharpEngine.Core/_Resources/Shaders/uiShader.frag diff --git a/Engine/SharpEngine.Core/_Resources/Shaders/uiShader.vert b/SharpEngine.Core/_Resources/Shaders/uiShader.vert similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Shaders/uiShader.vert rename to SharpEngine.Core/_Resources/Shaders/uiShader.vert diff --git a/Engine/SharpEngine.Core/_Resources/Textures/debug.jpg b/SharpEngine.Core/_Resources/Textures/debug.jpg similarity index 100% rename from Engine/SharpEngine.Core/_Resources/Textures/debug.jpg rename to SharpEngine.Core/_Resources/Textures/debug.jpg diff --git a/Engine/SharpEngine.Core/_Resources/icon.png b/SharpEngine.Core/_Resources/icon.png similarity index 100% rename from Engine/SharpEngine.Core/_Resources/icon.png rename to SharpEngine.Core/_Resources/icon.png diff --git a/SharpEngine.Shared/Debug.cs b/SharpEngine.Shared/Debug.cs new file mode 100644 index 0000000..43c2f7a --- /dev/null +++ b/SharpEngine.Shared/Debug.cs @@ -0,0 +1,79 @@ +using Serilog; +using Serilog.Events; +using System; +using System.Numerics; + +namespace SharpEngine.Shared; + +/// +/// Contains methods for debugging the application. +/// +public static class Debug +{ + /// + /// A logger instance used for debugging the application. + /// + public static ILogger Log { get; set; } + + private static LogEventLevel _logLevel = LogEventLevel.Information; + + /// + /// Initializes the class instance. + /// + static Debug() + { + SetLogger(); + } + + /// + /// Updates the logger instance. + /// + public static void SetLogger() + => Log = new LoggerConfiguration() + .MinimumLevel.Is(_logLevel) + .WriteTo.Console() + .CreateLogger(); + + /// + /// Sets the logging level for the application. + /// + /// + /// This affects the granularity of log messages generated. + /// + /// Specifies the severity level of log messages to be recorded. + public static void SetLogLevel(LogEventLevel logLevel) + { + _logLevel = logLevel; + SetLogger(); + } + +#if DEBUG + + /// + /// Draws a line on the screen. + /// + /// The starting point for the line. + /// The direction where the vector needs to be drawn. + /// Determines how long the line needs to be. + /// Determines how wide of a vector needs to be drawn. + /// Thrown when the method is called, indicating that the implementation is not yet provided. + public static void DrawLine(Vector3 startPoint, Vector3 direction, float length, float width = 1) + { + // https://math.stackexchange.com/questions/1286489/how-to-find-direction-and-normal-vector + throw new NotImplementedException(); + } + + /// + /// Draws a box in 3D space defined by two corner points. + /// + /// Specifies one corner of the box in 3D coordinates. + /// Specifies the opposite corner of the box in 3D coordinates. + /// Determines whether the box is rendered as a solid shape or as a wireframe. + /// Thrown when the method has not been implemented yet. + public static void DrawBox(Vector3 min, Vector3 max, bool wireframe = true) + { + throw new NotImplementedException(); + } + +#endif +} \ No newline at end of file diff --git a/Engine/SharpEngine.slnx b/SharpEngine.slnx similarity index 74% rename from Engine/SharpEngine.slnx rename to SharpEngine.slnx index 32cc136..4cfa5e7 100644 --- a/Engine/SharpEngine.slnx +++ b/SharpEngine.slnx @@ -1,9 +1,15 @@ + + + + + + @@ -18,14 +24,17 @@ + + + - - + + @@ -38,12 +47,17 @@ - - + + + + + + + + - - - + + @@ -55,10 +69,5 @@ - - - - - - + diff --git a/Tests/Directory.Build.props b/Tests/Directory.Build.props new file mode 100644 index 0000000..3ad3602 --- /dev/null +++ b/Tests/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + + False + + + + \ No newline at end of file diff --git a/Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs b/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs similarity index 85% rename from Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs rename to Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs index 39c13c4..be3028c 100644 --- a/Engine/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs +++ b/Tests/ObjLoader.Test/Loaders/MaterialLibraryLoaderTests.cs @@ -1,32 +1,30 @@ using System.Collections.Generic; -using System.IO; -using System.Text; -using NUnit.Framework; -using ObjLoader.Loader.Data; using System.Linq; + +using NUnit.Framework; using FluentAssertions; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; -using SharpEngine.Core.Entities.Properties; +using SharpEngine.Core.Components.ObjLoader.DataStore; using SharpEngine.Core.Components.Properties; -using ObjLoader.Loaders.MaterialLoader; +using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader; +using System.Diagnostics.CodeAnalysis; -namespace ObjLoader.Test.Loaders +namespace SharpEngine.Core.ObjLoader.Tests.Loaders { [TestFixture] public class MaterialLibraryLoaderTests { - private MaterialLibrarySpy _materialLibrarySpy; + private readonly MaterialLibrarySpy _materialLibrarySpy; + private readonly MaterialLibraryLoader _materialLibraryLoader; + private Material _firstMaterial; private Material _secondMaterial; private const float Epsilon = 0.000001f; - private MaterialLibraryLoader _materialLibraryLoader; - [SetUp] - public void SetUp() + public MaterialLibraryLoaderTests() { _materialLibrarySpy = new MaterialLibrarySpy(); - // _materialLibraryLoader = new MaterialLibraryLoader(_materialLibrarySpy); + _materialLibraryLoader = new MaterialLibraryLoader(null!); } [Test] @@ -102,12 +100,10 @@ public void Sets_correct_texure_maps() _firstMaterial.StencilDecalMap.Should().BeEquivalentTo("lenna_stencil.tga"); } + [MemberNotNull(nameof(_firstMaterial), nameof(_secondMaterial))] private void LoadMaterial() { - var data = Encoding.ASCII.GetBytes(MaterialLibrary); - var materialStream = new MemoryStream(data); - - _materialLibraryLoader.Load(materialStream); + _materialLibraryLoader.ParseFile(MaterialLibrary); _firstMaterial = _materialLibrarySpy.Materials.First(); _secondMaterial = _materialLibrarySpy.Materials.ElementAt(1); } diff --git a/Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs b/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs similarity index 82% rename from Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs rename to Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs index 8e4190a..56e9765 100644 --- a/Engine/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs +++ b/Tests/ObjLoader.Test/Loaders/ObjLoaderTests.cs @@ -2,13 +2,15 @@ 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; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; +using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader; + +using CoreObjLoader = SharpEngine.Core.ObjLoader.Loaders.ObjLoader.ObjLoader; -namespace ObjLoader.Test.Loaders +namespace SharpEngine.Core.ObjLoader.Tests.Loaders { [TestFixture] public class ObjLoaderTests @@ -25,7 +27,7 @@ public Stream Open(string materialFilePath) } } - private ObjLoader.Loaders.ObjLoader.ObjLoader _loader; + private CoreObjLoader _loader; private Mesh _loadResult; private DataStore _textureDataStore; @@ -53,12 +55,12 @@ public void SetUp() _materialStreamProviderSpy = new MaterialStreamProviderSpy(); _materialStreamProviderSpy.StreamToReturn = CreateMemoryStreamFromString(MaterialLibraryString); - _materialLibraryLoader = new MaterialLibraryLoader("", _textureDataStore); - _materialLibraryLoaderFacade = new MaterialLibraryLoaderFacade(_materialLibraryLoader); - // _materialLibraryParser = new MaterialLibraryParser(_materialLibraryLoaderFacade); + _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); + _loader = new CoreObjLoader("", _textureDataStore); } [Test] @@ -77,17 +79,17 @@ public void Loads_object_and_material_correctly() var group = _loadResult.Groups.First(); group.Faces.Should().HaveCount(12); - group.Material.Name.Should().BeEquivalentTo("cube_material"); + 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); + _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); + _loader = new CoreObjLoader("", _textureDataStore); Load(); @@ -107,7 +109,7 @@ private void Load() { var objectStream = CreateMemoryStreamFromString(ObjectFileString); - // _loadResult = _loader.Load(objectStream); + _loadResult = _loader.Load(null!).First(); } private Stream CreateMemoryStreamFromString(string str) diff --git a/Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj b/Tests/ObjLoader.Test/SharpEngine.Core.ObjLoader.Tests.csproj similarity index 82% rename from Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj rename to Tests/ObjLoader.Test/SharpEngine.Core.ObjLoader.Tests.csproj index 7725988..6a619d7 100644 --- a/Engine/Tests/ObjLoader.Test/ObjLoader.Tests.csproj +++ b/Tests/ObjLoader.Test/SharpEngine.Core.ObjLoader.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + $(DotNetTargetFramework) @@ -11,6 +11,10 @@ + + + CS8618 + diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs similarity index 94% rename from Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs index 64ba447..df5b03a 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/FaceParserTests.cs @@ -1,11 +1,11 @@ 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 +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class FaceParserTests diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs similarity index 76% rename from Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs index 97ea846..061dd2d 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/GroupParserTests.cs @@ -1,23 +1,21 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.Data; -using ObjLoader.Loader.TypeParsers; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; -namespace ObjLoader.Test.TypeParsers +using SharpEngine.Core.Components.ObjLoader.DataStore; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; + +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class GroupParserTests { - private GroupDataStoreMock _groupDataStoreMock; - private GroupParser _groupParser; + private readonly GroupDataStoreMock _groupDataStoreMock; + private readonly GroupParser _groupParser; - [SetUp] - public void SetUp() + public GroupParserTests() { _groupDataStoreMock = new GroupDataStoreMock(); - - // _groupParser = new GroupParser(_groupDataStoreMock); + _groupParser = new GroupParser(_groupDataStoreMock); } [Test] diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs similarity index 75% rename from Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs index 91089f1..3c974a6 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/MaterialLibraryParserTests.cs @@ -1,21 +1,20 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.TypeParsers; -using ObjLoader.Loaders.MaterialLoader; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; +using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader; -namespace ObjLoader.Test.TypeParsers +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class MaterialLibraryParserTests { - private MaterialLibraryLoaderFacadeSpy _materialLibraryLoaderFacadeSpy; - private MaterialLibraryParser _parser; + private readonly MaterialLibraryLoaderFacadeSpy _materialLibraryLoaderFacadeSpy; + private readonly MaterialLibraryParser _parser; - [SetUp] - public void SetUp() + public MaterialLibraryParserTests() { _materialLibraryLoaderFacadeSpy = new MaterialLibraryLoaderFacadeSpy(); - // _parser = new MaterialLibraryParser(_materialLibraryLoaderFacadeSpy); + _parser = new MaterialLibraryParser(_materialLibraryLoaderFacadeSpy); } [Test] diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs similarity index 80% rename from Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs index f9e03c7..f047eb5 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/NormalParserTests.cs @@ -1,23 +1,22 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.TypeParsers; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; + +using SharpEngine.Core.Components.ObjLoader.DataStore; using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; -namespace ObjLoader.Test.TypeParsers +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class NormalParserTests { - private NormalParser _normalParser; - private NormalDataStoreMock _normalDataStoreMock; + private readonly NormalParser _normalParser; + private readonly NormalDataStoreMock _normalDataStoreMock; - [SetUp] - public void SetUp() + public NormalParserTests() { _normalDataStoreMock = new NormalDataStoreMock(); - - // _normalParser = new NormalParser(_normalDataStoreMock); + _normalParser = new NormalParser(_normalDataStoreMock); } [Test] diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs similarity index 80% rename from Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs index 539308b..4b9ffd9 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/TextureParserTests.cs @@ -1,23 +1,23 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.TypeParsers; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; + +using SharpEngine.Core.Components.ObjLoader.DataStore; using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; -namespace ObjLoader.Test.TypeParsers +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class TextureParserTests { - private TextureDataStoreMock _textureDataStoreMock; - private TextureParser _textureParser; + private readonly TextureDataStoreMock _textureDataStoreMock; + private readonly TextureParser _textureParser; - [SetUp] - public void SetUp() + public TextureParserTests() { _textureDataStoreMock = new TextureDataStoreMock(); - // _textureParser = new TextureParser(_textureDataStoreMock); + _textureParser = new TextureParser(_textureDataStoreMock); } [Test] diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs similarity index 75% rename from Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs index 863fac8..29cbaa8 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/UseMaterialParserTests.cs @@ -1,20 +1,19 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.TypeParsers; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; -namespace ObjLoader.Test.TypeParsers +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class UseMaterialParserTests { - private ElementGroupSpy _elementGroupSpy; - private UseMaterialParser _parser; + private readonly ElementGroupSpy _elementGroupSpy; + private readonly UseMaterialParser _parser; - [SetUp] - public void SetUp() + public UseMaterialParserTests() { _elementGroupSpy = new ElementGroupSpy(); - // _parser = new UseMaterialParser(_elementGroupSpy); + _parser = new UseMaterialParser(_elementGroupSpy); } [Test] @@ -44,7 +43,7 @@ public void Parses_usemtl_line_correctly_1() _elementGroupSpy.MaterialName.Should().BeEquivalentTo("materialName"); } - private class ElementGroupSpy // : IElementGroup + private class ElementGroupSpy : IMaterialDataStore { public string MaterialName { get; private set; } diff --git a/Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs b/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs similarity index 80% rename from Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs rename to Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs index ba27316..8009a66 100644 --- a/Engine/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs +++ b/Tests/ObjLoader.Test/TypeParsers/VertexParserTests.cs @@ -1,23 +1,22 @@ using FluentAssertions; using NUnit.Framework; -using ObjLoader.Loader.TypeParsers; -using SharpEngine.Core.Components.Obsolete.ObjLoader.DataStore; + +using SharpEngine.Core.Components.ObjLoader.DataStore; using SharpEngine.Core.Components.Properties.Meshes.MeshData; +using SharpEngine.Core.ObjLoader.Loader.TypeParsers; -namespace ObjLoader.Test.TypeParsers +namespace SharpEngine.Core.ObjLoader.Tests.TypeParsers { [TestFixture] public class VertexParserTests { - private VertexDataStoreMock _vertexDataStoreMock; - private VertexParser _vertexParser; + private readonly VertexDataStoreMock _vertexDataStoreMock; + private readonly VertexParser _vertexParser; - [SetUp] - public void SetUp() + public VertexParserTests() { _vertexDataStoreMock = new VertexDataStoreMock(); - - // _vertexParser = new VertexParser(_vertexDataStoreMock); + _vertexParser = new VertexParser(_vertexDataStoreMock); } [Test] @@ -64,6 +63,5 @@ public void AddVertex(Vertex vertex) ParsedVertex = vertex; } } - } } \ No newline at end of file diff --git a/Engine/Tests/SharpEngine.Core.Tests/Class1.cs b/Tests/SharpEngine.Core.Tests/Class1.cs similarity index 100% rename from Engine/Tests/SharpEngine.Core.Tests/Class1.cs rename to Tests/SharpEngine.Core.Tests/Class1.cs diff --git a/Engine/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj b/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj similarity index 71% rename from Engine/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj rename to Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj index fa71b7a..bbcf245 100644 --- a/Engine/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj +++ b/Tests/SharpEngine.Core.Tests/SharpEngine.Core.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + $(DotNetTargetFramework) enable enable diff --git a/Engine/Tests/SharpEngine.Editor.Tests/Class1.cs b/Tests/SharpEngine.Editor.Tests/Class1.cs similarity index 100% rename from Engine/Tests/SharpEngine.Editor.Tests/Class1.cs rename to Tests/SharpEngine.Editor.Tests/Class1.cs diff --git a/Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj b/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj similarity index 71% rename from Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj rename to Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj index fa71b7a..bbcf245 100644 --- a/Engine/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj +++ b/Tests/SharpEngine.Editor.Tests/SharpEngine.Editor.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + $(DotNetTargetFramework) enable enable diff --git a/Engine/Tests/SharpEngine.Shared.Tests/Class1.cs b/Tests/SharpEngine.Shared.Tests/Class1.cs similarity index 100% rename from Engine/Tests/SharpEngine.Shared.Tests/Class1.cs rename to Tests/SharpEngine.Shared.Tests/Class1.cs diff --git a/Engine/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj b/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj similarity index 100% rename from Engine/Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj rename to Tests/SharpEngine.Shared.Tests/SharpEngine.Shared.Tests.csproj