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/Loaders/ObjLoader/ObjLoader.cs b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
deleted file mode 100644
index 798bc3c..0000000
--- a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using ObjLoader.Loader.Loaders;
-using ObjLoader.TypeParsers;
-using SharpEngine.Core.Entities.Properties.Meshes;
-using Silk.NET.Core.Contexts;
-using Silk.NET.OpenGL;
-using System.Collections.Generic;
-using System.IO;
-using Tutorial;
-
-namespace ObjLoader.Loaders.ObjLoader
-{
- public class ObjLoader : LoaderBase
- {
- private readonly string _path;
- private readonly DataStore _dataStore;
- private readonly List _typeParsers = [];
- private readonly List _unrecognizedLines = [];
-
- public ObjLoader(string path, DataStore dataStore)
- {
- _path = path;
- _dataStore = dataStore;
- }
-
- public ObjLoader SetupTypeParsers(params ITypeParser[] parsers)
- {
- foreach (var parser in parsers)
- _typeParsers.Add(parser);
-
- return this;
- }
-
- ///
- protected override void ParseLine(string keyword, string data)
- {
- foreach (var typeParser in _typeParsers)
- if (typeParser.CanParse(keyword))
- {
- typeParser.Parse(data);
- return;
- }
-
- _unrecognizedLines.Add(keyword + " " + data);
- }
-
- public List Load(GL gl)
- {
- var fileStream = new FileStream(_path, FileMode.Open, FileAccess.Read);
- StartLoad(fileStream);
-
- return
- [
- new(gl)
- {
- Vertices2 = _dataStore.Vertices,
- TextureCoordinates2 = _dataStore.Textures,
- Normals2 = _dataStore.Normals,
- Groups = _dataStore.Groups,
- Materials = _dataStore.Materials
- }
- ];
- }
- }
-}
\ No newline at end of file
diff --git a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs b/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
deleted file mode 100644
index 408855c..0000000
--- a/Engine/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using ObjLoader.Loader.TypeParsers;
-using ObjLoader.Loaders.MaterialLoader;
-
-using SharpEngine.Core.Entities.Properties.Meshes;
-using Silk.NET.OpenGL;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Tutorial;
-
-namespace ObjLoader.Loaders.ObjLoader
-{
- ///
- /// Handles loading 3D models.
- ///
- public static class ObjLoaderFactory
- {
- const string FbxExtension = ".fbx";
- const string ObjExtension = ".obj";
-
- ///
- /// Loads a mesh based on the file extension of the provided path.
- ///
- /// The OpenGL context where the model should be bound.
- /// Specifies the file path of the mesh to be loaded, which determines the loading method based on its extension.
- /// The model loaded from the file.
- /// Thrown when the file extension of the provided path is not recognized as a supported mesh format.
- public static Model_Old Load(GL gl, string path)
- => Path.GetExtension(path) switch
- {
- FbxExtension => LoadFbx("", path),
- ObjExtension => LoadObj(gl, path),
- _ => throw new NotSupportedException($"The file extension {Path.GetExtension(path)} is not a supported mesh file.")
- };
-
- // TODO: #3 Load fbx mesh from file
- private static Model_Old LoadFbx(string identifier, string meshFilePath)
- {
- throw new NotImplementedException();
- }
-
- private static Model_Old LoadObj(GL gl, string path)
- {
- return new Model_Old(gl, path);
-
- // TOOD: Use the ObjLoader to load the model instead of the external library.
-
- /*var dataStore = new DataStore();
-
- var faceParser = new FaceParser(dataStore);
- var groupParser = new GroupParser(dataStore);
- var normalParser = new NormalParser(dataStore);
- var textureParser = new TextureParser(dataStore);
- var vertexParser = new VertexParser(dataStore);
-
- var materialLibraryLoader = new MaterialLibraryLoader(path, dataStore);
- var materialLibraryParser = new MaterialLibraryParser(materialLibraryLoader);
- var useMaterialParser = new UseMaterialParser(dataStore);
-
- var loader = new ObjLoader(path, dataStore).SetupTypeParsers(faceParser, groupParser, normalParser, textureParser, vertexParser, materialLibraryParser, useMaterialParser);
- return loader.Load(gl);
- */
- }
- }
-}
\ No newline at end of file
diff --git a/Engine/ObjLoader/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/ObjLoader/TypeParsers/VertexParser.cs b/Engine/ObjLoader/TypeParsers/VertexParser.cs
deleted file mode 100644
index d6b4fcf..0000000
--- a/Engine/ObjLoader/TypeParsers/VertexParser.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using ObjLoader.Loader.Common;
-using ObjLoader.TypeParsers;
-using SharpEngine.Core.Components.Properties.Meshes.MeshData;
-using System;
-using Tutorial;
-
-namespace ObjLoader.Loader.TypeParsers
-{
- public class VertexParser : TypeParserBase, ITypeParser
- {
- private readonly DataStore _dataStore;
-
- public VertexParser(DataStore dataStore)
- {
- _dataStore = dataStore;
- }
-
- ///
- protected override string Keyword => "v";
-
- ///
- public override void Parse(string line)
- {
- string[] parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
-
- float x = parts[0].ParseInvariantFloat();
- float y = parts[1].ParseInvariantFloat();
- float z = parts[2].ParseInvariantFloat();
-
- var vertex = new Vertex()
- {
- Position = new System.Numerics.Vector3(x, y, z),
- };
- _dataStore.Vertices.Add(vertex);
- }
- }
-}
\ No newline at end of file
diff --git a/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/Shaders/ShaderExtensions.cs b/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
deleted file mode 100644
index 830a2aa..0000000
--- a/Engine/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
+++ /dev/null
@@ -1,261 +0,0 @@
-using SharpEngine.Shared;
-using Silk.NET.OpenGL;
-using System.Numerics;
-using System.Text.RegularExpressions;
-
-namespace SharpEngine.Core.Shaders;
-
-///
-/// Represents a shader program in the engine.
-///
-public partial class Shader
-{
- ///
- /// Initializes and compiles vertex and fragment shaders into a shader program.
- ///
- /// The current instance after initialization.
- public Shader Initialize()
- {
- // Load and compile shader
- if (!LoadShader(ShaderType.VertexShader, VertPath, out uint vertexShader))
- {
- Debug.Log.Error("Unable to load vertex shader.");
- return this;
- }
-
- if (!LoadShader(ShaderType.FragmentShader, FragPath, out uint fragmentShader))
- {
- Debug.Log.Error("Unable to load fragment shader.");
- return this;
- }
-
- // These two shaders must then be merged into a shader program, which can then be used by OpenGL.
- // To do this, create a program...
-
- Handle = _gl.CreateProgram();
-
- // Attach both shaders...
- _gl.AttachShader(Handle, vertexShader);
- _gl.AttachShader(Handle, fragmentShader);
-
- // And then link them together.
- bool shaderLinked = LinkProgram(Handle);
-
- // When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
- // Detach them, and then delete them.
- _gl.DetachShader(Handle, vertexShader);
- _gl.DetachShader(Handle, fragmentShader);
- _gl.DeleteShader(fragmentShader);
- _gl.DeleteShader(vertexShader);
-
- if (!shaderLinked)
- {
- Debug.Log.Information("Unable to link shader program.");
- return this;
- }
-
- // The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
- // Querying this from the shader is very slow, so we do it once on initialization and reuse those values
- // later.
- SetUniformLocations();
-
- return this;
- }
-
- private bool LoadShader(ShaderType shaderType, string shaderPath, out uint shader)
- {
- if (!File.Exists(shaderPath))
- {
- Debug.Log.Information("Shader file not found: {Path}", shaderPath);
-
- shader = 0;
- return false;
- }
-
- string shaderSource = File.ReadAllText(shaderPath);
- shaderSource = ProcessIncludes(shaderSource, Path.GetDirectoryName(shaderPath)!);
-
- // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
- shader = _gl.CreateShader(shaderType);
- _gl.ShaderSource(shader, shaderSource);
-
- if (!CompileShader(shader))
- {
- Debug.Log.Information("Unable to load {Type} shader from '{Path}'.", shaderType, shaderPath);
- return false;
- }
-
- return true;
- }
-
- private void SetUniformLocations()
- {
- // First, we have to get the number of active uniforms in the shader.
- _gl.GetProgram(Handle, GLEnum.ActiveUniforms, out var numberOfUniforms);
-
- Dictionary uniformLocations = [];
-
- // Loop over all the uniforms,
- for (uint i = 0; i < numberOfUniforms; i++)
- {
- // get the name of this uniform,
- var key = _gl.GetActiveUniform(Handle, i, out _, out _);
-
- // get the location,
- var location = _gl.GetUniformLocation(Handle, key);
-
- // and then add it to the dictionary.
- uniformLocations.Add(key, location);
- }
-
- _uniformLocations = uniformLocations;
- }
-
- private static string ProcessIncludes(string shaderCode, string directory)
- {
- const string includePattern = @"#include\s+""(.+?)""";
- return Regex.Replace(shaderCode, includePattern, match =>
- {
- string includePath = Path.Combine(directory, match.Groups[1].Value);
- string includeCode = File.ReadAllText(includePath);
- return ProcessIncludes(includeCode, Path.GetDirectoryName(includePath)!);
- }, RegexOptions.NonBacktracking);
- }
-
- private bool CompileShader(uint shader)
- {
- // Try to compile the shader
- _gl.CompileShader(shader);
-
- // Check for compilation errors
- _gl.GetShader(shader, GLEnum.CompileStatus, out var statusCode);
- if (statusCode != (int)GLEnum.True)
- {
- // We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
- var infoLog = _gl.GetShaderInfoLog(shader);
- Debug.Log.Error("Error occurred whilst compiling Shader({Shader}).\n\n{Log}", shader, infoLog);
-
- return false;
- }
-
- return true;
- }
-
- private bool LinkProgram(uint program)
- {
- _gl.LinkProgram(program);
- _gl.GetProgram(program, GLEnum.LinkStatus, out var statusCode);
-
- if (statusCode != (int)GLEnum.True)
- {
- string infoLog = _gl.GetProgramInfoLog(program);
- Debug.Log.Error("Error occurred whilst linking Program({Program}): {Info}", program, infoLog);
-
- return false;
- }
-
- return true;
- }
-
- ///
- /// Enables the shader program.
- ///
- public void Use()
- => _gl.UseProgram(Handle);
-
- ///
- /// Checks if the shader attribute exists within the current shader.
- ///
- /// The name of the attribute that's being looked for.
- /// Outputs the location of the attribute in the shader if found; otherwise -1.
- /// If the attribute exists, ; otherwise, .
- public bool TryGetAttribLocation(string attribName, out int location)
- {
- location = _gl.GetAttribLocation(Handle, attribName);
- if (location == ShaderAttributes.AttributeLocationNotFound)
- {
- Debug.Log.Warning("Attribute '{Attribute}' not found in shader program.", attribName);
- return false;
- }
-
- return true;
- }
-
- // Uniform setters
- // Uniforms are variables that can be set by user code, instead of reading them from the VBO.
- // You use VBOs for vertex-related data, and uniforms for almost everything else.
-
- // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
- // 1. Bind the program you want to set the uniform on
- // 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
- // 3. Use the appropriate GL.Uniform* function to set the uniform.
-
- private bool TrySetUniform(string uniformName, T data, Action setter)
- {
- if (!_uniformLocations.TryGetValue(uniformName, out int location))
- {
- Debug.Log.Information("Uniform '{UniformName}' not found in shader '{ShaderName}'.", uniformName, Name);
- return false;
- }
-
- // TODO: #95 The _gl.UseProgram should be not be called here. Rather the renderer should call it once before rendering.
- _gl.UseProgram(Handle);
- setter(location, data);
-
- return true;
- }
-
- ///
- /// Set a uniform int on this shader.
- ///
- /// The name of the uniform.
- /// The data to set.
- public bool SetInt(string name, int data)
- => TrySetUniform(name, data, _gl.Uniform1);
-
- ///
- /// Set a uniform float on this shader.
- ///
- /// The name of the uniform.
- /// The data to set.
- public bool SetFloat(string name, float data)
- => TrySetUniform(name, data, _gl.Uniform1);
-
- ///
- /// Set a uniform Matrix4 on this shader
- ///
- /// The name of the uniform.
- /// The data to set.
- /// Determines whether or not the matrix should be transposed. Defaults to .
- ///
- ///
- /// The matrix is transposed before being sent to the shader unless is set to .
- ///
- ///
- public bool SetMatrix4(string name, Matrix4x4 data, bool transpose = true)
- => TrySetUniform(name, data, (uniform, d) => _gl.UniformMatrix4(uniform, transpose, d.ToSpan()));
-
- ///
- /// Set a uniform Vector2 on this shader.
- ///
- /// The name of the uniform.
- /// The data to set.
- public bool SetVector2(string name, Vector2 data)
- => TrySetUniform(name, data, _gl.Uniform2);
-
- ///
- /// Set a uniform Vector3 on this shader.
- ///
- /// The name of the uniform.
- /// The data to set.
- public bool SetVector3(string name, Vector3 data)
- => TrySetUniform(name, data, _gl.Uniform3);
-
- ///
- /// Set a uniform Vector3 on this shader.
- ///
- /// The name of the uniform.
- /// The data to set.
- public bool SetVector4(string name, Vector4 data)
- => TrySetUniform(name, data, _gl.Uniform4);
-}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs b/Engine/SharpEngine.Core.Components/Properties/Textures/Texture.cs
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.Components/SharpEngine.Core.Components.csproj b/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
deleted file mode 100644
index 3ba046b..0000000
--- a/Engine/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
- $(GenerateNuGetPackages)
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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/Primitives/Plane.cs b/Engine/SharpEngine.Core/Primitives/Plane.cs
deleted file mode 100644
index ba046a5..0000000
--- a/Engine/SharpEngine.Core/Primitives/Plane.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using SharpEngine.Core.Entities.Properties.Meshes;
-using SharpEngine.Core.Windowing;
-
-namespace SharpEngine.Core.Primitives;
-
-///
-/// Used to create a primitive plane object.
-///
-public static class Plane
-{
- /// The plane mesh.
- public static Mesh Mesh { get; } = new(Window.GL)
- {
- Vertices =
- [
- 1f, 1f, 0.0f, // top right
- 1f, -1f, 0.0f, // bottom right
- -1f, -1f, 0.0f, // bottom left
- -1f, 1f, 0.0f, // top left
- ],
- Normals =
- [
- 0.0f, 0.0f, 1.0f,
- 0.0f, 0.0f, 1.0f,
- 0.0f, 0.0f, 1.0f,
- 0.0f, 0.0f, 1.0f,
- ],
- TextureCoordinates =
- [
- 1.0f, 0.0f,
- 1.0f, 0.0f,
- 0.0f, 1.0f,
- 0.0f, 1.0f,
- ],
- Indices =
- [
- 0, 1, 3, // first triangle
- 1, 2, 3, // second triangle
- ]
- };
-}
diff --git a/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/ShaderBase.cs b/Engine/SharpEngine.Core/Shaders/ShaderBase.cs
deleted file mode 100644
index e7fd389..0000000
--- a/Engine/SharpEngine.Core/Shaders/ShaderBase.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using SharpEngine.Core.Components.Properties;
-using SharpEngine.Shared;
-using System;
-
-namespace SharpEngine.Core.Shaders;
-
-///
-/// Represents the base class for all shaders.
-///
-public abstract class ShaderBase
-{
- /// Gets the shader.
- public Shader? Shader { get; protected set; }
-
- /// Gets or sets the vertex array object.
- public uint Vao { get; set; }
-
- ///
- /// Sets the attributes for the shader.
- ///
- ///
- /// if the attributes were set successfully; otherwise .
- ///
- public virtual bool SetAttributes()
- {
- if (Shader is null)
- {
- Debug.Log.Error("Unable to set shader attributes, shader not found.");
- return false;
- }
-
- 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.Core.Tests/Class1.cs b/Engine/Tests/SharpEngine.Core.Tests/Class1.cs
deleted file mode 100644
index ff34b98..0000000
--- a/Engine/Tests/SharpEngine.Core.Tests/Class1.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace SharpEngine.Core.Tests;
-
-public class Class1
-{
-
-}
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/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 84%
rename from Engine/Examples/MinecraftClone/Inventory.cs
rename to Examples/MinecraftClone/Inventory.cs
index ed8b7eb..b195560 100644
--- a/Engine/Examples/MinecraftClone/Inventory.cs
+++ b/Examples/MinecraftClone/Inventory.cs
@@ -1,4 +1,5 @@
-using System;
+using Minecraft.Terrain.Block;
+using System;
using System.Linq;
namespace Minecraft;
@@ -31,7 +32,7 @@ public class Inventory
public InventoryItem GetActiveSlotItems(int slotNumber)
=> Toolbar[slotNumber].Items;
- /*public void AddItem(BlockType type)
+ /*public void AddItem(BlockId type)
{
var item = Items.FirstOrDefault(x => x.Type == type);
if (item is not null)
@@ -43,10 +44,10 @@ public InventoryItem GetActiveSlotItems(int slotNumber)
///
/// Adds an item to the toolbar.
///
- /// The type of the block to add to the toolbar.
- public void AddToolbarItem(BlockType blockType)
+ /// The type of the block to add to the toolbar.
+ public void AddToolbarItem(BlockId BlockId)
{
- var slot = Toolbar.FirstOrDefault(i => i.Items.Type == blockType);
+ var slot = Toolbar.FirstOrDefault(i => i.Items.Type == BlockId);
if (slot is not null)
{
slot.Items.Amount += 1;
@@ -57,18 +58,18 @@ public void AddToolbarItem(BlockType blockType)
return;
}
- if (SelectedSlot.Items.Type == BlockType.None)
+ if (SelectedSlot.Items.Type == BlockId.Air)
{
- SelectedSlot.Items = new InventoryItem { Type = blockType, Amount = 1 };
+ SelectedSlot.Items = new InventoryItem { Type = BlockId, Amount = 1 };
Toolbar[SelectedSlotIndex] = SelectedSlot;
return;
}
for (int i = 0; i < Toolbar.Length; i++)
{
- if (Toolbar[i].Items.Type == BlockType.None)
+ if (Toolbar[i].Items.Type == BlockId.Air)
{
- Toolbar[i].Items = new InventoryItem { Type = blockType, Amount = 1 };
+ Toolbar[i].Items = new InventoryItem { Type = BlockId, Amount = 1 };
return;
}
}
@@ -130,7 +131,7 @@ public class InventoryItem
///
/// Gets or sets the type of the block in the inventory.
///
- public BlockType Type { get; set; } = BlockType.None;
+ public BlockId Type { get; set; } = BlockId.Air;
/// Gets or sets the amount of the block.
public int Amount { get; set; }
diff --git a/Engine/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs
similarity index 51%
rename from Engine/Examples/MinecraftClone/Minecraft.cs
rename to Examples/MinecraftClone/Minecraft.cs
index 92494da..cb637fd 100644
--- a/Engine/Examples/MinecraftClone/Minecraft.cs
+++ b/Examples/MinecraftClone/Minecraft.cs
@@ -1,25 +1,17 @@
using ImGuiNET;
-using Minecraft.Block;
-using ObjLoader.Loaders.ObjLoader;
+using Microsoft.Extensions.Logging;
+using Minecraft.Terrain.Block;
+using Minecraft.Terrain.Layers;
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.Numerics;
using SharpEngine.Core.Scenes;
using SharpEngine.Core.Windowing;
-using SharpEngine.Shared;
-using Silk.NET.Core.Native;
using Silk.NET.Input;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using Tutorial;
namespace Minecraft;
@@ -31,30 +23,47 @@ namespace Minecraft;
public class Minecraft : Game
{
private readonly Scene _scene;
-
- private SceneNode _lightsNode;
- private SceneNode _blocksNode;
+ private readonly ILogger _logger;
private Input _input;
private readonly Inventory _inventory;
private UIElement _uiElem;
+ private readonly Terrain.TerrainGenerator_New _terrain;
+ private readonly SceneNode _blocksNode;
+
///
/// 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();
+ _blocksNode = _scene.AddNode("Blocks");
+ _terrain = new Terrain.TerrainGenerator_New(_scene, _blocksNode,
+ [
+ new HeightMapPass(),
+ new BaseTerrainPass(),
+ new CavePass(),
+ // new OrePass(),
+ // new WaterPass(),
+ // new SurfaceDecorationPass(),
+ // new FoliagePass()
+ ]);
}
///
@@ -67,31 +76,33 @@ public override void Initialize()
_input = new Input(Camera);
_inventory.Initialize();
- _lightsNode = _scene.Root.AddChild("lights");
- _blocksNode = _scene.Root.AddChild("blocks");
+ _terrain.InitializeWorld();
- var gridLayout = new GridLayout();
+ InitializeUI();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogInformation(ex, "{Message}", ex.Message);
+ }
+ }
- // TODO: #89 Fix UI renderer
- _uiElem = new UIElement("uiElement");
- _scene.UIElements.Add(_uiElem);
+ private void InitializeUI()
+ {
+ // var gridLayout = new GridLayout();
- //var uiElem2 = new UIElement("uiElement");
- //uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f);
- //uiElem2.Transform.Position = new Vector2(30, 0);
+ // TODO: #89 Fix UI renderer
+ _uiElem = new UIElement(Window.GetGL(), "uiElement");
+ _scene.UIElements.Add(_uiElem);
- // gridLayout.AddChild(_uiElem, uiElem2);
- // _scene.UIElements.Add(_uiElem);
- // _scene.UIElements.Add(uiElem2);
+ var uiElem2 = new UIElement(Window.GetGL(), "uiElement");
+ uiElem2.Transform.Scale = new Vector2(0.2f, 0.2f);
+ uiElem2.Transform.Position = new Vector2(30, 0);
- //_scene.UIElements.Add(gridLayout);
+ // gridLayout.AddChild(_uiElem, uiElem2);
+ _scene.UIElements.Add(_uiElem);
+ _scene.UIElements.Add(uiElem2);
- InitializeWorld();
- }
- catch (Exception ex)
- {
- Debug.Log.Information(ex.Message, "{Message}", ex.Message);
- }
+ // _scene.UIElements.Add(gridLayout);
}
///
@@ -128,117 +139,34 @@ public void OnAfterRender(Frame frame)
ImGui.SliderFloat("rotation", ref rotation, 0, 360);
ImGui.Text($"UI Element width: {_uiElem.Width}");
- ImGui.SliderFloat("width", ref width, 0, 100);
+ ImGui.SliderFloat("width", ref width, 0, Window.Width);
ImGui.Text($"UI Element height: {_uiElem.Height}");
- ImGui.SliderFloat("height", ref height, 0, 100);
+ ImGui.SliderFloat("height", ref height, 0, Window.Height);
ImGui.End();
- _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(x, y);
- _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(sx, sy);
+ _uiElem.Transform.Position = new Vector2(x, y);
+ _uiElem.Transform.Scale = new Vector2(sx, sy);
_uiElem.Transform.Rotation.Angle = rotation;
_uiElem.Height = height;
_uiElem.Width = width;
}
-
- private void InitializeWorld()
- {
- InitializeLights();
- InitializeChunks();
-
- // 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 go = new GameObject(model);
- var go2 = new GameObject(model)
- {
- Transform = new Transform(new SharpEngine.Core.Numerics.Vector3(0, 0, 30))
- };
-
- _scene.Root.AddChild(go);
- _scene.Root.AddChild(go2);
- }
-
- private void InitializeLights()
- {
- _lightsNode.AddChild(new DirectionalLight());
-
- _lightsNode.AddChild(
- new PointLight(new Vector3(0.7f, 0.2f, 2.0f), 0),
- new PointLight(new Vector3(2.3f, -3.3f, -4.0f), 1),
- new PointLight(new Vector3(-4.0f, 2.0f, -12.0f), 2),
- new PointLight(new Vector3(0.0f, 0.0f, -3.0f), 3)
- );
-
- _lightsNode.AddChild(new SpotLight()
- {
- Ambient = new Vector3(0.0f, 0.0f, 0.0f),
- Diffuse = new Vector3(1.0f, 1.0f, 1.0f),
- Specular = new Vector3(1.0f, 1.0f, 1.0f),
- });
- }
-
- private void InitializeChunks()
- {
- // TODO: #88 Generate chunks when player moves
-
- // TODO: #87 Generate chunks using 3d Perlin noise
-
- const int chunkSize = 16;
- const int numChunks = 1;
- // const int numChunks = 3;
-
- for (int i = 0; i < numChunks; i++)
- {
- var chunkPos = new Vector3(i * chunkSize, 0, 0);
- GenerateChunk(chunkSize, chunkPos);
- }
- }
-
- private void GenerateChunk(int chunkSize, Vector3 chunkPos)
- {
- for (int x = 0; x < chunkSize; x++)
- {
- for (int z = 0; z < chunkSize; z++)
- {
- var blockPos = chunkPos + new Vector3(x, 0, z);
-
- var dirt = new Dirt(blockPos, $"Dirt ({x}{z})");
- _blocksNode.AddChild(dirt);
-
- for (int y = 1; y < chunkSize; y++)
- {
- blockPos.Y = -y;
- var stone = new Stone(blockPos, $"Dirt ({x}{z}.{y})");
- _blocksNode.AddChild(stone);
- }
- }
- }
- }
-
///
- 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?
///
public override void HandleKeyboard(IKeyboard input, double deltaTime)
{
- for (int i = 0; i <= 9; i++)
+ for (int i = 1; i <= 9; i++)
{
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);
}
}
@@ -263,42 +191,42 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button)
{
if (button == MouseButton.Right)
{
- if (_inventory.SelectedSlot.Items.Type != BlockType.None && _inventory.SelectedSlot.Items.Amount > 0)
+ if (_inventory.SelectedSlot.Items.Type != BlockId.Air && _inventory.SelectedSlot.Items.Amount > 0)
{
PlaceBlock();
_inventory.SelectedSlot.Items.Amount -= 1;
if (_inventory.SelectedSlot.Items.Amount < 0)
- _inventory.SelectedSlot.Items.Type = BlockType.None;
+ _inventory.SelectedSlot.Items.Type = BlockId.Air;
}
else
{
- Debug.Log.Information("No more {Type}s.", _inventory.SelectedSlot.Items.Type);
+ _logger.LogInformation("No more {Type}s.", _inventory.SelectedSlot.Items.Type);
}
}
if (button == MouseButton.Left)
{
- var destroyedBlockType = DestroyBlock();
- if (destroyedBlockType != BlockType.None)
+ var destroyedBlockId = DestroyBlock();
+ if (destroyedBlockId != BlockId.Air)
{
// 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);
- _inventory.AddToolbarItem(destroyedBlockType);
+ _logger.LogInformation("Block destroyed: {DestroyedBlockId}.", destroyedBlockId);
+ _inventory.AddToolbarItem(destroyedBlockId);
}
}
}
- private BlockType DestroyBlock()
+ private BlockId DestroyBlock()
{
- if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out Vector3 _, allowedTypes: typeof(BlockBase)))
- return BlockType.None;
+ if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out _, allowedTypes: typeof(BlockBase)))
+ return BlockId.Air;
var block = (BlockBase)intersectingObject!;
_blocksNode.RemoveChild(intersectingObject!);
- return block.BlockType;
+ return block.BlockId;
}
private void PlaceBlock()
@@ -313,13 +241,13 @@ 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)
{
Vector3 normal = Ray.GetClosestFaceNormal(hitPosition, intersectingObject);
- return (System.Numerics.Vector3)intersectingObject.Transform.Position + (normal * (System.Numerics.Vector3)intersectingObject.Transform.Scale);
+ return intersectingObject.Transform.Position + (normal * intersectingObject.Transform.Scale);
}
///
@@ -338,7 +266,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/MinecraftClone/Block/BlockBase.cs b/Examples/MinecraftClone/Terrain/Block/BlockBase.cs
similarity index 85%
rename from Engine/Examples/MinecraftClone/Block/BlockBase.cs
rename to Examples/MinecraftClone/Terrain/Block/BlockBase.cs
index 1c62d38..7461f0e 100644
--- a/Engine/Examples/MinecraftClone/Block/BlockBase.cs
+++ b/Examples/MinecraftClone/Terrain/Block/BlockBase.cs
@@ -1,9 +1,8 @@
using SharpEngine.Core.Entities;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Primitives;
-using System.Numerics;
-
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
///
/// Represents a block in the game.
@@ -13,10 +12,10 @@ public class BlockBase : GameObject
///
/// Gets the block type.
///
- public virtual BlockType BlockType { get; }
+ public virtual BlockId BlockId { get; }
/// Gets a value indicating whether the block is solid.
- public virtual bool IsSolid { get; }
+ public virtual bool IsSolid { get; } = true;
/// Gets or sets the size of the block in the inventory.
public int SizeInInventory { get; set; } = 1;
@@ -42,7 +41,7 @@ protected BlockBase(Vector3 position, string name, string diffuseMapFile, string
private void Initialize(Vector3 position, string name, string diffuseMapFile, string specularMapFile, string vertShaderFile, string fragShaderFile)
{
- var cube = PrimitiveFactory.Create(PrimitiveType.Cube, position, diffuseMapFile, specularMapFile, vertShaderFile, fragShaderFile);
+ var cube = PrimitiveFactory.Create(PrimitiveType.Cube, (SharpEngine.Core.Numerics.Vector3)position, diffuseMapFile, specularMapFile, vertShaderFile, fragShaderFile);
AssignProperties(cube, name);
}
diff --git a/Engine/Examples/MinecraftClone/BlockFactory.cs b/Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
similarity index 54%
rename from Engine/Examples/MinecraftClone/BlockFactory.cs
rename to Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
index 7991855..44d1deb 100644
--- a/Engine/Examples/MinecraftClone/BlockFactory.cs
+++ b/Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
@@ -1,9 +1,7 @@
-using Minecraft.Block;
-
+using SharpEngine.Core.Numerics;
using System;
-using System.Numerics;
-namespace Minecraft
+namespace Minecraft.Terrain.Block
{
///
/// Used to create a block objects.
@@ -18,27 +16,13 @@ public static class BlockFactory
/// The name of the block to be created.
/// The newly created block.
///
- public static BlockBase CreateBlock(BlockType type, Vector3 position, string name)
+ public static BlockBase CreateBlock(BlockId type, Vector3 position, string name)
=> type switch
{
- BlockType.Dirt => new Dirt(position, name),
- BlockType.Stone => new Stone(position, name),
+ BlockId.Dirt => new Dirt(position, name),
+ BlockId.Stone => new Stone(position, name),
+ BlockId.Grass => new Grass(position, name),
_ => throw new ArgumentException("Invalid block type", nameof(type)),
};
}
-
- ///
- /// Represents the type of block.
- ///
- public enum BlockType
- {
- /// Represents no block type.
- None,
-
- /// Represents a dirt block.
- Dirt,
-
- /// Represents a stone block.
- Stone
- }
}
diff --git a/Examples/MinecraftClone/Terrain/Block/BlockId.cs b/Examples/MinecraftClone/Terrain/Block/BlockId.cs
new file mode 100644
index 0000000..34b428a
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/BlockId.cs
@@ -0,0 +1,26 @@
+namespace Minecraft.Terrain.Block;
+
+///
+/// Represents the type of block.
+///
+public enum BlockId : ushort
+{
+ Air = 0,
+
+ Grass = 1,
+ Dirt = 2,
+ Stone = 3,
+
+ Water = 10,
+
+ CoalOre = 20,
+ IronOre = 21,
+ CopperOre = 22,
+
+ Wood = 50,
+
+ TallGrass = 100,
+ Flower = 101,
+ Log = 102,
+ Leaves = 103
+}
\ No newline at end of file
diff --git a/Engine/Examples/MinecraftClone/Block/Dirt.cs b/Examples/MinecraftClone/Terrain/Block/Dirt.cs
similarity index 83%
rename from Engine/Examples/MinecraftClone/Block/Dirt.cs
rename to Examples/MinecraftClone/Terrain/Block/Dirt.cs
index 183d5d8..d76622e 100644
--- a/Engine/Examples/MinecraftClone/Block/Dirt.cs
+++ b/Examples/MinecraftClone/Terrain/Block/Dirt.cs
@@ -1,8 +1,8 @@
using SharpEngine.Core._Resources;
using SharpEngine.Core.Extensions;
-using System.Numerics;
+using SharpEngine.Core.Numerics;
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
///
/// Represents a dirt block.
@@ -20,8 +20,5 @@ public Dirt(Vector3 position, string name) : base(position, name, PathExtensions
Default.FragmentShader) { }
///
- public override BlockType BlockType => BlockType.Dirt;
-
- ///
- public override bool IsSolid => true;
+ public override BlockId BlockId => BlockId.Dirt;
}
diff --git a/Examples/MinecraftClone/Terrain/Block/Grass.cs b/Examples/MinecraftClone/Terrain/Block/Grass.cs
new file mode 100644
index 0000000..8fb3faa
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Grass.cs
@@ -0,0 +1,21 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Grass : BlockBase
+{
+ ///
+ /// Initializes a new block.
+ ///
+ /// The position where the block should be initialized.
+ /// The name of the block to initialize.
+ public Grass(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\grass.jpg"),
+ PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"),
+ Default.VertexShader,
+ Default.FragmentShader) { }
+
+ ///
+ public override BlockId BlockId => BlockId.Grass;
+}
diff --git a/Examples/MinecraftClone/Terrain/Block/Iron.cs b/Examples/MinecraftClone/Terrain/Block/Iron.cs
new file mode 100644
index 0000000..431bf46
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Iron.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Iron : BlockBase
+{
+ public Iron(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.IronOre;
+}
diff --git a/Examples/MinecraftClone/Terrain/Block/Leaf.cs b/Examples/MinecraftClone/Terrain/Block/Leaf.cs
new file mode 100644
index 0000000..b047da2
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Leaf.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Leaf : BlockBase
+{
+ public Leaf(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.Leaves;
+}
diff --git a/Engine/Examples/MinecraftClone/Block/Stone.cs b/Examples/MinecraftClone/Terrain/Block/Stone.cs
similarity index 50%
rename from Engine/Examples/MinecraftClone/Block/Stone.cs
rename to Examples/MinecraftClone/Terrain/Block/Stone.cs
index 13352d3..dcd3b3f 100644
--- a/Engine/Examples/MinecraftClone/Block/Stone.cs
+++ b/Examples/MinecraftClone/Terrain/Block/Stone.cs
@@ -1,8 +1,8 @@
using SharpEngine.Core._Resources;
using SharpEngine.Core.Extensions;
-using System.Numerics;
+using SharpEngine.Core.Numerics;
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
internal class Stone : BlockBase
{
@@ -14,13 +14,14 @@ internal class Stone : BlockBase
///
/// The position where the block is created.
/// The name of the object in the scene.
- public Stone(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\container2.png"),
- PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"),
- Default.VertexShader,
- Default.FragmentShader) { }
+ public Stone(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
///
- public override BlockType BlockType => BlockType.Stone;
+ public override BlockId BlockId => BlockId.Stone;
///
public override bool IsSolid => true;
diff --git a/Examples/MinecraftClone/Terrain/Block/Wood.cs b/Examples/MinecraftClone/Terrain/Block/Wood.cs
new file mode 100644
index 0000000..f58ff58
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Wood.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Wood : BlockBase
+{
+ public Wood(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.Wood;
+}
diff --git a/Examples/MinecraftClone/Terrain/ChunkData.cs b/Examples/MinecraftClone/Terrain/ChunkData.cs
new file mode 100644
index 0000000..63d27c6
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/ChunkData.cs
@@ -0,0 +1,57 @@
+using Minecraft.Terrain.Block;
+
+namespace Minecraft.Terrain;
+
+public sealed class ChunkData
+{
+ private readonly BlockId[,,] _blocks;
+ private readonly int[,] _heightMap;
+
+ public ChunkData(int size, int height)
+ {
+ Size = size;
+ Height = height;
+
+ _blocks = new BlockId[size, height, size];
+ _heightMap = new int[size, size];
+ }
+
+ public int Size { get; }
+
+ public int Height { get; }
+
+ public BlockId GetBlock(int x, int y, int z)
+ {
+ return _blocks[x, y, z];
+ }
+
+ public void SetBlock(int x, int y, int z, BlockId block)
+ {
+ _blocks[x, y, z] = block;
+ }
+
+ public int GetHeight(int x, int z)
+ {
+ return _heightMap[x, z];
+ }
+
+ public void SetHeight(int x, int z, int height)
+ {
+ _heightMap[x, z] = height;
+ }
+
+ public bool IsInside(int x, int y, int z)
+ {
+ return x >= 0 &&
+ y >= 0 &&
+ z >= 0 &&
+ x < Size &&
+ y < Height &&
+ z < Size;
+ }
+
+ public bool IsAir(int x, int y, int z)
+ {
+ return GetBlock(x, y, z) == BlockId.Air;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs b/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs
new file mode 100644
index 0000000..6f19da9
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs
@@ -0,0 +1,27 @@
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+
+namespace Minecraft.Terrain;
+
+public sealed class ChunkGenerationContext
+{
+ public required int Seed { get; init; }
+
+ public required Vector3 ChunkPosition { get; init; }
+
+ public required int ChunkSize { get; init; }
+
+ public required int MinY { get; init; }
+
+ public required int MaxY { get; init; }
+
+ public required int WaterLevel { get; init; }
+
+ public required INoiseGenerator TerrainNoise { get; init; }
+
+ public required INoiseGenerator CaveNoise { get; init; }
+
+ public required INoiseGenerator OreNoise { get; init; }
+
+ public required INoiseGenerator FoliageNoise { get; init; }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs b/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs
new file mode 100644
index 0000000..5980bbd
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs
@@ -0,0 +1,6 @@
+namespace Minecraft.Terrain;
+
+public interface IChunkGenerationLayer
+{
+ void Generate(ChunkGenerationContext context, ChunkData chunk);
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs b/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs
new file mode 100644
index 0000000..4ad29f1
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs
@@ -0,0 +1,59 @@
+using Minecraft.Terrain;
+using Minecraft.Terrain.Block;
+using System;
+
+namespace Minecraft.Terrain.Layers;
+
+public sealed class BaseTerrainPass : IChunkGenerationLayer
+{
+ private const int DirtDepth = 3;
+
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ int surfaceWorldY = chunk.GetHeight(x, z);
+ int surfaceLocalY = WorldYToLocalY(context, surfaceWorldY);
+
+ GenerateColumn(chunk, x, z, surfaceLocalY);
+ }
+ }
+ }
+
+ private static void GenerateColumn(ChunkData chunk, int x, int z, int surfaceLocalY)
+ {
+ for (int localY = 0; localY < chunk.Height; localY++)
+ {
+ BlockId block = GetBlockForLayer(localY, surfaceLocalY);
+
+ chunk.SetBlock(x, localY, z, block);
+ }
+ }
+
+ private static BlockId GetBlockForLayer(int localY, int surfaceLocalY)
+ {
+ if (localY > surfaceLocalY)
+ return BlockId.Air;
+
+ if (localY == 0)
+ return BlockId.Stone;
+
+ int depthFromSurface = surfaceLocalY - localY;
+
+ return depthFromSurface switch
+ {
+ 0 => BlockId.Grass,
+
+ > 0 and <= DirtDepth => BlockId.Dirt,
+
+ _ => BlockId.Stone
+ };
+ }
+
+ private static int WorldYToLocalY(ChunkGenerationContext context, int worldY)
+ {
+ return worldY - context.MinY;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/CavePass.cs b/Examples/MinecraftClone/Terrain/Layers/CavePass.cs
new file mode 100644
index 0000000..6239107
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/CavePass.cs
@@ -0,0 +1,54 @@
+using Minecraft.Terrain.Block;
+
+namespace Minecraft.Terrain.Layers;
+
+public sealed class CavePass : IChunkGenerationLayer
+{
+ private const float CaveThreshold = 0.68f;
+
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int y = 0; y < chunk.Height; y++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ TryCarveBlock(context, chunk, x, y, z);
+ }
+ }
+ }
+ }
+
+ private static void TryCarveBlock(
+ ChunkGenerationContext context,
+ ChunkData chunk,
+ int localX,
+ int localY,
+ int localZ)
+ {
+ if (localY == 0)
+ return;
+
+ BlockId block = chunk.GetBlock(localX, localY, localZ);
+
+ if (block != BlockId.Stone)
+ return;
+
+ float worldX = context.ChunkPosition.X + localX;
+ float worldY = LocalYToWorldY(context, localY);
+ float worldZ = context.ChunkPosition.Z + localZ;
+
+ float caveNoise = context.CaveNoise.Sample3D(worldX, worldY, worldZ);
+
+ if (caveNoise <= CaveThreshold)
+ return;
+
+ chunk.SetBlock(localX, localY, localZ, BlockId.Air);
+ }
+
+ private static int LocalYToWorldY(ChunkGenerationContext context, int localY)
+ {
+ return context.MinY + localY;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs b/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs
new file mode 100644
index 0000000..cffdedc
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs
@@ -0,0 +1,26 @@
+namespace Minecraft.Terrain.Layers;
+
+internal class HeightMapPass : IChunkGenerationLayer
+{
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ float worldX = context.ChunkPosition.X + x;
+ float worldZ = context.ChunkPosition.Z + z;
+
+ float noise = context.TerrainNoise.Sample2D(worldX, worldZ);
+
+ int minSurfaceY = 0;
+ int maxSurfaceY = 16;
+
+ int surfaceY = minSurfaceY +
+ (int)(noise * (maxSurfaceY - minSurfaceY));
+
+ chunk.SetHeight(x, z, surfaceY);
+ }
+ }
+ }
+}
diff --git a/Examples/MinecraftClone/Terrain/TerrainGenerator.cs b/Examples/MinecraftClone/Terrain/TerrainGenerator.cs
new file mode 100644
index 0000000..b71e3e9
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/TerrainGenerator.cs
@@ -0,0 +1,158 @@
+using Minecraft.Terrain.Block;
+using SharpEngine.Core.Entities;
+using SharpEngine.Core.Entities.Lights;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+using SharpEngine.Core.Scenes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Minecraft.Terrain;
+
+public sealed class TerrainGenerator_New
+{
+ private readonly INoiseGenerator _terrainNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ private readonly INoiseGenerator _caveNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ // TODO: Tweak
+ private readonly INoiseGenerator _oreNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ // TODO: Tweak
+ private readonly INoiseGenerator _foliageNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ private readonly IReadOnlyList _layers;
+
+ private readonly Scene _scene;
+
+ private SceneNode _lightsNode;
+ private SceneNode _blocksNode;
+
+ internal TerrainGenerator_New(Scene scene, SceneNode blocksNode, IEnumerable? layers = null)
+ {
+ _scene = scene;
+ _lightsNode = _scene.AddNode("Lights");
+ _blocksNode = blocksNode;
+ _layers = layers?.ToArray() ?? Array.Empty();
+ }
+
+ internal void InitializeWorld()
+ {
+ InitializeLights();
+ InitializeChunks();
+ }
+
+ private void InitializeLights()
+ {
+ _lightsNode.AddChild(new DirectionalLight());
+
+ _lightsNode.AddChild(
+ new PointLight(new Vector3(0.7f, 0.2f, 2.0f), 0),
+ new PointLight(new Vector3(2.3f, -3.3f, -4.0f), 1),
+ new PointLight(new Vector3(-4.0f, 2.0f, -12.0f), 2),
+ new PointLight(new Vector3(0.0f, 0.0f, -3.0f), 3)
+ );
+
+ _lightsNode.AddChild(new SpotLight()
+ {
+ Ambient = new Vector3(0.0f, 0.0f, 0.0f),
+ Diffuse = new Vector3(1.0f, 1.0f, 1.0f),
+ Specular = new Vector3(1.0f, 1.0f, 1.0f),
+ });
+ }
+
+ private void InitializeChunks()
+ {
+ const int chunkSize = 16;
+ const int numChunks = 5;
+
+ for (int chunkZ = 0; chunkZ < numChunks; chunkZ++)
+ {
+ for (int chunkX = 0; chunkX < numChunks; chunkX++)
+ {
+ var chunkPos = new Vector3(chunkX * chunkSize, 0, chunkZ * chunkSize);
+
+ var context = new ChunkGenerationContext
+ {
+ Seed = 1234,
+ ChunkPosition = chunkPos,
+ ChunkSize = chunkSize,
+ MinY = -16,
+ MaxY = 16,
+ WaterLevel = 0,
+
+ TerrainNoise = _terrainNoise,
+ CaveNoise = _caveNoise,
+ OreNoise = _oreNoise,
+ FoliageNoise = _foliageNoise
+ };
+
+ ChunkData chunk = GenerateChunk(context);
+
+ CreateBlocksFromChunkData(chunk, chunkPos);
+ }
+ }
+ }
+
+ public ChunkData GenerateChunk(ChunkGenerationContext context)
+ {
+ int height = context.MaxY - context.MinY + 1;
+
+ var chunk = new ChunkData(context.ChunkSize, height);
+
+ foreach (IChunkGenerationLayer layer in _layers)
+ {
+ layer.Generate(context, chunk);
+ }
+
+ return chunk;
+ }
+
+ private void CreateBlocksFromChunkData(ChunkData chunk, Vector3 chunkPos)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int y = 0; y < chunk.Height; y++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ BlockId block = chunk.GetBlock(x, y, z);
+
+ if (block == BlockId.Air)
+ continue;
+
+ var blockPos = chunkPos + new Vector3(x, y, z);
+
+ var blockObject = BlockFactory.CreateBlock(block, blockPos, $"Block_{blockPos.X}_{blockPos.Y}_{blockPos.Z}");
+
+ _blocksNode.AddChild(blockObject);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs b/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs
new file mode 100644
index 0000000..84ee4f0
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs
@@ -0,0 +1,145 @@
+using Minecraft.Terrain.Block;
+using SharpEngine.Core.Entities.Lights;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+using SharpEngine.Core.Scenes;
+
+namespace Minecraft.Terrain;
+
+internal class TerrainGenerator_Old
+{
+ private readonly INoiseGenerator _terrainNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ private readonly INoiseGenerator _caveNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ private readonly Scene _scene;
+
+ private SceneNode _lightsNode;
+ private SceneNode _blocksNode;
+
+ internal TerrainGenerator_Old(Scene scene, SceneNode blocksNode)
+ {
+ _scene = scene;
+ _lightsNode = _scene.AddNode("Lights");
+ _blocksNode = blocksNode;
+ }
+
+ internal void InitializeWorld()
+ {
+ InitializeLights();
+ InitializeChunks();
+ }
+
+ private void InitializeLights()
+ {
+ _lightsNode.AddChild(new DirectionalLight());
+
+ _lightsNode.AddChild(
+ new PointLight(new Vector3(0.7f, 0.2f, 2.0f), 0),
+ new PointLight(new Vector3(2.3f, -3.3f, -4.0f), 1),
+ new PointLight(new Vector3(-4.0f, 2.0f, -12.0f), 2),
+ new PointLight(new Vector3(0.0f, 0.0f, -3.0f), 3)
+ );
+
+ _lightsNode.AddChild(new SpotLight()
+ {
+ Ambient = new Vector3(0.0f, 0.0f, 0.0f),
+ Diffuse = new Vector3(1.0f, 1.0f, 1.0f),
+ Specular = new Vector3(1.0f, 1.0f, 1.0f),
+ });
+ }
+
+ private void InitializeChunks()
+ {
+ // TODO: #88 Generate chunks when player moves
+
+ // TODO: #87 Generate chunks using 3d Perlin noise
+
+ const int chunkSize = 16;
+ const int numChunks = 1;
+ // const int numChunks = 3;
+
+ for (int i = 0; i < numChunks; i++)
+ {
+ var chunkPos = new Vector3(i * chunkSize, 0, 0);
+ GenerateChunk(chunkSize, chunkPos);
+ }
+ }
+
+ private void GenerateChunk(int chunkSize, Vector3 chunkPos)
+ {
+ const int minTerrainHeight = 4;
+ const int maxTerrainHeight = 16;
+
+ const int dirtDepth = 3;
+
+ const int lowestY = -16;
+
+ const float caveThreshold = 0.68f;
+
+ for (int x = 0; x < chunkSize; x++)
+ {
+ for (int z = 0; z < chunkSize; z++)
+ {
+ float worldX = chunkPos.X + x;
+ float worldZ = chunkPos.Z + z;
+
+ float terrainNoise = _terrainNoise.Sample2D(worldX, worldZ);
+
+ int columnHeight = minTerrainHeight +
+ (int)(terrainNoise * (maxTerrainHeight - minTerrainHeight));
+
+ int surfaceY = (int)chunkPos.Y + columnHeight - 1;
+ int bottomY = (int)chunkPos.Y + lowestY;
+
+ for (int worldY = surfaceY; worldY >= bottomY; worldY--)
+ {
+ var blockPos = new Vector3(worldX, worldY, worldZ);
+
+ int depthFromSurface = surfaceY - worldY;
+
+ if (depthFromSurface == 0)
+ {
+ var grass = new Grass(blockPos, $"Grass ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(grass);
+
+ continue;
+ }
+
+ if (depthFromSurface <= dirtDepth)
+ {
+ var dirt = new Dirt(blockPos, $"Dirt ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(dirt);
+
+ continue;
+ }
+
+ bool isLowestLevel = worldY == bottomY;
+
+ if (!isLowestLevel)
+ {
+ float caveNoise = _caveNoise.Sample3D(worldX, worldY, worldZ);
+
+ if (caveNoise > caveThreshold)
+ continue;
+ }
+
+ var stone = new Stone(blockPos, $"Stone ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(stone);
+ }
+ }
+ }
+ }
+}
diff --git a/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md b/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md
new file mode 100644
index 0000000..a971e31
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md
@@ -0,0 +1,478 @@
+# Terrain Layering and Generation Pipeline
+
+This document describes a scalable terrain layering approach for chunk-based world generation.
+
+The goal is to avoid placing every terrain rule directly inside one large block loop. Instead, terrain generation should be treated as a sequence of independent generation passes.
+
+Each pass has one responsibility and modifies lightweight chunk/block data before the final renderable blocks or meshes are created.
+
+---
+
+## Core Idea
+
+Terrain generation should be split into two main concerns:
+
+```text
+Terrain shape
+ Determines where terrain exists.
+
+Terrain materials
+ Determines what each block should be.
+```
+
+For example:
+
+```text
+Shape:
+ - Column height
+ - Cave positions
+ - Terrain slope
+ - Sea level
+ - Mountain regions
+
+Materials:
+ - Grass
+ - Dirt
+ - Stone
+ - Ore
+ - Water
+ - Foliage
+```
+
+This makes terrain easier to extend later with features such as ores, trees, flowers, water, snow, sand, or biome-specific surfaces.
+
+---
+
+## Recommended Generation Pipeline
+
+```mermaid
+flowchart TD
+ A[Chunk Coordinates] --> B[Height Map Pass]
+
+ B --> C[Base Terrain Pass]
+ C --> C1[Grass Surface Layer]
+ C --> C2[Dirt Subsurface Layer]
+ C --> C3[Stone Underground Layer]
+ C --> C4[Bottom Stone / Bedrock Layer]
+
+ C1 --> D[Chunk Block Data]
+ C2 --> D
+ C3 --> D
+ C4 --> D
+
+ D --> E[Cave Pass]
+ E --> E1[Carve Only Stone]
+ E1 --> F[Ore Pass]
+
+ F --> F1[Replace Stone With Ore]
+ F1 --> G[Water Pass]
+
+ G --> G1[Fill Empty Blocks Below Water Level]
+ G1 --> H[Surface Decoration Pass]
+
+ H --> H1[Grass Tufts]
+ H --> H2[Flowers]
+ H --> H3[Rocks]
+
+ H1 --> I[Foliage Pass]
+ H2 --> I
+ H3 --> I
+
+ I --> I1[Trees]
+ I --> I2[Bushes]
+ I --> I3[Large Plants]
+
+ I1 --> J[Finalize Chunk]
+ I2 --> J
+ I3 --> J
+
+ J --> K[Mesh / GameObject Creation]
+```
+
+---
+
+## Layer Responsibilities
+
+### 1. Height Map Pass
+
+The height map pass decides the surface height for each `x/z` column.
+
+It should answer:
+
+```text
+How tall is the terrain at this world position?
+```
+
+Typical inputs:
+
+- World seed
+- World `x/z` position
+- 2D noise
+- Biome data
+- Terrain settings
+
+Example output:
+
+```text
+Column at x=10, z=4 has surfaceY = 12
+```
+
+The height map pass should not decide whether a block is grass, dirt, stone, ore, or water. It only defines the terrain shape.
+
+---
+
+### 2. Base Terrain Pass
+
+The base terrain pass creates the main solid terrain layers.
+
+Recommended rule:
+
+```text
+Highest solid block = Grass
+3 blocks below surface = Dirt
+Everything below that = Stone
+Lowest level = Stone or Bedrock
+```
+
+Conceptually:
+
+```text
+surfaceY Grass
+surfaceY - 1 Dirt
+surfaceY - 2 Dirt
+surfaceY - 3 Dirt
+surfaceY - 4 Stone
+surfaceY - 5 Stone
+...
+bottomY Stone / Bedrock
+```
+
+This pass should create predictable base terrain before caves, ores, water, or decorations are added.
+
+---
+
+### 3. Cave Pass
+
+The cave pass removes underground blocks based on 3D noise.
+
+Important rule:
+
+```text
+Caves should only carve stone.
+```
+
+This prevents caves from cutting holes through grass and dirt surface layers unless that is intentionally desired.
+
+Recommended cave rules:
+
+```text
+If block is Stone
+And block is not the lowest layer
+And cave noise is above threshold
+Then replace block with Air
+```
+
+The lowest layer should usually remain solid to prevent chunks from having holes through the bottom.
+
+---
+
+### 4. Ore Pass
+
+The ore pass replaces some stone blocks with ore blocks.
+
+Important rule:
+
+```text
+Ores should replace stone, not grass or dirt.
+```
+
+Example rules:
+
+```text
+Coal:
+ Allowed in stone
+ Common
+ Wide Y range
+
+Iron:
+ Allowed in stone
+ Medium rarity
+ Mid/deep Y range
+
+Rare ore:
+ Allowed in stone
+ Low rarity
+ Deep Y range
+```
+
+Ore generation can use:
+
+- 3D noise
+- Random chance
+- Y-level ranges
+- Vein size rules
+- Biome-specific modifiers
+
+---
+
+### 5. Water Pass
+
+The water pass fills empty spaces below a configured water level.
+
+Recommended rule:
+
+```text
+If block is Air
+And y is below or equal to waterLevel
+Then place Water
+```
+
+This should usually happen after caves are carved, because caves below sea level may become flooded.
+
+Water generation may also consider:
+
+- Oceans
+- Lakes
+- Rivers
+- Underground water
+- Biome rules
+
+---
+
+### 6. Surface Decoration Pass
+
+The surface decoration pass places small objects on top of terrain.
+
+Examples:
+
+- Grass tufts
+- Flowers
+- Pebbles
+- Mushrooms
+- Snow layers
+- Fallen sticks
+
+Recommended rule:
+
+```text
+If block is Grass
+And space above is Air
+And decoration noise/random allows it
+Then place decoration above the grass block
+```
+
+Surface decorations should usually not replace the terrain block itself. They usually occupy the air block above the surface.
+
+---
+
+### 7. Foliage Pass
+
+The foliage pass handles larger vegetation.
+
+Examples:
+
+- Trees
+- Bushes
+- Large plants
+- Cacti
+- Reeds
+
+Tree placement usually needs more checks than small decorations:
+
+```text
+If block is Grass
+And enough vertical space exists
+And enough horizontal space exists
+And tree noise/random allows it
+Then place tree
+```
+
+Large foliage should be generated late because it may occupy several blocks.
+
+---
+
+## Recommended Pass Order
+
+A practical order is:
+
+```text
+1. Height Map Pass
+2. Base Terrain Pass
+3. Cave Pass
+4. Bottom Layer Pass
+5. Ore Pass
+6. Water Pass
+7. Surface Decoration Pass
+8. Foliage Pass
+9. Final Mesh / GameObject Creation
+```
+
+A more abstract version:
+
+```text
+Shape
+Material
+Carving
+Replacement
+Fluid
+Decoration
+Finalization
+```
+
+---
+
+## Why Use Passes?
+
+Using generation passes keeps each system focused.
+
+Instead of one huge method containing every rule:
+
+```text
+GenerateChunk()
+ handles grass
+ handles dirt
+ handles stone
+ handles caves
+ handles ores
+ handles water
+ handles trees
+ handles flowers
+```
+
+Prefer:
+
+```text
+HeightMapPass
+BaseTerrainPass
+CavePass
+OrePass
+WaterPass
+DecorationPass
+FoliagePass
+```
+
+This makes the world generator easier to extend, debug, test, and tune.
+
+---
+
+## Recommended Data Flow
+
+```mermaid
+flowchart LR
+ A[World Seed] --> B[Generation Settings]
+ B --> C[Chunk Data]
+ C --> D[Generation Passes]
+ D --> E[Block IDs]
+ E --> F[Chunk Mesh]
+ E --> G[Optional GameObjects]
+```
+
+The generator should ideally operate on lightweight block data first.
+
+For example:
+
+```text
+BlockId.Grass
+BlockId.Dirt
+BlockId.Stone
+BlockId.Air
+BlockId.Water
+BlockId.IronOre
+```
+
+After generation is complete, the engine can convert the chunk data into:
+
+- A mesh
+- Renderable chunk objects
+- Physics colliders
+- Optional `GameObject` instances for interactive blocks
+
+This is usually better than creating one full `GameObject` for every block during terrain generation.
+
+---
+
+## Mental Model: Masks
+
+A useful way to think about terrain features is through masks.
+
+A mask defines where something is allowed to exist.
+
+Examples:
+
+```text
+Grass mask:
+ Only the highest solid block in a column
+
+Dirt mask:
+ Only the first 3 blocks below grass
+
+Stone mask:
+ Everything below dirt
+
+Cave mask:
+ Only stone blocks, excluding the bottom layer
+
+Ore mask:
+ Only stone blocks within allowed Y ranges
+
+Water mask:
+ Empty space below water level
+
+Tree mask:
+ Grass surface, enough empty space above, suitable random/noise value
+```
+
+This avoids messy conditionals and makes each terrain feature easier to reason about.
+
+---
+
+## Suggested Future Interfaces
+
+Eventually, the terrain generator could be modeled around generation passes:
+
+```csharp
+public interface IChunkGenerationPass
+{
+ void Generate(ChunkGenerationContext context, ChunkData chunk);
+}
+```
+
+Example passes:
+
+```csharp
+public sealed class HeightMapPass : IChunkGenerationPass;
+public sealed class BaseTerrainPass : IChunkGenerationPass;
+public sealed class CavePass : IChunkGenerationPass;
+public sealed class OrePass : IChunkGenerationPass;
+public sealed class WaterPass : IChunkGenerationPass;
+public sealed class FoliagePass : IChunkGenerationPass;
+```
+
+The chunk generator then becomes a pipeline:
+
+```csharp
+foreach (IChunkGenerationPass pass in _passes)
+{
+ pass.Generate(context, chunk);
+}
+```
+
+---
+
+## Summary
+
+Use a terrain generation pipeline where each layer or feature is handled by a dedicated pass.
+
+Recommended structure:
+
+```text
+Height map decides shape.
+Base terrain creates grass, dirt and stone.
+Caves carve only stone.
+Ores replace stone.
+Water fills empty air below water level.
+Decorations and foliage are placed last.
+Final chunk data is converted into renderable meshes or objects.
+```
+
+This keeps the system flexible enough to support future terrain features such as foliage, water, ores, biomes, snow, rivers, and structures.
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..36a530e 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,10 @@ 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.Initialize();
window.Run();
}
}
@@ -30,9 +29,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 62%
rename from Engine/Examples/MultipleWindows/Program.cs
rename to Examples/MultipleWindows/Program.cs
index 788b808..b1dfc16 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;
@@ -116,15 +117,16 @@ private static SharpEngine.Core.Windowing.Window CreateWindow()
private static void EnqueueWindow()
{
var window = CreateWindow();
- foreach (var mouse in window!.Input!.Mice)
- mouse.Click += Mouse_Click;
- _inputContexts.Add(window.Input);
+ // Prefer the higher-level Window event to avoid wiring low-level input handlers.
+ window.OnButtonMouseDown += (mouse, button) => Mouse_Click(mouse, (MouseButton)button, mouse.Position);
+
+ if (window.Input is not null)
+ _inputContexts.Add(window.Input);
+
_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 76%
rename from Engine/Examples/Tutorial 4.1 - Model Loading/Program.cs
rename to Examples/Tutorial 4.1 - Model Loading/Program.cs
index 1bffe63..8f974e5 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++)
{
@@ -74,17 +82,21 @@ private static void OnLoad()
Gl = GL.GetApi(window);
- Shader = new Shader(Gl, PathExtensions.GetAssemblyPath("shader2.vert"), Default.LightShader, "test").Initialize();
+ Shader = new Shader(Gl, PathExtensions.GetAssemblyPath("shader2.vert"), Default.LightShader, "test");
Texture = new Texture(Gl, "silk.png");
- var model = ObjLoaderFactory.Load(Gl, "Untitled2.obj");
-
- Models.Add(model);
- Models.Add(model);
+ if (ObjLoaderFactory.Load(Gl, "D:\\Untitled2.obj", out var 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 +118,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 +139,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 +175,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 +210,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..1840908
--- /dev/null
+++ b/ObjLoader/DataStore.cs
@@ -0,0 +1,114 @@
+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
+{
+ ///
+ /// Represents the smoothing group information for a face group.
+ ///
+ /// Determines whether smoothing is enabled for the face group.
+ /// The ID of the smoothing group, if applicable.
+ public readonly record struct SmoothingGroup(bool Enabled, int? GroupId);
+
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// TODO: This is probably not implemented corretly.
+ /// We are not handling things on a per-object basis and we are not handling smoothing groups correctly.
+ /// We should probably have a more complex data structure that can handle multiple objects, each with their own groups, materials, and smoothing groups.
+ ///
+ public class DataStore : IGroupDataStore, IFaceGroup, ITextureDataStore, INormalDataStore, IVertexDataStore, IMaterialDataStore, ISmoothingGroupDataStore, IObjectNameDataStore
+ {
+ 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; } = [];
+
+ /// Gets the list of smoothing groups defined in the file.
+ public List SmoothingGroups { get; } = [];
+
+ /// Gets the name of the object defined in the file, if any.
+ public string ObjectName { get; private set; } = string.Empty;
+
+ ///
+ /// 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);
+
+ ///
+ public void AddMaterial(Material currentMaterial)
+ => Materials.Add(currentMaterial);
+
+ ///
+ public void SetSmoothingGroup(int groupNumber)
+ => SmoothingGroups.Add(new SmoothingGroup(true, groupNumber));
+
+ ///
+ public void SetSmoothingGroupOff()
+ => SmoothingGroups.Add(new SmoothingGroup(false, null));
+
+ ///
+ public void SetObjectName(string name)
+ => ObjectName = name;
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/IMaterialDataStore.cs b/ObjLoader/IMaterialDataStore.cs
new file mode 100644
index 0000000..9424ed5
--- /dev/null
+++ b/ObjLoader/IMaterialDataStore.cs
@@ -0,0 +1,21 @@
+using SharpEngine.Core.Components.Properties;
+
+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
+{
+ ///
+ /// Adds a material to the material data store.
+ ///
+ /// The material to add.
+ void AddMaterial(Material currentMaterial);
+
+ ///
+ /// Sets the material for the current group.
+ ///
+ /// The name of the material to set.
+ void SetMaterial(string materialName);
+}
\ No newline at end of file
diff --git a/ObjLoader/IObjectNameDataStore.cs b/ObjLoader/IObjectNameDataStore.cs
new file mode 100644
index 0000000..44806a0
--- /dev/null
+++ b/ObjLoader/IObjectNameDataStore.cs
@@ -0,0 +1,13 @@
+namespace SharpEngine.Core.ObjLoader;
+
+///
+/// Holds the name of the object being parsed, allowing it to be set during parsing and retrieved later.
+///
+public interface IObjectNameDataStore
+{
+ ///
+ /// Sets the name of the object being parsed.
+ ///
+ /// The name of the object.
+ void SetObjectName(string name);
+}
\ No newline at end of file
diff --git a/ObjLoader/ISmoothingGroupDataStore.cs b/ObjLoader/ISmoothingGroupDataStore.cs
new file mode 100644
index 0000000..8afc13c
--- /dev/null
+++ b/ObjLoader/ISmoothingGroupDataStore.cs
@@ -0,0 +1,18 @@
+namespace SharpEngine.Core.ObjLoader;
+
+///
+/// Represents a data store for smoothing group information in an OBJ file, allowing the storage of smoothing group lines for later processing.
+///
+public interface ISmoothingGroupDataStore
+{
+ ///
+ /// Sets the current smoothing group to the specified group number.
+ ///
+ /// The smoothing group number.
+ void SetSmoothingGroup(int groupNumber);
+
+ ///
+ /// Turns off the current smoothing group.
+ ///
+ void SetSmoothingGroupOff();
+}
\ No newline at end of file
diff --git a/ObjLoader/Loaders/IFileManager.cs b/ObjLoader/Loaders/IFileManager.cs
new file mode 100644
index 0000000..21e33eb
--- /dev/null
+++ b/ObjLoader/Loaders/IFileManager.cs
@@ -0,0 +1,13 @@
+using System.IO;
+
+namespace SharpEngine.Core.ObjLoader.Loader.Loaders
+{
+ ///
+ /// Represents an abstraction of a stream reader for testability.
+ ///
+ public interface IFileManager
+ {
+ ///
+ StreamReader StreamReader(string path);
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/Loaders/LoaderBase.cs b/ObjLoader/Loaders/LoaderBase.cs
new file mode 100644
index 0000000..206c6d5
--- /dev/null
+++ b/ObjLoader/Loaders/LoaderBase.cs
@@ -0,0 +1,83 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Telemetry;
+using System.IO;
+using System.IO.Abstractions;
+
+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
+ {
+ private readonly IFileStreamFactory _fileStreamFactory;
+ private readonly IFileManager _fileManager;
+
+ private readonly ILogger _logger = LoggingExtensions.CreateLogger();
+
+ ///
+ /// Initializes a new instance of the .
+ ///
+ protected LoaderBase(IFileStreamFactory fileStreamFactory, IFileManager fileManager)
+ {
+ _fileStreamFactory = fileStreamFactory;
+ _fileManager = fileManager;
+ }
+
+ ///
+ /// 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.
+ /// if the file was parsed successfully; otherwise, .
+ public bool ParseFile(string path)
+ {
+ if (!_fileStreamFactory.FileSystem.File.Exists(path))
+ {
+ _logger.LogError("File not found: {Path}", path);
+ return false;
+ }
+
+ using var fileStream = _fileStreamFactory.FileSystem.FileStream.New(path, FileMode.Open, FileAccess.Read);
+ using var lineStreamReader = _fileManager.StreamReader(path);
+
+ while (!lineStreamReader.EndOfStream)
+ {
+ var currentLine = lineStreamReader.ReadLine();
+
+ if (!string.IsNullOrWhiteSpace(currentLine))
+ {
+ if (!ParseLine(currentLine))
+ {
+ _logger.LogError("Failed to parse line: {Line}", currentLine);
+ // return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private bool ParseLine(string currentLine)
+ {
+ if (currentLine[0] == '#')
+ return true;
+
+ var fields = currentLine.Trim().Split(null, 2);
+ var keyword = fields[0].Trim();
+ var data = fields[1].Trim();
+
+ return 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.
+ /// if the line was parsed successfully; otherwise, .
+ protected abstract bool 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..b45b5a0
--- /dev/null
+++ b/ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs
@@ -0,0 +1,114 @@
+using SharpEngine.Core.Components.ObjLoader.DataStore;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Core.ObjLoader.Loader.Loaders;
+using SharpEngine.Shared.Extensions;
+
+using System;
+using System.Collections.Generic;
+using System.IO.Abstractions;
+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 IMaterialDataStore _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.
+ /// Represents a factory for creating file streams.
+ /// Represents a manager for handling file operations.
+ public MaterialLibraryLoader(IMaterialDataStore dataStore, IFileStreamFactory fileStreamFactory, IFileManager fileManager) : base(fileStreamFactory, fileManager)
+ {
+ _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 = new TextureDto(m));
+ AddParseAction("map_Kd", m => _currentMaterial.DiffuseMap = new TextureDto(m));
+
+ AddParseAction("map_Ks", m => _currentMaterial.SpecularMap = new TextureDto(m));
+ AddParseAction("map_Ns", m => _currentMaterial.SpecularHighlightTextureMap = new TextureDto(m));
+
+ AddParseAction("map_d", m => _currentMaterial.AlphaTextureMap = new TextureDto(m));
+
+ AddParseAction("map_bump", m => _currentMaterial.BumpMap = new TextureDto(m));
+ AddParseAction("bump", m => _currentMaterial.BumpMap = new TextureDto(m));
+
+ AddParseAction("disp", m => _currentMaterial.DisplacementMap = new TextureDto(m));
+
+ AddParseAction("decal", m => _currentMaterial.StencilDecalMap = new TextureDto(m));
+ }
+
+ private void AddParseAction(string key, Action action)
+ => _parseActionDictionary.Add(key.ToLowerInvariant(), action);
+
+ ///
+ protected override bool ParseLine(string keyword, string data)
+ {
+ var parseAction = GetKeywordAction(keyword, out bool found);
+
+ if (!found)
+ {
+ _unrecognizedLines.Add(keyword + " " + data);
+ return false;
+ }
+
+ parseAction!(data);
+ return true;
+ }
+
+ 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.AddMaterial(_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/ObjLoader/Loaders/ObjLoader/FileManager.cs b/ObjLoader/Loaders/ObjLoader/FileManager.cs
new file mode 100644
index 0000000..0e44ce3
--- /dev/null
+++ b/ObjLoader/Loaders/ObjLoader/FileManager.cs
@@ -0,0 +1,15 @@
+using SharpEngine.Core.ObjLoader.Loader.Loaders;
+using System.IO;
+
+namespace SharpEngine.Core.ObjLoader.Loaders.ObjLoader
+{
+ ///
+ /// Represents a testable wrapper for creating stream file related objects.
+ ///
+ public class FileManager : IFileManager
+ {
+ ///
+ public StreamReader StreamReader(string path)
+ => new(path);
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/Loaders/ObjLoader/ObjLoader.cs b/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
new file mode 100644
index 0000000..fbb9b0f
--- /dev/null
+++ b/ObjLoader/Loaders/ObjLoader/ObjLoader.cs
@@ -0,0 +1,101 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.ObjLoader.Loader.Loaders;
+using SharpEngine.Core.ObjLoader.TypeParsers;
+using SharpEngine.Telemetry;
+using Silk.NET.OpenGL;
+
+using System.Collections.Generic;
+using System.IO.Abstractions;
+using System.Linq;
+
+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;
+ private readonly DataStore _dataStore;
+ private readonly List _typeParsers = [];
+ private readonly List _unrecognizedLines = [];
+
+ private readonly ILogger _logger = LoggingExtensions.CreateLogger();
+
+ ///
+ /// 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.
+ /// Represents a factory for creating file streams.
+ /// Represents a manager for handling file operations.
+ public ObjLoader(string path, DataStore dataStore, IFileStreamFactory fileStreamFactory, IFileManager fileManager) : base(fileStreamFactory, fileManager)
+ {
+ _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)
+ _typeParsers.Add(parser);
+
+ return this;
+ }
+
+ ///
+ protected override bool ParseLine(string keyword, string data)
+ {
+ foreach (var typeParser in _typeParsers)
+ {
+ if (typeParser.CanParse(keyword))
+ {
+ typeParser.Parse(data);
+ return true;
+ }
+ else
+ {
+ _logger.LogDebug("Type parser {Parser} cannot parse keyword: {Keyword}", typeParser.GetType().Name, keyword);
+ }
+ }
+
+ _unrecognizedLines.Add(keyword + " " + data);
+ _logger.LogError("Unrecognized line in OBJ file: {Line}", keyword + " " + data);
+
+ return false;
+ }
+
+ ///
+ /// 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)
+ {
+ if (!ParseFile(_path))
+ {
+ _logger.LogError("Failed to parse OBJ file: {Path}", _path);
+ return [];
+ }
+
+ // TODO: Handle multiple meshes and objects in the OBJ file. Currently, this implementation assumes a single mesh.
+ return
+ [
+ new(gl)
+ {
+ Name = _dataStore.ObjectName,
+ Vertices2 = _dataStore.Vertices,
+ TextureCoordinates2 = _dataStore.Textures,
+ Normals2 = _dataStore.Normals,
+ Groups = _dataStore.Groups,
+ Materials = _dataStore.Materials
+ }
+ ];
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs b/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
new file mode 100644
index 0000000..20b7d20
--- /dev/null
+++ b/ObjLoader/Loaders/ObjLoader/ObjLoaderFactory.cs
@@ -0,0 +1,109 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Core.Components.Properties.Meshes;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.ObjLoader.Loader.TypeParsers;
+using SharpEngine.Core.ObjLoader.Loaders.MaterialLoader;
+using SharpEngine.Core.ObjLoader.TypeParsers;
+using SharpEngine.Telemetry;
+using Silk.NET.OpenGL;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Abstractions;
+
+namespace SharpEngine.Core.ObjLoader.Loaders.ObjLoader
+{
+ ///
+ /// Handles loading 3D models.
+ ///
+ public static class ObjLoaderFactory
+ {
+ private static readonly FileSystem _fileSystem;
+ private static readonly FileManager _fileManager;
+
+ private readonly static ILogger _logger = LoggingExtensions.CreateLogger(typeof(ObjLoaderFactory));
+
+ static ObjLoaderFactory()
+ {
+ _fileSystem = new FileSystem();
+ _fileManager = new FileManager();
+ }
+
+ const string FbxExtension = ".fbx";
+ const string ObjExtension = ".obj";
+
+ ///
+ /// Loads a mesh based on the file extension of the provided path.
+ ///
+ /// The OpenGL context where the model should be bound.
+ /// Specifies the file path of the mesh to be loaded, which determines the loading method based on its extension.
+ /// The model loaded from the file.
+ /// The model loaded from the file.
+ /// Thrown when the file extension of the provided path is not recognized as a supported mesh format.
+ /// Thrown when no meshes are loaded.
+ public static bool Load(GL gl, string path, out Model? model)
+ {
+ try
+ {
+ string fileExtension = Path.GetExtension(path);
+ model = fileExtension switch
+ {
+ FbxExtension => LoadFbx("", path),
+ ObjExtension => LoadObj(gl, path),
+ _ => throw new NotSupportedException($"The file extension {fileExtension} is not a supported mesh file.")
+ };
+
+ _logger.LogDebug("Successfully loaded model '{name}' from path: {path}.", model.Name, path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to load model from path: {path}.", path);
+
+ model = null;
+ return false;
+ }
+ }
+
+ // TODO: #3 Load fbx mesh from file
+ private static Model LoadFbx(string identifier, string meshFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ private static Model LoadObj(GL gl, string path)
+ {
+ var meshes = LoadObjMeshes(gl, path);
+
+ if (meshes.Count == 0)
+ throw new InvalidOperationException("The .obj file contains no meshes.");
+
+ return new Model(gl, path, meshes);
+ }
+
+ private static List LoadObjMeshes(GL gl, string path)
+ {
+ var dataStore = new DataStore();
+
+ var faceParser = new FaceParser(dataStore);
+ var groupParser = new GroupParser(dataStore);
+ var normalParser = new NormalParser(dataStore);
+ var textureParser = new TextureParser(dataStore);
+ var vertexParser = new VertexParser(dataStore);
+ var smoothingGroupParser = new SmoothingGroupParser(dataStore);
+ var objectNameParser = new ObjectNameParser(dataStore);
+
+ var materialLibraryLoader = new MaterialLibraryLoader(dataStore, _fileSystem.FileStream, _fileManager);
+
+ var materialLoader = new MaterialLibraryLoaderFacade(materialLibraryLoader, path);
+ var materialLibraryParser = new MaterialLibraryParser(materialLoader);
+ var useMaterialParser = new UseMaterialParser(dataStore);
+
+ var loader = new ObjLoader(path, dataStore, _fileSystem.FileStream, _fileManager)
+ .SetupTypeParsers(faceParser, groupParser, normalParser, textureParser, vertexParser, materialLibraryParser, useMaterialParser, smoothingGroupParser, objectNameParser);
+
+ 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 52%
rename from Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj
rename to ObjLoader/SharpEngine.Core.ObjLoader.csproj
index 7cc24cf..04ffde4 100644
--- a/Engine/ObjLoader/SharpEngine.Core.ObjLoader.csproj
+++ b/ObjLoader/SharpEngine.Core.ObjLoader.csproj
@@ -1,16 +1,17 @@
-
+
- net8.0
+ $(DotNetTargetFramework)
-
+
+
-
+
\ No newline at end of file
diff --git a/Engine/ObjLoader/TypeParsers/FaceParser.cs b/ObjLoader/TypeParsers/FaceParser.cs
similarity index 55%
rename from Engine/ObjLoader/TypeParsers/FaceParser.cs
rename to ObjLoader/TypeParsers/FaceParser.cs
index c7328f7..83581d5 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;
@@ -19,7 +27,7 @@ public FaceParser(IFaceGroup faceGroup)
protected override string Keyword => "f";
///
- public override void Parse(string line)
+ public override bool Parse(string line)
{
string[] vertices = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
@@ -27,19 +35,22 @@ public override void Parse(string line)
foreach (string vertexString in vertices)
{
- var faceVertex = ParseFaceVertex(vertexString);
- face.AddVertex(faceVertex);
+ if (TryParseFaceVertex(vertexString, out FaceVertex faceVertex))
+ {
+ face.AddVertex(faceVertex);
+ }
}
_faceGroup.AddFace(face);
+ return true;
}
- private static FaceVertex ParseFaceVertex(string vertexString)
+ private static bool TryParseFaceVertex(string vertexString, out FaceVertex faceVertex)
{
string[] fields = vertexString.Split('/', StringSplitOptions.None);
int vertexIndex = fields[0].ParseInvariantInt();
- var faceVertex = new FaceVertex(vertexIndex, 0, 0);
+ faceVertex = new FaceVertex(vertexIndex, 0, 0);
if (fields.Length > 1)
{
@@ -53,7 +64,7 @@ private static FaceVertex ParseFaceVertex(string vertexString)
faceVertex.NormalIndex = normalIndex;
}
- return faceVertex;
+ return true;
}
}
}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/GroupParser.cs b/ObjLoader/TypeParsers/GroupParser.cs
new file mode 100644
index 0000000..0934906
--- /dev/null
+++ b/ObjLoader/TypeParsers/GroupParser.cs
@@ -0,0 +1,32 @@
+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 bool Parse(string line)
+ {
+ _dataStore.PushGroup(line);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/ITypeParser.cs b/ObjLoader/TypeParsers/ITypeParser.cs
new file mode 100644
index 0000000..3fd685b
--- /dev/null
+++ b/ObjLoader/TypeParsers/ITypeParser.cs
@@ -0,0 +1,22 @@
+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.
+ /// if the keyword can be parsed; otherwise, .
+ bool CanParse(string keyword);
+
+ ///
+ /// Parses a single input line and updates the object's state accordingly.
+ ///
+ /// The input line to parse.
+ /// if the line was successfully parsed; otherwise, .
+ bool 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..7be0889
--- /dev/null
+++ b/ObjLoader/TypeParsers/MaterialLibraryParser.cs
@@ -0,0 +1,32 @@
+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 bool Parse(string line)
+ {
+ _libraryLoaderFacade.Load(line);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/NormalParser.cs b/ObjLoader/TypeParsers/NormalParser.cs
new file mode 100644
index 0000000..111800b
--- /dev/null
+++ b/ObjLoader/TypeParsers/NormalParser.cs
@@ -0,0 +1,42 @@
+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 bool 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);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/ObjectNameParser.cs b/ObjLoader/TypeParsers/ObjectNameParser.cs
new file mode 100644
index 0000000..13d0d9f
--- /dev/null
+++ b/ObjLoader/TypeParsers/ObjectNameParser.cs
@@ -0,0 +1,33 @@
+using SharpEngine.Core.ObjLoader.Loader.TypeParsers;
+using System;
+
+namespace SharpEngine.Core.ObjLoader.TypeParsers;
+
+///
+/// Parses object name lines `o object_name` and handles the object name accordingly.
+///
+public class ObjectNameParser : TypeParserBase, ITypeParser
+{
+ private IObjectNameDataStore _dataStore;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The data store to store the parsed object name.
+ public ObjectNameParser(IObjectNameDataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "o";
+
+ ///
+ /// Thrown when the line format is invalid.
+ public override bool Parse(string data)
+ {
+ _dataStore.SetObjectName(data);
+
+ return true;
+ }
+}
diff --git a/ObjLoader/TypeParsers/SmoothingGroupParser.cs b/ObjLoader/TypeParsers/SmoothingGroupParser.cs
new file mode 100644
index 0000000..693bcea
--- /dev/null
+++ b/ObjLoader/TypeParsers/SmoothingGroupParser.cs
@@ -0,0 +1,45 @@
+using SharpEngine.Core.ObjLoader.Loader.TypeParsers;
+using System;
+
+namespace SharpEngine.Core.ObjLoader.TypeParsers;
+
+///
+/// Parses smoothing group definitions ("s") and populates the current group's smoothing group information.
+///
+public class SmoothingGroupParser : TypeParserBase, ITypeParser
+{
+ private ISmoothingGroupDataStore _dataStore;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The data store to store the parsed smoothing group information.
+ public SmoothingGroupParser(ISmoothingGroupDataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "s";
+
+ ///
+ ///
+ /// The line is expected to be in the format "group_number" or "off" so the s at the beginning is not included in the data string passed to this method.
+ ///
+ /// Thrown when the line format is invalid or the smoothing group number is not a valid integer.
+ public override bool Parse(string data)
+ {
+ if (data.Equals("off", StringComparison.OrdinalIgnoreCase))
+ {
+ _dataStore.SetSmoothingGroupOff();
+ return true;
+ }
+
+ // Parse the smoothing group number
+ if (!int.TryParse(data, out int groupNumber))
+ throw new FormatException($"Invalid smoothing group identifier: {data}");
+
+ _dataStore.SetSmoothingGroup(groupNumber);
+ return true;
+ }
+}
diff --git a/ObjLoader/TypeParsers/TextureParser.cs b/ObjLoader/TypeParsers/TextureParser.cs
new file mode 100644
index 0000000..df5a0e3
--- /dev/null
+++ b/ObjLoader/TypeParsers/TextureParser.cs
@@ -0,0 +1,41 @@
+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 bool 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);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/TypeParserBase.cs b/ObjLoader/TypeParsers/TypeParserBase.cs
new file mode 100644
index 0000000..30ac334
--- /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 bool 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..76d6f5d
--- /dev/null
+++ b/ObjLoader/TypeParsers/UseMaterialParser.cs
@@ -0,0 +1,31 @@
+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 bool Parse(string line)
+ {
+ _dataStore.SetMaterial(line);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ObjLoader/TypeParsers/VertexParser.cs b/ObjLoader/TypeParsers/VertexParser.cs
new file mode 100644
index 0000000..fadb733
--- /dev/null
+++ b/ObjLoader/TypeParsers/VertexParser.cs
@@ -0,0 +1,48 @@
+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 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 IVertexDataStore _dataStore;
+
+ ///
+ /// Initializes a new instance of with the specified data store to populate.
+ ///
+ /// The data store to populate.
+ public VertexParser(IVertexDataStore dataStore)
+ {
+ _dataStore = dataStore;
+ }
+
+ ///
+ protected override string Keyword => "v";
+
+ ///
+ public override bool Parse(string line)
+ {
+ string[] parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
+
+ float x = parts[0].ParseInvariantFloat();
+ float y = parts[1].ParseInvariantFloat();
+ float z = parts[2].ParseInvariantFloat();
+
+ var vertex = new Vertex()
+ {
+ Position = new System.Numerics.Vector3(x, y, z),
+ };
+
+ _dataStore.AddVertex(vertex);
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 0d657e1..aae9d34 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@


-->
+
+[Documentation](https://docs.sharpengine.net/)
+
## About
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 64%
rename from Engine/SharpEngine.Core.Components/Properties/Material.cs
rename to SharpEngine.Core.Components/Properties/Material.cs
index 07c21be..3028aa2 100644
--- a/Engine/SharpEngine.Core.Components/Properties/Material.cs
+++ b/SharpEngine.Core.Components/Properties/Material.cs
@@ -5,6 +5,27 @@
namespace SharpEngine.Core.Components.Properties;
+public record TextureDto
+{
+ public TextureDto(string path, Texture? texture = null)
+ {
+ Path = path;
+ Texture = texture;
+ }
+
+ public string Path { get; set; }
+ public Texture? Texture { get; set; }
+}
+
+public enum TextureUnitIndex : int
+{
+ /// The texture unit for the diffuse map.
+ DIFFUSE_UNIT = 0,
+
+ /// The texture unit for the specular map.
+ SPECULAR_UNIT = 1
+}
+
///
/// Represents the material rendered onto a game object.
///
@@ -13,49 +34,39 @@ public class Material
///
/// Initializes a new instance of .
///
+ /// The name assigned to the new material.
+ public Material(string materialName)
+ : this(materialName, diffuseMap: null, specularMap: null) { }
+
+ ///
+ /// 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)
{
- DiffuseMap = diffuseMap;
- SpecularMap = specularMap ?? diffuseMap;
+ DiffuseMap = new TextureDto(diffuseMap?.Path ?? string.Empty, diffuseMap);
+ SpecularMap = new TextureDto(specularMap?.Path ?? string.Empty, specularMap);
Specular = new(0.5f, 0.5f, 0.5f);
Shininess = 32.0f;
- }
- ///
- /// Initializes a new instance of .
- ///
- /// The name assigned to the new material.
- public Material(string materialName)
- {
Name = materialName;
}
+ // Resolve dependency issue with GL to fix this.
+ // public Shader Shader { get; init; }
+ public Shader Shader { get; set; }
+
/// Gets or sets the name of the material.
public string Name { get; set; }
/// Gets or sets the diffuse map texture.
- public Texture DiffuseMap { get; set; }
-
- /// Gets or sets the path to the diffuse texture map.
- public string DiffuseTextureMap { get; set; }
+ public TextureDto? DiffuseMap { get; set; }
/// Gets or sets the specular map texture.
- public Texture SpecularMap { get; set; }
-
- /// Gets or sets the path to the specular texture map.
- public string SpecularTextureMap { get; set; }
-
- /// Gets a value indicating whether the material uses a specular map.
- public bool UseSpecularMap => SpecularMap.Handle != DiffuseMap.Handle;
-
- /// The texture unit for the diffuse map.
- public const int DIFFUSE_UNIT = 0;
-
- /// The texture unit for the specular map.
- public const int SPECULAR_UNIT = 1;
+ public TextureDto? SpecularMap { get; set; }
/// Gets or sets the specular color of the material.
public Vector3 Specular { get; set; }
@@ -82,22 +93,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 TextureDto? AmbientTextureMap { get; set; }
/// Gets or sets the path to the specular highlight texture map.
- public string SpecularHighlightTextureMap { get; set; }
+ public TextureDto? SpecularHighlightTextureMap { get; set; }
/// Gets or sets the path to the bump map.
- public string BumpMap { get; set; }
+ public TextureDto? BumpMap { get; set; }
/// Gets or sets the path to the displacement map.
- public string DisplacementMap { get; set; }
+ public TextureDto? DisplacementMap { get; set; }
/// Gets or sets the path to the stencil decal map.
- public string StencilDecalMap { get; set; }
+ public TextureDto? StencilDecalMap { get; set; }
/// Gets or sets the path to the alpha texture map.
- public string AlphaTextureMap { get; set; }
+ public TextureDto? AlphaTextureMap { get; set; }
///
/// Sets the uniform values for the material in the specified shader.
@@ -107,21 +118,21 @@ public void SetUniformValues(Shader shader)
{
// TODO: Get all shader uniforms and set their values automatically
- DiffuseMap.Use(TextureUnit.Texture0);
- shader.SetInt("material.diffuse", DIFFUSE_UNIT);
-
- if (UseSpecularMap)
+ if (DiffuseMap is not null)
{
- SpecularMap.Use(TextureUnit.Texture1);
- shader.SetInt("material.specular", SPECULAR_UNIT);
- shader.SetVector3("material.specular", Specular);
- shader.SetFloat("material.shininess", Shininess);
+ DiffuseMap.Texture?.Use(TextureUnit.Texture0);
+ shader.SetTextureUnit("material.diffuse", TextureUnitIndex.DIFFUSE_UNIT);
}
- else
+
+ if (SpecularMap is not null)
{
- shader.SetInt("material.specular", 0);
- shader.SetVector3("material.specular", Vector3.Zero);
- shader.SetFloat("material.shininess", 0);
+ SpecularMap.Texture?.Use(TextureUnit.Texture1);
+ shader.SetTextureUnit("material.specular", TextureUnitIndex.SPECULAR_UNIT);
}
+ else
+ shader.SetTextureUnit("material.specular", TextureUnitIndex.DIFFUSE_UNIT);
+
+ shader.SetFloat("material.shininess", Shininess);
+
}
}
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 75%
rename from Engine/SharpEngine.Core.Components/Properties/Meshes/Mesh.cs
rename to SharpEngine.Core.Components/Properties/Meshes/Mesh.cs
index e6f66fc..2d5a8c5 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.
@@ -133,6 +131,20 @@ public Mesh(GL gl, float[] vertices, uint[] indices, List textures)
SetupMesh();
}
+ ///
+ /// Initializes a new instance of the class with the specified OpenGL context, interleaved vertices, and indices.
+ ///
+ /// The OpenGL context.
+ /// The interleaved vertex data (position, normal, uv per vertex).
+ /// The indices of the mesh.
+ public Mesh(GL gl, float[] vertices, uint[] indices)
+ {
+ GL = gl;
+ Vertices = vertices;
+ Indices = indices;
+ SetupMesh();
+ }
+
///
/// Initializes a new instance of the class with the specified OpenGL context.
///
@@ -143,9 +155,25 @@ public Mesh(GL gl)
SetupMesh();
}
+ ///
+ /// Disposes the existing GPU buffers and reinitializes them using the current and data.
+ ///
+ ///
+ /// Call this method after updating or on a mesh that was already
+ /// set up, for example after in-place processing via .
+ ///
+ public void ReinitializeGpuBuffers()
+ {
+ VAO.Dispose();
+ VBO.Dispose();
+ EBO.Dispose();
+ SetupMesh();
+ }
+
///
/// Allocates the required memory for the mesh and sets up the Vertex Array Object (VAO), Vertex Buffer Object (VBO), and Element Buffer Object (EBO).
///
+ [MemberNotNull(nameof(EBO), nameof(VBO), nameof(VAO))]
public void SetupMesh()
{
EBO = new BufferObject(GL, Indices, BufferTargetARB.ElementArrayBuffer);
@@ -167,6 +195,14 @@ public void SetupMesh()
public void Bind()
=> VAO.Bind();
+ public void Draw()
+ {
+ if (Indices.Length > 0)
+ GL.DrawElements(PrimitiveType.Triangles, (uint)Indices.Length, DrawElementsType.UnsignedInt, []);
+ else
+ GL.DrawArrays(PrimitiveType.Triangles, 0, (uint)(Vertices.Length / (VertexData.VerticesSize + VertexData.NormalsSize + VertexData.TexCoordsSize)));
+ }
+
///
protected virtual void Dispose(bool disposing)
{
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..2947ec0
--- /dev/null
+++ b/SharpEngine.Core.Components/Properties/Meshes/Model.cs
@@ -0,0 +1,305 @@
+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; } = [];
+
+ public string Name { get; }
+
+ ///
+ /// 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) : this(gl, path, Enumerable.Empty())
+ {
+ _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;
+
+ if (meshes.Any())
+ Meshes = meshes.Select(ProcessMesh).ToList();
+
+ Name = meshes.FirstOrDefault()?.Name ?? System.IO.Path.GetFileNameWithoutExtension(path) ?? "UnnamedModel";
+ }
+
+ ///
+ public void Dispose()
+ {
+ foreach (var mesh in Meshes)
+ mesh.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Processes the provided mesh in place, updating its vertex data, materials, textures, and GPU buffers so it is ready for rendering.
+ ///
+ /// The mesh to process. Must not be null.
+ ///
+ /// The same instance with updated , ,
+ /// , , and reinitialized GPU buffers.
+ /// When the mesh already carries GPU-ready vertex data ( is non-empty) its
+ /// materials and textures are refreshed in place; otherwise vertices are expanded from face groups and all
+ /// data properties are computed and assigned.
+ ///
+ /// Thrown if is null.
+ public Mesh ProcessMesh(Mesh mesh)
+ {
+ ArgumentNullException.ThrowIfNull(mesh);
+
+ if (mesh.Vertices.Length > 0)
+ {
+ var existingMaterials = mesh.Materials.Select(CloneMaterial).ToList();
+ var existingTextures = (existingMaterials.Count > 0 ? BuildTextures(existingMaterials) : CloneTextures(mesh.Textures)).ToList();
+ mesh.Materials = existingMaterials;
+ mesh.Textures = existingTextures;
+ mesh.ReinitializeGpuBuffers();
+ return 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();
+
+ mesh.Vertices = BuildVertices(vertices);
+ mesh.Indices = indices;
+ mesh.Textures = textures;
+ mesh.Vertices2 = vertices;
+ mesh.Materials = materials;
+ mesh.ReinitializeGpuBuffers();
+ return mesh;
+ }
+
+ 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.DiffuseMap?.Path))
+ return null;
+
+ var diffuseTexture = new EngineTexture(_gl, ResolveAssetPath(definition.DiffuseMap.Path), EngineTextureType.Diffuse);
+ EngineTexture? specularTexture = null;
+
+ if (!string.IsNullOrWhiteSpace(definition.SpecularMap?.Path))
+ specularTexture = new EngineTexture(_gl, ResolveAssetPath(definition.SpecularMap.Path), 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,
+ DiffuseMap = definition.DiffuseMap,
+ SpecularMap = definition.SpecularMap,
+ 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?.Texture is not null)
+ diffuseTexture = new EngineTexture(_gl, material.DiffuseMap.Path, material.DiffuseMap.Texture.Type);
+
+ EngineTexture? specularTexture = null;
+ if (material.SpecularMap?.Texture is not null)
+ specularTexture = new EngineTexture(_gl, material.SpecularMap.Path, material.SpecularMap.Texture.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,
+ DiffuseMap = material.DiffuseMap,
+ SpecularMap = material.SpecularMap,
+ 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.SpecularMap?.Texture is not null)
+ materials.Add(material.SpecularMap.Texture!);
+
+ if (material.DiffuseMap?.Texture is not null)
+ materials.Add(material.DiffuseMap.Texture!);
+
+ 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);
+ }
+
+ ///
+ /// Converts a collection of objects into an interleaved float array
+ /// (position XYZ, normal XYZ, texture-coordinate UV per vertex) suitable for uploading to a GPU vertex buffer.
+ ///
+ /// The vertices to convert.
+ /// A flat array with eight elements per vertex.
+ internal 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/SharpEngine.Core.Components/Properties/Shaders/IShader.cs b/SharpEngine.Core.Components/Properties/Shaders/IShader.cs
new file mode 100644
index 0000000..3c34378
--- /dev/null
+++ b/SharpEngine.Core.Components/Properties/Shaders/IShader.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.Numerics;
+
+namespace SharpEngine.Core.Shaders;
+
+///
+/// Defines the contract for setting shader uniform parameters and querying their locations.
+///
+public interface IShader
+{
+ /// Gets the uniform name-to-location map cached at shader initialization.
+ IReadOnlyDictionary UniformLocations { get; }
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// if the uniform was found and set; otherwise .
+ bool SetInt(string name, int data);
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// if the uniform was found and set; otherwise .
+ bool SetFloat(string name, float data);
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// Whether to transpose the matrix before uploading. Defaults to .
+ /// if the uniform was found and set; otherwise .
+ bool SetMatrix4(string name, Matrix4x4 data, bool transpose = true);
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// if the uniform was found and set; otherwise .
+ bool SetVector2(string name, Vector2 data);
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// if the uniform was found and set; otherwise .
+ bool SetVector3(string name, Vector3 data);
+
+ /// Sets a uniform on this shader.
+ /// The name of the uniform.
+ /// The value to set.
+ /// if the uniform was found and set; otherwise .
+ bool SetVector4(string name, Vector4 data);
+}
diff --git a/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs b/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
similarity index 60%
rename from Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
rename to SharpEngine.Core.Components/Properties/Shaders/Shader.cs
index 13aea94..8bee87e 100644
--- a/Engine/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
+++ b/SharpEngine.Core.Components/Properties/Shaders/Shader.cs
@@ -1,17 +1,14 @@
-using SharpEngine.Shared;
+using Microsoft.Extensions.Logging;
+using SharpEngine.Telemetry;
using Silk.NET.OpenGL;
-using System.Diagnostics.CodeAnalysis;
namespace SharpEngine.Core.Shaders;
///
/// Represents a shader program.
///
-public partial class Shader : IDisposable
+public partial class Shader : ShaderProgram, IShader, IDisposable
{
- /// Gets the handle to the shader program.
- public uint Handle { get; private set; }
-
/// Gets or sets the identifying name of the shader.
public string Name { get; set; }
@@ -21,10 +18,6 @@ public partial class Shader : IDisposable
/// Gets or sets the path to the fragment shader file.
public string FragPath { get; set; }
- private Dictionary _uniformLocations = [];
- private bool disposedValue;
- private readonly GL _gl;
-
///
/// Initializes a new instance of .
///
@@ -32,14 +25,13 @@ 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)
+ public Shader(GL gl, string vertPath, string fragPath, string name) : base(gl)
{
Name = name;
- _gl = gl;
// There are several different types of shaders, but the only two you need for basic rendering are the vertex and fragment shaders.
// The vertex shader is responsible for moving around vertices, and uploading that data to the fragment shader.
@@ -48,36 +40,14 @@ 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;
- }
-
- ///
- protected virtual void Dispose(bool disposing)
- {
- if (disposedValue)
- return;
-
- if (disposing)
- {
- _gl.DeleteProgram(Handle);
- Handle = 0;
- _uniformLocations.Clear();
- }
-
- disposedValue = true;
- }
- ///
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ Initialize(vertPath, fragPath);
}
}
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/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs b/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
new file mode 100644
index 0000000..eaa5390
--- /dev/null
+++ b/SharpEngine.Core.Components/Properties/Shaders/ShaderExtensions.cs
@@ -0,0 +1,105 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Core.Components.Properties;
+using Silk.NET.OpenGL;
+using System.Numerics;
+using System.Text.RegularExpressions;
+
+namespace SharpEngine.Core.Shaders;
+
+///
+/// Represents a shader program in the engine.
+///
+public partial class Shader
+{
+ // Uniform setters
+ // Uniforms are variables that can be set by user code, instead of reading them from the VBO.
+ // You use VBOs for vertex-related data, and uniforms for almost everything else.
+
+ // Setting a uniform is almost always the exact same, so I'll explain it here once, instead of in every method:
+ // 1. Bind the program you want to set the uniform on
+ // 2. Get a handle to the location of the uniform with GL.GetUniformLocation.
+ // 3. Use the appropriate GL.Uniform* function to set the uniform.
+
+ private bool TrySetUniform(string uniformName, T data, Action setter)
+ {
+ if (!_uniformLocations.TryGetValue(uniformName, out int location))
+ {
+ _logger.LogInformation("Uniform '{UniformName}' not found in shader '{ShaderName}'.", uniformName, Name);
+ return false;
+ }
+
+ // TODO: #95 The GL.UseProgram should be not be called here. Rather the renderer should call it once before rendering.
+ GL.UseProgram(Handle);
+ setter(location, data);
+
+ return true;
+ }
+
+ ///
+ /// Set a uniform int on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetInt(string name, int data)
+ => TrySetUniform(name, data, GL.Uniform1);
+
+ public bool SetTextureUnit(string name, TextureUnitIndex textureUnit)
+ => TrySetUniform(name, (int)textureUnit, GL.Uniform1);
+
+ ///
+ /// Set a uniform float on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetFloat(string name, float data)
+ => TrySetUniform(name, data, GL.Uniform1);
+
+ ///
+ /// Set a uniform Matrix4 on this shader
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ /// Determines whether or not the matrix should be transposed. Defaults to .
+ ///
+ ///
+ /// The matrix is transposed before being sent to the shader unless is set to .
+ ///
+ ///
+ public bool SetMatrix4(string name, Matrix4x4 data, bool transpose = true)
+ => TrySetUniform(name, data, (uniform, d) => GL.UniformMatrix4(uniform, transpose, d.ToSpan()));
+
+ ///
+ /// Set a uniform Vector2 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector2(string name, SharpEngine.Core.Numerics.Vector2 data)
+ => TrySetUniform(name, (Vector2)data, GL.Uniform2);
+
+ ///
+ /// Set a uniform Vector2 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector2(string name, Vector2 data)
+ => TrySetUniform(name, data, GL.Uniform2);
+
+ ///
+ /// Set a uniform Vector3 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector3(string name, Vector3 data)
+ => TrySetUniform(name, data, GL.Uniform3);
+
+ public bool SetVector3(string name, SharpEngine.Core.Numerics.Vector3 data)
+ => TrySetUniform(name, (Vector3)data, GL.Uniform3);
+
+ ///
+ /// Set a uniform Vector3 on this shader.
+ ///
+ /// The name of the uniform.
+ /// The data to set.
+ public bool SetVector4(string name, Vector4 data)
+ => TrySetUniform(name, data, GL.Uniform4);
+}
diff --git a/SharpEngine.Core.Components/Properties/Shaders/ShaderProgram.cs b/SharpEngine.Core.Components/Properties/Shaders/ShaderProgram.cs
new file mode 100644
index 0000000..2249dee
--- /dev/null
+++ b/SharpEngine.Core.Components/Properties/Shaders/ShaderProgram.cs
@@ -0,0 +1,238 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Telemetry;
+using Silk.NET.OpenGL;
+using System.Text.RegularExpressions;
+
+namespace SharpEngine.Core.Shaders;
+
+public abstract class ShaderProgram : IDisposable
+{
+ protected readonly ILogger _logger;
+
+ protected readonly GL GL;
+
+ protected Dictionary _uniformLocations = [];
+ private bool disposedValue;
+
+ /// Gets the handle to the shader program.
+ public uint Handle { get; protected set; }
+
+ /// Gets or sets the vertex array object.
+ public uint Vao { get; set; }
+
+ /// Gets the uniform name-to-location map cached at shader initialization.
+ public IReadOnlyDictionary UniformLocations => _uniformLocations;
+
+ ///
+ /// Sets the attributes for the shader.
+ ///
+ ///
+ /// if the attributes were set successfully; otherwise .
+ ///
+ public virtual bool SetAttributes(GL gl) => true;
+
+ protected ShaderProgram(GL gl)
+ {
+ GL = gl;
+ _logger = LoggingExtensions.CreateLogger();
+ }
+
+ ///
+ /// Initializes and compiles vertex and fragment shaders into a shader program.
+ ///
+ protected void Initialize(string vertPath, string fragPath)
+ {
+ // Load and compile shader
+ if (!LoadShader(ShaderType.VertexShader, vertPath, out uint vertexShader))
+ {
+ _logger.LogError("Unable to load vertex shader.");
+ return;
+ }
+
+ if (!LoadShader(ShaderType.FragmentShader, fragPath, out uint fragmentShader))
+ {
+ _logger.LogError("Unable to load fragment shader.");
+ return;
+ }
+ // These two shaders must then be merged into a shader program, which can then be used by OpenGL.
+ // To do this, create a program...
+
+ Handle = GL.CreateProgram();
+
+ // Attach both shaders...
+ GL.AttachShader(Handle, vertexShader);
+ GL.AttachShader(Handle, fragmentShader);
+
+ // And then link them together.
+ bool shaderLinked = LinkProgram(Handle);
+
+ // When the shader program is linked, it no longer needs the individual shaders attached to it; the compiled code is copied into the shader program.
+ // Detach them, and then delete them.
+ GL.DetachShader(Handle, vertexShader);
+ GL.DetachShader(Handle, fragmentShader);
+ GL.DeleteShader(fragmentShader);
+ GL.DeleteShader(vertexShader);
+
+ if (!shaderLinked)
+ {
+ _logger.LogInformation("Unable to link shader program.");
+ return;
+ }
+
+ // The shader is now ready to go, but first, we're going to cache all the shader uniform locations.
+ // Querying this from the shader is very slow, so we do it once on initialization and reuse those values
+ // later.
+ SetUniformLocations(Handle);
+
+ Vao = GL.GenVertexArray();
+ GL.BindVertexArray(Vao);
+
+ SetAttributes(GL);
+
+ return;
+ }
+
+ private bool LoadShader(ShaderType shaderType, string shaderPath, out uint shaderProgram)
+ {
+ if (!File.Exists(shaderPath))
+ {
+ _logger.LogInformation("Shader file not found: {Path}", shaderPath);
+
+ shaderProgram = 0;
+ return false;
+ }
+
+ string shaderSource = File.ReadAllText(shaderPath);
+ shaderSource = ProcessIncludes(shaderSource, Path.GetDirectoryName(shaderPath)!);
+
+ // GL.CreateShader will create an empty shader (obviously). The ShaderType enum denotes which type of shader will be created.
+ shaderProgram = GL.CreateShader(shaderType);
+ GL.ShaderSource(shaderProgram, shaderSource);
+
+ if (!CompileShader(shaderProgram))
+ {
+ _logger.LogInformation("Unable to load {Type} shader from '{Path}'.", shaderType, shaderPath);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void SetUniformLocations(uint shaderProgramHandle)
+ {
+ // First, we have to get the number of active uniforms in the shader.
+ GL.GetProgram(shaderProgramHandle, GLEnum.ActiveUniforms, out var numberOfUniforms);
+
+ Dictionary uniformLocations = [];
+
+ // Loop over all the uniforms,
+ for (uint i = 0; i < numberOfUniforms; i++)
+ {
+ // get the name of this uniform,
+ var key = GL.GetActiveUniform(shaderProgramHandle, i, out _, out _);
+
+ // get the location,
+ var location = GL.GetUniformLocation(shaderProgramHandle, key);
+
+ // and then add it to the dictionary.
+ uniformLocations.Add(key, location);
+ }
+
+ _uniformLocations = uniformLocations;
+ }
+
+ private static string ProcessIncludes(string shaderCode, string directory)
+ {
+ const string includePattern = @"#include\s+""(.+?)""";
+ return Regex.Replace(shaderCode, includePattern, match =>
+ {
+ string includePath = Path.Combine(directory, match.Groups[1].Value);
+ string includeCode = File.ReadAllText(includePath);
+ return ProcessIncludes(includeCode, Path.GetDirectoryName(includePath)!);
+ }, RegexOptions.NonBacktracking);
+ }
+
+ private bool CompileShader(uint shaderProgram)
+ {
+ // Try to compile the shader
+ GL.CompileShader(shaderProgram);
+
+ // Check for compilation errors
+ GL.GetShader(shaderProgram, GLEnum.CompileStatus, out var statusCode);
+ if (statusCode != (int)GLEnum.True)
+ {
+ // We can use `GL.GetShaderInfoLog(shader)` to get information about the error.
+ var infoLog = GL.GetShaderInfoLog(shaderProgram);
+ _logger.LogError("Error occurred whilst compiling Shader({Shader}).\n\n{Log}", shaderProgram, infoLog);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool LinkProgram(uint program)
+ {
+ GL.LinkProgram(program);
+ GL.GetProgram(program, GLEnum.LinkStatus, out var statusCode);
+
+ if (statusCode != (int)GLEnum.True)
+ {
+ string infoLog = GL.GetProgramInfoLog(program);
+ _logger.LogError("Error occurred whilst linking Program({Program}): {Info}", program, infoLog);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Enables the shader program.
+ ///
+ public void Use()
+ => GL.UseProgram(Handle);
+
+ ///
+ /// Checks if the shader attribute exists within the current shader.
+ ///
+ /// The name of the attribute that's being looked for.
+ /// Outputs the location of the attribute in the shader if found; otherwise -1.
+ /// If the attribute exists, ; otherwise, .
+ public bool TryGetAttribLocation(string attribName, out int location)
+ {
+ location = GL.GetAttribLocation(Handle, attribName);
+ if (location == ShaderAttributes.AttributeLocationNotFound)
+ {
+ _logger.LogWarning("Attribute '{Attribute}' not found in shader program.", attribName);
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposedValue)
+ return;
+
+ if (disposing)
+ {
+ // TODO: Collect handles in a separate container with proper access to the shared GL context.
+ //_gl.DeleteProgram(Handle);
+ Handle = 0;
+ _uniformLocations.Clear();
+ }
+
+ disposedValue = true;
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/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 85%
rename from Engine/SharpEngine.Core.Components/Properties/Transform2D.cs
rename to SharpEngine.Core.Components/Properties/Transform2D.cs
index 8a44d56..0809d3e 100644
--- a/Engine/SharpEngine.Core.Components/Properties/Transform2D.cs
+++ b/SharpEngine.Core.Components/Properties/Transform2D.cs
@@ -19,8 +19,11 @@ public class Transform2D : ITransform
public Vector2 Scale { get; set; } = new(1, 1);
///
- /// Gets or sets the rotation of the game object.
+ /// Gets or sets the quaternion rotation of the game object.
///
+ ///
+ /// To get the rotation angle in degrees, use .
+ ///
public Quaternion Rotation { get; set; } = new() { Angle = 0 };
///
diff --git a/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj b/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
new file mode 100644
index 0000000..99cc5f5
--- /dev/null
+++ b/SharpEngine.Core.Components/SharpEngine.Core.Components.csproj
@@ -0,0 +1,32 @@
+
+
+
+ $(DotNetTargetFramework)
+ enable
+ enable
+
+
+
+ $(GenerateNuGetPackages)
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Parameter1>SharpEngine.Core.Components.Tests
+
+
+
+
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/SharpEngine.Core.Numerics/MathExtensions.cs b/SharpEngine.Core.Numerics/MathExtensions.cs
new file mode 100644
index 0000000..21881ae
--- /dev/null
+++ b/SharpEngine.Core.Numerics/MathExtensions.cs
@@ -0,0 +1,64 @@
+namespace SharpEngine.Core.Numerics;
+
+///
+/// Provides mathematical utility functions.
+///
+public static partial class MathExtensions
+{
+ ///
+ /// Returns the largest integer less than or equal to the specified float value.
+ ///
+ /// The float value.
+ /// The largest integer less than or equal to the specified float value.
+ public static int FastFloor(float value)
+ {
+ int integer = (int)value;
+
+ return value < integer ? integer - 1 : integer;
+ }
+
+ ///
+ /// Smooths the input value.
+ ///
+ /// The input value.
+ /// The smoothed value.
+ public static float Fade(float t)
+ {
+ return t * t * t * (t * (t * 6f - 15f) + 10f);
+ }
+
+ ///
+ /// Performs linear interpolation between two values.
+ ///
+ /// The start value.
+ /// The end value.
+ /// The interpolation factor.
+ /// The interpolated value.
+ public static float Lerp(float a, float b, float t)
+ {
+ return a + t * (b - a);
+ }
+
+ ///
+ /// Computes the gradient for a given hash and coordinates.
+ ///
+ /// The hash value.
+ /// The x coordinate.
+ /// The y coordinate.
+ /// The z coordinate.
+ /// The computed gradient.
+ public static float Grad(int hash, float x, float y, float z)
+ {
+ int h = hash & 15;
+
+ float u = h < 8 ? x : y;
+ float v = h < 4
+ ? y
+ : h is 12 or 14
+ ? x
+ : z;
+
+ return ((h & 1) == 0 ? u : -u) +
+ ((h & 2) == 0 ? v : -v);
+ }
+}
diff --git a/SharpEngine.Core.Numerics/Noise/INoiseGenerator.cs b/SharpEngine.Core.Numerics/Noise/INoiseGenerator.cs
new file mode 100644
index 0000000..d285dc2
--- /dev/null
+++ b/SharpEngine.Core.Numerics/Noise/INoiseGenerator.cs
@@ -0,0 +1,45 @@
+namespace SharpEngine.Core.Numerics.Noise;
+
+///
+/// Defines a noise generator with controllable fractal parameters.
+///
+public interface INoiseGenerator
+{
+ /// Gets or sets the scale factor applied to the input coordinates.
+ ///
+ /// Higher values make the noise appear more zoomed out.
+ ///
+ float Scale { get; set; }
+
+ /// Gets or sets the number of octaves (noise layers) to combine.
+ int Octaves { get; set; }
+
+ ///
+ /// Gets or sets the persistence,
+ /// which controls how much each octave contributes relative to the previous one (amplitude decay per octave).
+ ///
+ float Persistence { get; set; }
+
+ ///
+ /// Gets or sets the lacunarity,
+ /// which controls how quickly the frequency increases per octave.
+ ///
+ float Lacunarity { get; set; }
+
+ ///
+ /// Samples the noise at the given 2D coordinates.
+ ///
+ /// The X coordinate.
+ /// The Z coordinate.
+ /// A normalized value in the range [0, 1].
+ float Sample2D(float x, float z);
+
+ ///
+ /// Samples the noise at the given 3D coordinates.
+ ///
+ /// The X coordinate.
+ /// The Y coordinate.
+ /// The Z coordinate.
+ /// A normalized value in the range [0, 1].
+ float Sample3D(float x, float y, float z);
+}
diff --git a/SharpEngine.Core.Numerics/Noise/PerlinNoiseGenerator.cs b/SharpEngine.Core.Numerics/Noise/PerlinNoiseGenerator.cs
new file mode 100644
index 0000000..a961cd8
--- /dev/null
+++ b/SharpEngine.Core.Numerics/Noise/PerlinNoiseGenerator.cs
@@ -0,0 +1,191 @@
+using MathEx = SharpEngine.Core.Numerics.MathExtensions;
+
+namespace SharpEngine.Core.Numerics.Noise;
+
+///
+/// Deterministic Perlin noise generator with fractal octave support.
+///
+public sealed class PerlinNoiseGenerator : INoiseGenerator
+{
+ private readonly int[] _permutation = new int[512];
+
+ private float _scale = 32f;
+ private int _octaves = 4;
+ private float _persistence = 0.5f;
+ private float _lacunarity = 2f;
+
+ ///
+ /// Creates a new Perlin noise generator.
+ ///
+ /// The seed used to generate the permutation table.
+ public PerlinNoiseGenerator(int seed = 1337)
+ {
+ InitializePermutation(seed);
+ }
+
+ ///
+ public float Scale
+ {
+ get => _scale;
+ set => _scale = value <= 0f ? 0.0001f : value;
+ }
+
+ ///
+ public int Octaves
+ {
+ get => _octaves;
+ set => _octaves = Math.Max(1, value);
+ }
+
+ ///
+ public float Persistence
+ {
+ get => _persistence;
+ set => _persistence = Math.Max(0f, value);
+ }
+
+ ///
+ public float Lacunarity
+ {
+ get => _lacunarity;
+ set => _lacunarity = Math.Max(0.0001f, value);
+ }
+
+ ///
+ public float Sample2D(float x, float z)
+ {
+ float total = 0f;
+ float amplitude = 1f;
+ float frequency = 1f;
+ float maxAmplitude = 0f;
+
+ for (int i = 0; i < Octaves; i++)
+ {
+ float sampleX = x / Scale * frequency;
+ float sampleZ = z / Scale * frequency;
+
+ float noise = Perlin(sampleX, 0f, sampleZ);
+
+ total += noise * amplitude;
+ maxAmplitude += amplitude;
+
+ amplitude *= Persistence;
+ frequency *= Lacunarity;
+ }
+
+ float normalized = total / maxAmplitude;
+
+ return ToUnitRange(normalized);
+ }
+
+ ///
+ public float Sample3D(float x, float y, float z)
+ {
+ float total = 0f;
+ float amplitude = 1f;
+ float frequency = 1f;
+ float maxAmplitude = 0f;
+
+ for (int i = 0; i < Octaves; i++)
+ {
+ float sampleX = x / Scale * frequency;
+ float sampleY = y / Scale * frequency;
+ float sampleZ = z / Scale * frequency;
+
+ float noise = Perlin(sampleX, sampleY, sampleZ);
+
+ total += noise * amplitude;
+ maxAmplitude += amplitude;
+
+ amplitude *= Persistence;
+ frequency *= Lacunarity;
+ }
+
+ float normalized = total / maxAmplitude;
+
+ return ToUnitRange(normalized);
+ }
+
+ private void InitializePermutation(int seed)
+ {
+ int[] source = new int[256];
+
+ for (int i = 0; i < source.Length; i++)
+ source[i] = i;
+
+ Random random = new(seed);
+
+ for (int i = source.Length - 1; i > 0; i--)
+ {
+ int swapIndex = random.Next(i + 1);
+
+ (source[i], source[swapIndex]) = (source[swapIndex], source[i]);
+ }
+
+ for (int i = 0; i < 512; i++)
+ _permutation[i] = source[i & 255];
+ }
+
+ private float Perlin(float x, float y, float z)
+ {
+ int xi = MathEx.FastFloor(x) & 255;
+ int yi = MathEx.FastFloor(y) & 255;
+ int zi = MathEx.FastFloor(z) & 255;
+
+ float xf = x - MathEx.FastFloor(x);
+ float yf = y - MathEx.FastFloor(y);
+ float zf = z - MathEx.FastFloor(z);
+
+ float u = MathEx.Fade(xf);
+ float v = MathEx.Fade(yf);
+ float w = MathEx.Fade(zf);
+
+ var yiOffset = _permutation[xi] + yi;
+
+ int aaa = _permutation[_permutation[yiOffset] + zi];
+ int aba = _permutation[_permutation[yiOffset + 1] + zi];
+ int aab = _permutation[_permutation[yiOffset] + zi + 1];
+ int abb = _permutation[_permutation[yiOffset + 1] + zi + 1];
+
+ var yiOffsetNext = _permutation[xi + 1] + yi;
+
+ int baa = _permutation[_permutation[yiOffsetNext] + zi];
+ int bba = _permutation[_permutation[yiOffsetNext + 1] + zi];
+ int bab = _permutation[_permutation[yiOffsetNext] + zi + 1];
+ int bbb = _permutation[_permutation[yiOffsetNext + 1] + zi + 1];
+
+ float x1 = MathEx.Lerp(
+ MathEx.Grad(aaa, xf, yf, zf),
+ MathEx.Grad(baa, xf - 1f, yf, zf),
+ u);
+
+ float x2 = MathEx.Lerp(
+ MathEx.Grad(aba, xf, yf - 1f, zf),
+ MathEx.Grad(bba, xf - 1f, yf - 1f, zf),
+ u);
+
+ float y1 = MathEx.Lerp(x1, x2, v);
+
+ x1 = MathEx.Lerp(
+ MathEx.Grad(aab, xf, yf, zf - 1f),
+ MathEx.Grad(bab, xf - 1f, yf, zf - 1f),
+ u);
+
+ x2 = MathEx.Lerp(
+ MathEx.Grad(abb, xf, yf - 1f, zf - 1f),
+ MathEx.Grad(bbb, xf - 1f, yf - 1f, zf - 1f),
+ u);
+
+ float y2 = MathEx.Lerp(x1, x2, v);
+
+ return MathEx.Lerp(y1, y2, w);
+ }
+
+ private static float ToUnitRange(float value)
+ {
+ // Perlin is roughly in [-1, 1], so remap to [0, 1].
+ float result = value * 0.5f + 0.5f;
+
+ return Math.Clamp(result, 0f, 1f);
+ }
+}
\ No newline at end of file
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/SharpEngine.Core/Abstractions/DefaultSettings.cs b/SharpEngine.Core/Abstractions/DefaultSettings.cs
new file mode 100644
index 0000000..9347b4e
--- /dev/null
+++ b/SharpEngine.Core/Abstractions/DefaultSettings.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.Logging;
+using SharpEngine.Core.Renderers;
+using Silk.NET.Input;
+
+namespace SharpEngine.Core.Interfaces;
+
+///
+public class DefaultSettings : ISettings
+{
+ ///
+ public bool UseWireFrame { get; set; }
+
+ ///
+ public bool PrintFrameRate { get; set; }
+
+ ///
+ public RenderFlags RendererFlags { get; set; } = RenderFlags.All;
+
+ ///
+ public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
+
+ ///
+ public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
+
+ ///
+ public LogLevel LogLevel { get; set; } = LogLevel.Information;
+}
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..73662b9 100644
--- a/Engine/SharpEngine.Core/Abstractions/IGame.cs
+++ b/SharpEngine.Core/Abstractions/IGame.cs
@@ -1,6 +1,8 @@
-using SharpEngine.Core.Entities.Views;
+using SharpEngine.Core.Entities.Views;
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Enums;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Scenes;
using Silk.NET.Input;
@@ -10,8 +12,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 Vector3(0), new DefaultViewSettings());
+ }
+
+ ///
+ /// Initializes a new instance of the .
+ ///
+ ///
+ /// Sets up a default camera.
+ ///
+ public Game(Scene scene)
+ {
+ Scene = scene;
+ Camera = new(new 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 +55,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 61%
rename from Engine/SharpEngine.Core/Abstractions/ISettings.cs
rename to SharpEngine.Core/Abstractions/ISettings.cs
index 23888d2..cc8fba4 100644
--- a/Engine/SharpEngine.Core/Abstractions/ISettings.cs
+++ b/SharpEngine.Core/Abstractions/ISettings.cs
@@ -1,6 +1,6 @@
-using SharpEngine.Core.Renderers;
+using Microsoft.Extensions.Logging;
+using SharpEngine.Core.Renderers;
using Silk.NET.Input;
-using Silk.NET.Windowing;
namespace SharpEngine.Core.Interfaces;
@@ -27,23 +27,9 @@ public interface ISettings
/// Gets or sets the secondary mouse button.
public MouseButton SecondaryButton { get; set; }
-}
-
-///
-public class DefaultSettings : ISettings
-{
- ///
- public bool UseWireFrame { get; set; }
-
- ///
- public bool PrintFrameRate { get; set; }
-
- ///
- public RenderFlags RendererFlags { get; set; } = RenderFlags.All;
-
- ///
- public MouseButton PrimaryButton { get; set; } = MouseButton.Left;
- ///
- public MouseButton SecondaryButton { get; set; } = MouseButton.Right;
+ ///
+ /// Gets or sets the log level for the game engine.
+ ///
+ public LogLevel LogLevel { get; set; }
}
diff --git a/Engine/SharpEngine.Core/Audio/Audio.cs b/SharpEngine.Core/Audio/Audio.cs
similarity index 51%
rename from Engine/SharpEngine.Core/Audio/Audio.cs
rename to SharpEngine.Core/Audio/Audio.cs
index 7d9d171..7d38c4d 100644
--- a/Engine/SharpEngine.Core/Audio/Audio.cs
+++ b/SharpEngine.Core/Audio/Audio.cs
@@ -8,6 +8,8 @@ namespace SharpEngine.Core.Audio;
///
public static class Audio
{
+ private static AudioPlayerBase? audioPlayer = null;
+
///
/// Plays an audio file if it is in the WAV format.
///
@@ -18,11 +20,34 @@ public static void Play(string filePath)
switch (Path.GetExtension(filePath))
{
case ".wav":
- new WavPlayer().Play(filePath);
+ audioPlayer = new WavPlayer();
+ audioPlayer.Play(filePath);
+ break;
+
+ case ".mp3":
+ audioPlayer = new Mp3Player();
+ audioPlayer.Play(filePath);
break;
default:
throw new NotSupportedException("Unsupported file format.");
}
}
+
+ ///
+ public static void Stop()
+ {
+ audioPlayer?.Stop();
+ audioPlayer = null;
+ }
+
+ ///
+ /// Pauses the currently playing audio, if any.
+ ///
+ public static void Pause() => audioPlayer?.Pause();
+
+ ///
+ /// Resumes the currently paused audio, if any.
+ ///
+ public static void Resume() => audioPlayer?.Resume();
}
diff --git a/Engine/SharpEngine.Core/Audio/AudioBuffer.cs b/SharpEngine.Core/Audio/AudioBuffer.cs
similarity index 93%
rename from Engine/SharpEngine.Core/Audio/AudioBuffer.cs
rename to SharpEngine.Core/Audio/AudioBuffer.cs
index 506f426..6041946 100644
--- a/Engine/SharpEngine.Core/Audio/AudioBuffer.cs
+++ b/SharpEngine.Core/Audio/AudioBuffer.cs
@@ -29,7 +29,7 @@ public AudioBuffer(AL al)
///
/// The data to be loaded into the buffer.
/// Details about the data.
- public void LoadData(ReadOnlySpan data, WavData wavData)
+ public void LoadData(ReadOnlySpan data, AudioData wavData)
{
unsafe
{
diff --git a/Engine/SharpEngine.Core/Audio/WavData.cs b/SharpEngine.Core/Audio/AudioData.cs
similarity index 90%
rename from Engine/SharpEngine.Core/Audio/WavData.cs
rename to SharpEngine.Core/Audio/AudioData.cs
index 80ac937..fa81d42 100644
--- a/Engine/SharpEngine.Core/Audio/WavData.cs
+++ b/SharpEngine.Core/Audio/AudioData.cs
@@ -3,9 +3,9 @@
namespace SharpEngine.Core.Audio;
///
-/// Represents audio data in WAV format.
+/// Represents the data in a audio file.
///
-public class WavData
+public class AudioData
{
/// Gets or sets the number of channels used in the data.
public short NumChannels { get; set; }
diff --git a/Engine/SharpEngine.Core/Audio/AudioDevice.cs b/SharpEngine.Core/Audio/AudioDevice.cs
similarity index 79%
rename from Engine/SharpEngine.Core/Audio/AudioDevice.cs
rename to SharpEngine.Core/Audio/AudioDevice.cs
index aa0560d..d5429bc 100644
--- a/Engine/SharpEngine.Core/Audio/AudioDevice.cs
+++ b/SharpEngine.Core/Audio/AudioDevice.cs
@@ -8,6 +8,14 @@ namespace SharpEngine.Core.Audio;
///
public unsafe class AudioDevice : IDisposable
{
+ // Shared single AudioDevice instance to ensure a single OpenAL context is used
+ private static readonly AudioDevice _shared = new AudioDevice();
+
+ ///
+ /// Gets the shared audio device instance used by the application.
+ ///
+ public static AudioDevice Shared => _shared;
+
/// Gets the OpenAL context.
public AL Al { get; }
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 65%
rename from Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs
rename to SharpEngine.Core/Audio/AudioPlayerBase.cs
index 5778672..8df590a 100644
--- a/Engine/SharpEngine.Core/Audio/AudioPlayerBase.cs
+++ b/SharpEngine.Core/Audio/AudioPlayerBase.cs
@@ -1,4 +1,5 @@
using SharpEngine.Core.Scenes;
+using Silk.NET.OpenAL;
using System;
using System.IO;
@@ -17,7 +18,7 @@ public abstract class AudioPlayerBase : SceneNode
public AudioProperties AudioProperties { get; set; }
/// Contains the audio data of the media file.
- protected readonly WavData Data;
+ protected readonly AudioData Data;
/// Represents a container for the .
protected readonly AudioBuffer AudioBuffer;
@@ -30,9 +31,10 @@ public abstract class AudioPlayerBase : SceneNode
///
protected AudioPlayerBase()
{
- Data = new WavData();
+ Data = new AudioData();
- var audioDevice = new AudioDevice();
+ // Use the shared audio device so multiple players share the same OpenAL context
+ var audioDevice = AudioDevice.Shared;
AudioBuffer = new AudioBuffer(audioDevice.Al);
AudioSource = new AudioSource(audioDevice.Al);
@@ -61,7 +63,27 @@ protected virtual void ValidateFile(string filePath)
}
///
- /// Stops the audio playback of the associated AudioSource.
+ /// Stops the audio playback of the associated .
///
- public virtual void Stop() => AudioSource.Stop();
+ public virtual void Stop()
+ {
+ // Stop playback but do not delete the source here. Deleting the source
+ // in AudioSource.Stop caused subsequent players to generate new sources
+ // while buffers still referenced old sources, leading to overlapping audio.
+ AudioSource.Stop();
+
+ // Detach buffer from source so it can be reused safely
+ var al = AudioDevice.Shared.Al;
+ al.SetSourceProperty(AudioSource.Get(), SourceInteger.Buffer, 0);
+ }
+
+ ///
+ /// Pauses the audio playback of the associated .
+ ///
+ public virtual void Pause() => AudioSource.Pause();
+
+ ///
+ /// Resumes the audio playback of the associated .
+ ///
+ public virtual void Resume() => AudioSource.Resume();
}
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 59%
rename from Engine/SharpEngine.Core/Audio/AudioSource.cs
rename to SharpEngine.Core/Audio/AudioSource.cs
index 9e59179..6c11cd4 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;
///
@@ -42,6 +42,29 @@ public void Play(AudioBuffer buffer)
public void Stop()
{
_al.SourceStop(_source);
+ }
+
+ ///
+ /// Pauses playback of the source.
+ ///
+ public void Pause()
+ {
+ _al.SourcePause(_source);
+ }
+
+ ///
+ /// Resumes playback of the source if it was paused.
+ ///
+ public void Resume()
+ {
+ _al.SourcePlay(_source);
+ }
+
+ ///
+ /// Deletes the underlying OpenAL source. Call when the source will no longer be used.
+ ///
+ public void Dispose()
+ {
_al.DeleteSource(_source);
}
}
\ No newline at end of file
diff --git a/SharpEngine.Core/Audio/Mp3Player.cs b/SharpEngine.Core/Audio/Mp3Player.cs
new file mode 100644
index 0000000..1fe5ff3
--- /dev/null
+++ b/SharpEngine.Core/Audio/Mp3Player.cs
@@ -0,0 +1,74 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using NLayer;
+using Silk.NET.OpenAL;
+
+namespace SharpEngine.Core.Audio;
+
+internal class Mp3Player : AudioPlayerBase
+{
+ private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger();
+
+ ///
+ protected override string FileExtension => AudioFileExtensions.Mp3Extension;
+
+ public Mp3Player()
+ {
+ }
+
+ ///
+ /// Thrown if the audio data contains more than 2 channels.
+ /// Thrown if the file path is invalid.
+ /// Thrown if the file is not found.
+ public override void Play(string filePath)
+ {
+ ValidateFile(filePath);
+
+ using var fs = File.OpenRead(filePath);
+ using var mpeg = new MpegFile(fs);
+
+ int sampleRate = mpeg.SampleRate;
+ int channels = mpeg.Channels;
+
+ // Decode to float samples and convert to 16-bit PCM
+ using var pcmStream = new MemoryStream();
+ var floatBuffer = new float[4096];
+ int samplesRead;
+ while ((samplesRead = mpeg.ReadSamples(floatBuffer, 0, floatBuffer.Length)) > 0)
+ {
+ // samplesRead is number of float samples (interleaved channels)
+ var bytes = new byte[samplesRead * 2];
+ for (int i = 0; i < samplesRead; i++)
+ {
+ float f = floatBuffer[i];
+ // clamp and convert
+ int intVal = (int)MathF.Round(f * 32767f);
+ if (intVal > short.MaxValue) intVal = short.MaxValue;
+ else if (intVal < short.MinValue) intVal = short.MinValue;
+ short s = (short)intVal;
+ bytes[i * 2] = (byte)(s & 0xFF);
+ bytes[i * 2 + 1] = (byte)((s >> 8) & 0xFF);
+ }
+
+ pcmStream.Write(bytes, 0, bytes.Length);
+ }
+
+ var pcmData = pcmStream.ToArray();
+
+ // Fill WavData
+ Data.NumChannels = (short)channels;
+ Data.SampleRate = sampleRate;
+ Data.BitsPerSample = 16;
+ Data.Format = channels switch
+ {
+ 1 => BufferFormat.Mono16,
+ 2 => BufferFormat.Stereo16,
+ _ => throw new InvalidOperationException($"Can't play audio with {channels} channels.")
+ };
+
+ AudioBuffer.LoadData(pcmData, Data);
+ AudioSource.Play(AudioBuffer);
+ }
+}
\ No newline at end of file
diff --git a/Engine/SharpEngine.Core/Audio/WavPlayer.cs b/SharpEngine.Core/Audio/WavPlayer.cs
similarity index 90%
rename from Engine/SharpEngine.Core/Audio/WavPlayer.cs
rename to SharpEngine.Core/Audio/WavPlayer.cs
index a686bea..33401aa 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,8 +12,10 @@ namespace SharpEngine.Core.Audio;
///
public class WavPlayer : AudioPlayerBase
{
+ private static readonly ILogger Logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger();
+
///
- protected override string FileExtension => ".wav";
+ protected override string FileExtension => AudioFileExtensions.WavExtension;
private static class WavConstants
{
@@ -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;
}
@@ -88,7 +90,7 @@ private static bool CheckHeader(ReadOnlySpan file, ref int index, ReadOnly
return true;
}
- private static void ParseFormat(ReadOnlySpan formatChunk, WavData wavData)
+ private static void ParseFormat(ReadOnlySpan formatChunk, AudioData wavData)
{
int index = 0;
var audioFormat = BinaryPrimitives.ReadInt16LittleEndian(formatChunk.Slice(index, WavConstants.AudioFormatLength));
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..99b0a14
--- /dev/null
+++ b/SharpEngine.Core/Entities/GameObject.cs
@@ -0,0 +1,150 @@
+using SharpEngine.Core.Attributes;
+using SharpEngine.Core.Components.Properties;
+using SharpEngine.Core.Components.Properties.Meshes;
+using SharpEngine.Core.Entities.Properties;
+using SharpEngine.Core.Entities.Properties.Meshes;
+using SharpEngine.Core.Entities.UI;
+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 Silk.NET.OpenGL;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EngineTexture = SharpEngine.Core.Components.Properties.Textures.Texture;
+using Shader = SharpEngine.Core.Shaders.Shader;
+
+namespace SharpEngine.Core.Entities;
+
+///
+/// Represents a game object in the scene.
+///
+public class GameObject : EmptyNode, IRenderable
+{
+ // TODO: Remove these two.
+ protected record struct TempShaderDataContainer(string shaderVertPath, string shaderFragPath, string shaderName);
+ protected readonly TempShaderDataContainer _tempShaderData;
+
+ private readonly object _modelCacheLock = new();
+ private readonly Dictionary