Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Examples/Tutorial 4.1 - Model Loading/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
private static void OnLoad()
{
if (window == null)
throw new NullReferenceException("Window is not initialized.");

Check warning on line 69 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN1&open=AZ8PYuI7UniKS_RlXeN1&pullRequest=138

IInputContext input = window.CreateInput();
primaryKeyboard = input.Keyboards[0];
Expand All @@ -85,16 +85,17 @@
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))

Check warning on line 88 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor your code not to use hardcoded absolute paths or URIs.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN2&open=AZ8PYuI7UniKS_RlXeN2&pullRequest=138
{
_models.Add(model!);
_models.Add(model!);
}
}

private static void OnUpdate(double deltaTime)
{
if (primaryKeyboard == null)
throw new NullReferenceException("Primary keyboard is not initialized.");

Check warning on line 98 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN5&open=AZ8PYuI7UniKS_RlXeN5&pullRequest=138

var moveSpeed = 2.5f * (float) deltaTime;

Expand All @@ -118,10 +119,10 @@
private static void OnRender(double deltaTime)
{
if (Gl == null)
throw new NullReferenceException("OpenGL context is not initialized.");

Check warning on line 122 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN3&open=AZ8PYuI7UniKS_RlXeN3&pullRequest=138

if (window == null)
throw new NullReferenceException("Window is not initialized.");

Check warning on line 125 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN4&open=AZ8PYuI7UniKS_RlXeN4&pullRequest=138

Gl.Enable(EnableCap.DepthTest);
Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
Expand Down Expand Up @@ -150,13 +151,13 @@
private static void RenderModel(Model model, Matrix4x4 modelMatrix, Matrix4x4 view, Matrix4x4 projection)
{
if (Texture == null)
throw new NullReferenceException("Texture is not initialized.");

Check warning on line 154 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN6&open=AZ8PYuI7UniKS_RlXeN6&pullRequest=138

if (Shader == null)
throw new NullReferenceException("Shader is not initialized.");

Check warning on line 157 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN7&open=AZ8PYuI7UniKS_RlXeN7&pullRequest=138

if (Gl == null)
throw new NullReferenceException("OpenGL context is not initialized.");

Check warning on line 160 in Examples/Tutorial 4.1 - Model Loading/Program.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuI7UniKS_RlXeN8&open=AZ8PYuI7UniKS_RlXeN8&pullRequest=138

foreach (var mesh in model.Meshes)
{
Expand Down
33 changes: 30 additions & 3 deletions ObjLoader/DataStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,23 @@
namespace SharpEngine.Core.ObjLoader
{
/// <summary>
/// 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.
/// Represents the smoothing group information for a face group.
/// </summary>
/// <param name="Enabled">Determines whether smoothing is enabled for the face group.</param>
/// <param name="GroupId">The ID of the smoothing group, if applicable.</param>
public readonly record struct SmoothingGroup(bool Enabled, int? GroupId);


/// <summary>
/// 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.
/// </summary>
public class DataStore : IGroupDataStore, IFaceGroup, ITextureDataStore, INormalDataStore, IVertexDataStore, IMaterialDataStore
/// <remarks>
/// TODO: This is probably not implemented corretly.

Check warning on line 24 in ObjLoader/DataStore.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this 'TODO' comment.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuIwUniKS_RlXeN0&open=AZ8PYuIwUniKS_RlXeN0&pullRequest=138
/// 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.
/// </remarks>
public class DataStore : IGroupDataStore, IFaceGroup, ITextureDataStore, INormalDataStore, IVertexDataStore, IMaterialDataStore, ISmoothingGroupDataStore, IObjectNameDataStore
{
private Group _currentGroup = null!;

Expand All @@ -35,6 +44,12 @@
/// <summary>Gets the list of groups present in the file.</summary>
public List<Group> Groups { get; } = [];

/// <summary>Gets the list of smoothing groups defined in the file.</summary>
public List<SmoothingGroup> SmoothingGroups { get; } = [];

/// <summary>Gets the name of the object defined in the file, if any.</summary>
public string ObjectName { get; private set; } = string.Empty;

/// <summary>
/// Initializes a new instance of <see cref="DataStore"/> and creates a default group.
/// </summary>
Expand Down Expand Up @@ -83,5 +98,17 @@
/// <inheritdoc />
public void AddMaterial(Material currentMaterial)
=> Materials.Add(currentMaterial);

/// <inheritdoc />
public void SetSmoothingGroup(int groupNumber)
=> SmoothingGroups.Add(new SmoothingGroup(true, groupNumber));

/// <inheritdoc />
public void SetSmoothingGroupOff()
=> SmoothingGroups.Add(new SmoothingGroup(false, null));

/// <inheritdoc />
public void SetObjectName(string name)
=> ObjectName = name;
}
}
13 changes: 13 additions & 0 deletions ObjLoader/IObjectNameDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace SharpEngine.Core.ObjLoader;

/// <summary>
/// Holds the name of the object being parsed, allowing it to be set during parsing and retrieved later.
/// </summary>
public interface IObjectNameDataStore
{
/// <summary>
/// Sets the name of the object being parsed.
/// </summary>
/// <param name="name">The name of the object.</param>
void SetObjectName(string name);
}
18 changes: 18 additions & 0 deletions ObjLoader/ISmoothingGroupDataStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace SharpEngine.Core.ObjLoader;

/// <summary>
/// Represents a data store for smoothing group information in an OBJ file, allowing the storage of smoothing group lines for later processing.
/// </summary>
public interface ISmoothingGroupDataStore
{
/// <summary>
/// Sets the current smoothing group to the specified group number.
/// </summary>
/// <param name="groupNumber">The smoothing group number.</param>
void SetSmoothingGroup(int groupNumber);

/// <summary>
/// Turns off the current smoothing group.
/// </summary>
void SetSmoothingGroupOff();
}
32 changes: 26 additions & 6 deletions ObjLoader/Loaders/LoaderBase.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Extensions.Logging;
using SharpEngine.Telemetry;
using System.IO;
using System.IO.Abstractions;

Expand All @@ -11,6 +13,8 @@
private readonly IFileStreamFactory _fileStreamFactory;
private readonly IFileManager _fileManager;

private readonly ILogger<LoaderBase> _logger = LoggingExtensions.CreateLogger<LoaderBase>();

/// <summary>
/// Initializes a new instance of the <see cref="LoaderBase"/>.
/// </summary>
Expand All @@ -27,8 +31,15 @@
/// I/O exceptions (for example FileNotFoundException, UnauthorizedAccessException, IOException) are propagated to the caller.
/// </remarks>
/// <param name="path">The path of the file to open and parse.</param>
public void ParseFile(string path)
/// <returns><see langword="true"/> if the file was parsed successfully; otherwise, <see langword="false"/>.</returns>
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);

Expand All @@ -37,27 +48,36 @@
var currentLine = lineStreamReader.ReadLine();

if (!string.IsNullOrWhiteSpace(currentLine))
ParseLine(currentLine);
{
if (!ParseLine(currentLine))

Check warning on line 52 in ObjLoader/Loaders/LoaderBase.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this if statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuIlUniKS_RlXeNz&open=AZ8PYuIlUniKS_RlXeNz&pullRequest=138
{
_logger.LogError("Failed to parse line: {Line}", currentLine);
// return false;

Check warning on line 55 in ObjLoader/Loaders/LoaderBase.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this commented out code.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuIlUniKS_RlXeNy&open=AZ8PYuIlUniKS_RlXeNy&pullRequest=138
}
}
}

return true;
}

private void ParseLine(string currentLine)
private bool ParseLine(string currentLine)
{
if (currentLine[0] == '#')
return;
return true;

var fields = currentLine.Trim().Split(null, 2);
var keyword = fields[0].Trim();
var data = fields[1].Trim();

ParseLine(keyword, data);
return ParseLine(keyword, data);
}

/// <summary>
/// Parses a single line consisting of a keyword and its associated data.
/// </summary>
/// <param name="keyword">Keyword that identifies the line type or command.</param>
/// <param name="data">Data associated with the keyword; may be empty or contain parameters or raw text to interpret.</param>
protected abstract void ParseLine(string keyword, string data);
/// <returns><see langword="true"/> if the line was parsed successfully; otherwise, <see langword="false"/>.</returns>
protected abstract bool ParseLine(string keyword, string data);
}
}
5 changes: 3 additions & 2 deletions ObjLoader/Loaders/MaterialLoader/MaterialLibraryLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,18 @@ private void AddParseAction(string key, Action<string> action)
=> _parseActionDictionary.Add(key.ToLowerInvariant(), action);

/// <inheritdoc />
protected override void ParseLine(string keyword, string data)
protected override bool ParseLine(string keyword, string data)
{
var parseAction = GetKeywordAction(keyword, out bool found);

if (!found)
{
_unrecognizedLines.Add(keyword + " " + data);
return;
return false;
}

parseAction!(data);
return true;
}

private Action<string>? GetKeywordAction(string keyword, out bool found)
Expand Down
15 changes: 15 additions & 0 deletions ObjLoader/Loaders/ObjLoader/FileManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using SharpEngine.Core.ObjLoader.Loader.Loaders;
using System.IO;

namespace SharpEngine.Core.ObjLoader.Loaders.ObjLoader
{
/// <summary>
/// Represents a testable wrapper for creating stream file related objects.
/// </summary>
public class FileManager : IFileManager
{
/// <inheritdoc />
public StreamReader StreamReader(string path)
=> new(path);
}
}
29 changes: 24 additions & 5 deletions ObjLoader/Loaders/ObjLoader/ObjLoader.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using SharpEngine.Core.Entities.Properties.Meshes;
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
{
Expand All @@ -19,6 +21,8 @@
private readonly List<ITypeParser> _typeParsers = [];
private readonly List<string> _unrecognizedLines = [];

private readonly ILogger<ObjLoader> _logger = LoggingExtensions.CreateLogger<ObjLoader>();

/// <summary>
/// Initializes a new instance of <see cref="ObjLoader"/> with the specified file path and data store.
/// </summary>
Expand Down Expand Up @@ -46,16 +50,25 @@
}

/// <inheritdoc />
protected override void ParseLine(string keyword, string data)
protected override bool ParseLine(string keyword, string data)
{
foreach (var typeParser in _typeParsers)
{
if (typeParser.CanParse(keyword))
{
typeParser.Parse(data);
return;
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;
}

/// <summary>
Expand All @@ -64,12 +77,18 @@
/// <param name="gl">The OpenGL context used to construct GPU resources.</param>
public List<Mesh> Load(GL gl)
{
ParseFile(_path);
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.

Check warning on line 86 in ObjLoader/Loaders/ObjLoader/ObjLoader.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this 'TODO' comment.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8PYuIaUniKS_RlXeNx&open=AZ8PYuIaUniKS_RlXeNx&pullRequest=138
return
[
new(gl)
{
Name = _dataStore.ObjectName,
Vertices2 = _dataStore.Vertices,
TextureCoordinates2 = _dataStore.Textures,
Normals2 = _dataStore.Normals,
Expand Down
Loading