add docs for engine

This commit is contained in:
2025-01-07 06:25:09 +03:00
parent 0df34ef0ec
commit 926ce601d9
92 changed files with 3051 additions and 170 deletions

View File

@@ -4,16 +4,46 @@ using OpenTK.Mathematics;
namespace Engine.Asset.Font; namespace Engine.Asset.Font;
/// <summary>
/// Represents a font loaded from a texture atlas and metadata.
/// </summary>
public class Font public class Font
{ {
/// <summary>
/// The static texture atlas used for rendering the font.
/// </summary>
public StaticTexture AtlasTexture { get; } public StaticTexture AtlasTexture { get; }
/// <summary>
/// The metadata associated with the font.
/// </summary>
public Metadata.Metadata Metadata { get; } public Metadata.Metadata Metadata { get; }
/// <summary>
/// The unit range of the font, calculated from the distance range and the atlas size.
/// </summary>
public Vector2 UnitRange { get; } public Vector2 UnitRange { get; }
/// <summary>
/// A dictionary mapping Unicode code points to glyphs.
/// </summary>
private readonly Dictionary<int, Glyph> _glyphs = new(); private readonly Dictionary<int, Glyph> _glyphs = new();
/// <summary>
/// A dictionary mapping Unicode code points to glyph data.
/// </summary>
private readonly Dictionary<int, GlyphData> _glyphData = new(); private readonly Dictionary<int, GlyphData> _glyphData = new();
/// <summary>
/// A dictionary mapping pairs of Unicode code points to kerning data.
/// </summary>
private readonly Dictionary<(int, int), Kerning> _kernings = new(); private readonly Dictionary<(int, int), Kerning> _kernings = new();
/// <summary>
/// Initializes a new instance of the <see cref="Font"/> class with the specified texture atlas and metadata.
/// </summary>
/// <param name="parAtlasTexture">The static texture atlas for the font.</param>
/// <param name="parMetadata">The metadata for the font.</param>
public Font(StaticTexture parAtlasTexture, Metadata.Metadata parMetadata) public Font(StaticTexture parAtlasTexture, Metadata.Metadata parMetadata)
{ {
AtlasTexture = parAtlasTexture; AtlasTexture = parAtlasTexture;
@@ -25,26 +55,52 @@ public class Font
LoadKernings(); LoadKernings();
} }
/// <summary>
/// Retrieves the glyph associated with the specified Unicode value.
/// </summary>
/// <param name="parUnicode">The Unicode value of the glyph.</param>
/// <returns>The glyph for the specified Unicode value, or <c>null</c> if not found.</returns>
public Glyph? GetGlyph(int parUnicode) public Glyph? GetGlyph(int parUnicode)
{ {
return _glyphs.GetValueOrDefault(parUnicode); return _glyphs.GetValueOrDefault(parUnicode);
} }
/// <summary>
/// Retrieves the glyph data associated with the specified Unicode value.
/// </summary>
/// <param name="parUnicode">The Unicode value of the glyph.</param>
/// <returns>The glyph data for the specified Unicode value, or <c>null</c> if not found.</returns>
public GlyphData? GetGlyphData(int parUnicode) public GlyphData? GetGlyphData(int parUnicode)
{ {
return _glyphData.GetValueOrDefault(parUnicode); return _glyphData.GetValueOrDefault(parUnicode);
} }
/// <summary>
/// Retrieves the kerning information between two specified Unicode values.
/// </summary>
/// <param name="parUnicode1">The Unicode value of the first character.</param>
/// <param name="parUnicode2">The Unicode value of the second character.</param>
/// <returns>The kerning information, or <c>null</c> if not found.</returns>
public Kerning? GetKerning(int parUnicode1, int parUnicode2) public Kerning? GetKerning(int parUnicode1, int parUnicode2)
{ {
return _kernings.GetValueOrDefault((parUnicode1, parUnicode2)); return _kernings.GetValueOrDefault((parUnicode1, parUnicode2));
} }
/// <summary>
/// Creates an iterator for traversing through the specified text using the font.
/// </summary>
/// <param name="parText">The text to iterate over.</param>
/// <returns>An iterator for the text.</returns>
public FontIterator Iterator(string parText) public FontIterator Iterator(string parText)
{ {
return new FontIterator(this, parText); return new FontIterator(this, parText);
} }
/// <summary>
/// Measures the size of the specified text when rendered using the font.
/// </summary>
/// <param name="parText">The text to measure.</param>
/// <returns>The size of the text in <see cref="Vector2"/>.</returns>
public Vector2 Measure(string parText) public Vector2 Measure(string parText)
{ {
var fontIterator = Iterator(parText); var fontIterator = Iterator(parText);
@@ -53,6 +109,9 @@ public class Font
return new Vector2(fontIterator.MaxWidth, fontIterator.MaxHeight); return new Vector2(fontIterator.MaxWidth, fontIterator.MaxHeight);
} }
/// <summary>
/// Loads the glyphs from the metadata into the font with their associated data.
/// </summary>
private void LoadGlyphs() private void LoadGlyphs()
{ {
foreach (var glyph in Metadata.Glyphs) foreach (var glyph in Metadata.Glyphs)
@@ -68,6 +127,9 @@ public class Font
} }
} }
/// <summary>
/// Loads the kerning information from the metadata into the font.
/// </summary>
private void LoadKernings() private void LoadKernings()
{ {
foreach (var kerning in Metadata.Kerning) foreach (var kerning in Metadata.Kerning)
@@ -76,11 +138,26 @@ public class Font
} }
} }
/// <summary>
/// Represents the data associated with a single glyph in the font.
/// </summary>
public record GlyphData public record GlyphData
{ {
/// <summary>
/// The positions of the glyph's vertices.
/// </summary>
public Vector2[] Positions { get; } public Vector2[] Positions { get; }
/// <summary>
/// The texture UV coordinates for the glyph's vertices.
/// </summary>
public Vector2[] UVs { get; } public Vector2[] UVs { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GlyphData"/> record with the given metadata and glyph.
/// </summary>
/// <param name="parMetadata">The font metadata.</param>
/// <param name="parGlyph">The glyph for which to create data.</param>
public GlyphData(in Metadata.Metadata parMetadata, in Glyph parGlyph) public GlyphData(in Metadata.Metadata parMetadata, in Glyph parGlyph)
{ {
var size = new Vector2(parMetadata.Atlas.Width, parMetadata.Atlas.Height); var size = new Vector2(parMetadata.Atlas.Width, parMetadata.Atlas.Height);

View File

@@ -3,26 +3,71 @@ using OpenTK.Mathematics;
namespace Engine.Asset.Font; namespace Engine.Asset.Font;
/// <summary>
/// An iterator that traverses through the characters of a given text using a font, calculating the glyph data, positions, and kerning.
/// </summary>
public class FontIterator : IEnumerable<FontIterator.NextGlyphData> public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
{ {
/// <summary>
/// The maximum width encountered while measuring the text.
/// </summary>
public float MaxWidth { get; private set; } public float MaxWidth { get; private set; }
/// <summary>
/// The maximum height encountered while measuring the text.
/// </summary>
public float MaxHeight { get; private set; } public float MaxHeight { get; private set; }
/// <summary>
/// The font to use for text iteration.
/// </summary>
private readonly Font _font; private readonly Font _font;
/// <summary>
/// The text to iterate over.
/// </summary>
private readonly string _text; private readonly string _text;
/// <summary>
/// The current index in the text.
/// </summary>
private int _currentIndex; private int _currentIndex;
/// <summary>
/// The previous Unicode code point.
/// </summary>
private int _previousCodepoint = -1; private int _previousCodepoint = -1;
/// <summary>
/// The number of characters on the current line.
/// </summary>
private int _lineCharCount; private int _lineCharCount;
/// <summary>
/// The current cursor position.
/// </summary>
private Vector2 _cursor = Vector2.Zero; private Vector2 _cursor = Vector2.Zero;
/// <summary>
/// The current kerning offset.
/// </summary>
private Vector2 _kerning = Vector2.Zero; private Vector2 _kerning = Vector2.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="FontIterator"/> class with the specified font and text.
/// </summary>
/// <param name="parFont">The font to use for rendering.</param>
/// <param name="parText">The text to iterate over.</param>
public FontIterator(Font parFont, string parText) public FontIterator(Font parFont, string parText)
{ {
_font = parFont; _font = parFont;
_text = parText; _text = parText;
} }
/// <summary>
/// Returns an enumerator that iterates through the next glyph data for the text.
/// </summary>
/// <returns>An enumerator for the next glyph data in the text.</returns>
public IEnumerator<NextGlyphData> GetEnumerator() public IEnumerator<NextGlyphData> GetEnumerator()
{ {
while (_currentIndex < _text.Length) while (_currentIndex < _text.Length)
@@ -80,21 +125,38 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
} }
} }
/// <summary>
/// Returns a non-generic enumerator for the <see cref="FontIterator"/>.
/// </summary>
/// <returns>A non-generic enumerator for the font iterator.</returns>
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return GetEnumerator(); return GetEnumerator();
} }
/// <summary>
/// Determines if the provided codepoint represents a line break character.
/// </summary>
/// <param name="parCodepoint">The codepoint to check.</param>
/// <returns><c>true</c> if the codepoint represents a line break, otherwise <c>false</c>.</returns>
private static bool IsLineBreak(int parCodepoint) private static bool IsLineBreak(int parCodepoint)
{ {
return parCodepoint == '\n'; return parCodepoint == '\n';
} }
/// <summary>
/// Determines if the provided codepoint represents a tab character.
/// </summary>
/// <param name="parCodepoint">The codepoint to check.</param>
/// <returns><c>true</c> if the codepoint represents a tab character, otherwise <c>false</c>.</returns>
private static bool IsTab(int parCodepoint) private static bool IsTab(int parCodepoint)
{ {
return parCodepoint == '\t'; return parCodepoint == '\t';
} }
/// <summary>
/// Handles the tab character by advancing the cursor position to the next tab stop.
/// </summary>
private void Tab() private void Tab()
{ {
var spaceGlyph = _font.GetGlyph(' '); var spaceGlyph = _font.GetGlyph(' ');
@@ -107,6 +169,9 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
_cursor.X += missingSpaces * spaceGlyph.Advance; _cursor.X += missingSpaces * spaceGlyph.Advance;
} }
/// <summary>
/// Handles a line break by resetting the cursor position and updating the line height.
/// </summary>
private void LineBreak() private void LineBreak()
{ {
_kerning = Vector2.Zero; _kerning = Vector2.Zero;
@@ -116,5 +181,8 @@ public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
_previousCodepoint = -1; _previousCodepoint = -1;
} }
/// <summary>
/// Contains the data for the next glyph in the iteration, including its positions, texture UVs, and offset.
/// </summary>
public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset); public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset);
} }

View File

@@ -2,26 +2,44 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Represents the texture atlas used for font rendering.
/// </summary>
[JsonSerializable(typeof(Atlas))] [JsonSerializable(typeof(Atlas))]
[Serializable] [Serializable]
public record Atlas public record Atlas
{ {
/// <summary>
/// The distance range for the atlas.
/// </summary>
[JsonPropertyName("distanceRange")] [JsonPropertyName("distanceRange")]
[JsonInclude] [JsonInclude]
public float DistanceRange { get; private set; } public float DistanceRange { get; private set; }
/// <summary>
/// The size of the atlas.
/// </summary>
[JsonPropertyName("size")] [JsonPropertyName("size")]
[JsonInclude] [JsonInclude]
public float Size { get; private set; } public float Size { get; private set; }
/// <summary>
/// The width of the atlas.
/// </summary>
[JsonPropertyName("width")] [JsonPropertyName("width")]
[JsonInclude] [JsonInclude]
public int Width { get; private set; } public int Width { get; private set; }
/// <summary>
/// The height of the atlas.
/// </summary>
[JsonPropertyName("height")] [JsonPropertyName("height")]
[JsonInclude] [JsonInclude]
public int Height { get; private set; } public int Height { get; private set; }
/// <summary>
/// The Y-origin of the atlas.
/// </summary>
[JsonPropertyName("yOrigin")] [JsonPropertyName("yOrigin")]
[JsonInclude] [JsonInclude]
public string YOrigin { get; private set; } public string YOrigin { get; private set; }

View File

@@ -2,22 +2,37 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Defines the bounds of a font glyph in the coordinate space.
/// </summary>
[JsonSerializable(typeof(Bounds))] [JsonSerializable(typeof(Bounds))]
[Serializable] [Serializable]
public record Bounds public record Bounds
{ {
/// <summary>
/// The left boundary of the bounds.
/// </summary>
[JsonPropertyName("left")] [JsonPropertyName("left")]
[JsonInclude] [JsonInclude]
public float Left { get; private set; } public float Left { get; private set; }
/// <summary>
/// The bottom boundary of the bounds.
/// </summary>
[JsonPropertyName("bottom")] [JsonPropertyName("bottom")]
[JsonInclude] [JsonInclude]
public float Bottom { get; private set; } public float Bottom { get; private set; }
/// <summary>
/// The right boundary of the bounds.
/// </summary>
[JsonPropertyName("right")] [JsonPropertyName("right")]
[JsonInclude] [JsonInclude]
public float Right { get; private set; } public float Right { get; private set; }
/// <summary>
/// The top boundary of the bounds.
/// </summary>
[JsonPropertyName("top")] [JsonPropertyName("top")]
[JsonInclude] [JsonInclude]
public float Top { get; private set; } public float Top { get; private set; }

View File

@@ -2,22 +2,37 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Represents a single glyph in the font with associated metadata.
/// </summary>
[JsonSerializable(typeof(Glyph))] [JsonSerializable(typeof(Glyph))]
[Serializable] [Serializable]
public record Glyph public record Glyph
{ {
/// <summary>
/// The Unicode code point of the glyph.
/// </summary>
[JsonPropertyName("unicode")] [JsonPropertyName("unicode")]
[JsonInclude] [JsonInclude]
public int Unicode { get; private set; } public int Unicode { get; private set; }
/// <summary>
/// The advance width of the glyph.
/// </summary>
[JsonPropertyName("advance")] [JsonPropertyName("advance")]
[JsonInclude] [JsonInclude]
public float Advance { get; private set; } public float Advance { get; private set; }
/// <summary>
/// The bounds of the glyph in the plane.
/// </summary>
[JsonPropertyName("planeBounds")] [JsonPropertyName("planeBounds")]
[JsonInclude] [JsonInclude]
public Bounds? PlaneBounds { get; private set; } public Bounds? PlaneBounds { get; private set; }
/// <summary>
/// The bounds of the glyph in the atlas.
/// </summary>
[JsonPropertyName("atlasBounds")] [JsonPropertyName("atlasBounds")]
[JsonInclude] [JsonInclude]
public Bounds? AtlasBounds { get; private set; } public Bounds? AtlasBounds { get; private set; }

View File

@@ -2,18 +2,30 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Represents kerning information between two characters in the font.
/// </summary>
[JsonSerializable(typeof(Kerning))] [JsonSerializable(typeof(Kerning))]
[Serializable] [Serializable]
public record Kerning public record Kerning
{ {
/// <summary>
/// The Unicode value of the first character in the kerning pair.
/// </summary>
[JsonPropertyName("unicode1")] [JsonPropertyName("unicode1")]
[JsonInclude] [JsonInclude]
public int Unicode1 { get; private set; } public int Unicode1 { get; private set; }
/// <summary>
/// The Unicode value of the second character in the kerning pair.
/// </summary>
[JsonPropertyName("unicode2")] [JsonPropertyName("unicode2")]
[JsonInclude] [JsonInclude]
public int Unicode2 { get; private set; } public int Unicode2 { get; private set; }
/// <summary>
/// The amount of kerning (adjustment) between the two characters.
/// </summary>
[JsonPropertyName("advance")] [JsonPropertyName("advance")]
[JsonInclude] [JsonInclude]
public float Advance { get; private set; } public float Advance { get; private set; }

View File

@@ -2,22 +2,37 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Represents metadata for a font, including atlas, metrics, glyphs, and kerning.
/// </summary>
[JsonSerializable(typeof(Metadata))] [JsonSerializable(typeof(Metadata))]
[Serializable] [Serializable]
public record Metadata public record Metadata
{ {
/// <summary>
/// The atlas information for the font.
/// </summary>
[JsonPropertyName("atlas")] [JsonPropertyName("atlas")]
[JsonInclude] [JsonInclude]
public Atlas Atlas { get; private set; } public Atlas Atlas { get; private set; }
/// <summary>
/// The metrics of the font.
/// </summary>
[JsonPropertyName("metrics")] [JsonPropertyName("metrics")]
[JsonInclude] [JsonInclude]
public Metrics Metrics { get; private set; } public Metrics Metrics { get; private set; }
/// <summary>
/// The array of glyphs in the font.
/// </summary>
[JsonPropertyName("glyphs")] [JsonPropertyName("glyphs")]
[JsonInclude] [JsonInclude]
public Glyph[] Glyphs { get; private set; } public Glyph[] Glyphs { get; private set; }
/// <summary>
/// The array of kerning pairs for the font.
/// </summary>
[JsonPropertyName("kerning")] [JsonPropertyName("kerning")]
[JsonInclude] [JsonInclude]
public Kerning[] Kerning { get; private set; } public Kerning[] Kerning { get; private set; }

View File

@@ -2,30 +2,51 @@
namespace Engine.Asset.Font.Metadata; namespace Engine.Asset.Font.Metadata;
/// <summary>
/// Represents the metrics for a font, including size and line height.
/// </summary>
[JsonSerializable(typeof(Metrics))] [JsonSerializable(typeof(Metrics))]
[Serializable] [Serializable]
public record Metrics public record Metrics
{ {
/// <summary>
/// The em size of the font.
/// </summary>
[JsonPropertyName("emSize")] [JsonPropertyName("emSize")]
[JsonInclude] [JsonInclude]
public float EmSize { get; private set; } public float EmSize { get; private set; }
/// <summary>
/// The line height of the font.
/// </summary>
[JsonPropertyName("lineHeight")] [JsonPropertyName("lineHeight")]
[JsonInclude] [JsonInclude]
public float LineHeight { get; private set; } public float LineHeight { get; private set; }
/// <summary>
/// The ascender height of the font.
/// </summary>
[JsonPropertyName("ascender")] [JsonPropertyName("ascender")]
[JsonInclude] [JsonInclude]
public float Ascender { get; private set; } public float Ascender { get; private set; }
/// <summary>
/// The descender height of the font.
/// </summary>
[JsonPropertyName("descender")] [JsonPropertyName("descender")]
[JsonInclude] [JsonInclude]
public float Descender { get; private set; } public float Descender { get; private set; }
/// <summary>
/// The Y-coordinate of the underline.
/// </summary>
[JsonPropertyName("underlineY")] [JsonPropertyName("underlineY")]
[JsonInclude] [JsonInclude]
public float UnderlineY { get; private set; } public float UnderlineY { get; private set; }
/// <summary>
/// The thickness of the underline in the font.
/// </summary>
[JsonPropertyName("underlineThickness")] [JsonPropertyName("underlineThickness")]
public float UnderlineThickness { get; private set; } public float UnderlineThickness { get; private set; }
} }

View File

@@ -3,12 +3,34 @@ using Engine.Graphics.Texture;
namespace Engine.Asset; namespace Engine.Asset;
/// <summary>
/// Represents an image containing pixels of a specified type.
/// The image stores pixel data in a 2D array and provides methods for converting it into textures.
/// </summary>
public class Image<T> where T : struct, IPixel public class Image<T> where T : struct, IPixel
{ {
/// <summary>
/// The width of the image, in pixels.
/// </summary>
public int Width { get; } public int Width { get; }
/// <summary>
/// The height of the image, in pixels.
/// </summary>
public int Height { get; } public int Height { get; }
/// <summary>
/// The 2D array of pixels representing the image's content.
/// Each element in the array is of type <typeparamref name="T"/>.
/// </summary>
public T[,] Pixels { get; } public T[,] Pixels { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Image{T}"/> class with specified width and height.
/// Allocates a 2D array to hold pixel data.
/// </summary>
/// <param name="parWidth">The width of the image in pixels.</param>
/// <param name="parHeight">The height of the image in pixels.</param>
public Image(int parWidth, int parHeight) public Image(int parWidth, int parHeight)
{ {
Width = parWidth; Width = parWidth;
@@ -16,8 +38,18 @@ public class Image<T> where T : struct, IPixel
Pixels = new T[parHeight, parWidth]; Pixels = new T[parHeight, parWidth];
} }
/// <summary>
/// Indexer to access individual pixels in the image by their Y (row) and X (column) coordinates.
/// </summary>
/// <param name="parY">The Y coordinate (row) of the pixel.</param>
/// <param name="parX">The X coordinate (column) of the pixel.</param>
/// <returns>The pixel at the specified coordinates.</returns>
public T this[int parY, int parX] => Pixels[parY, parX]; public T this[int parY, int parX] => Pixels[parY, parX];
/// <summary>
/// Converts the image into a dynamic texture that can be uploaded to the GPU.
/// </summary>
/// <returns>A <see cref="DynamicTexture"/> representing the image.</returns>
public DynamicTexture ToDynamicTexture() public DynamicTexture ToDynamicTexture()
{ {
var texture = DynamicTexture.Create<T>(Width, Height); var texture = DynamicTexture.Create<T>(Width, Height);
@@ -25,6 +57,11 @@ public class Image<T> where T : struct, IPixel
return texture; return texture;
} }
/// <summary>
/// Converts the image into a static texture that can be uploaded to the GPU.
/// Static textures are optimized for performance but cannot be updated after creation.
/// </summary>
/// <returns>A <see cref="StaticTexture"/> representing the image.</returns>
public StaticTexture ToStaticTexture() public StaticTexture ToStaticTexture()
{ {
var texture = StaticTexture.Create<T>(Width, Height); var texture = StaticTexture.Create<T>(Width, Height);

View File

@@ -1,9 +1,23 @@
namespace Engine.Asset.Mesh.Loader; namespace Engine.Asset.Mesh.Loader;
/// <summary>
/// Defines the interface for a mesh loader that can load meshes from a given stream.
/// </summary>
public interface IMeshLoader public interface IMeshLoader
{ {
/// <summary>
/// Loads a mesh from the provided text reader with optional parameters.
/// </summary>
/// <param name="parReader">The text reader to read the mesh data from.</param>
/// <param name="parParameters">Optional parameters to control the mesh loading behavior.</param>
/// <returns>The loaded mesh.</returns>
public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default); public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default);
/// <summary>
/// Optimizes the provided mesh by removing duplicate vertices and reusing indices.
/// </summary>
/// <param name="parMesh">The mesh to optimize.</param>
/// <returns>A new optimized mesh.</returns>
internal static Mesh Optimize(Mesh parMesh) internal static Mesh Optimize(Mesh parMesh)
{ {
var optimizedMesh = new Mesh(); var optimizedMesh = new Mesh();

View File

@@ -1,12 +1,33 @@
namespace Engine.Asset.Mesh.Loader; namespace Engine.Asset.Mesh.Loader;
/// <summary>
/// Specifies options for mesh loading behavior, such as loading normals, UVs, and optimization.
/// </summary>
[Flags] [Flags]
public enum MeshLoaderParameters public enum MeshLoaderParameters
{ {
/// <summary>
/// No options selected.
/// </summary>
None = 0, None = 0,
/// <summary>
/// Load normals for the mesh.
/// </summary>
LoadNormals = 1 << 0, LoadNormals = 1 << 0,
/// <summary>
/// Load UVs for the mesh.
/// </summary>
LoadUVs = 1 << 1, LoadUVs = 1 << 1,
/// <summary>
/// Optimize the mesh by removing duplicate vertices and reusing indices.
/// </summary>
Optimize = 1 << 2, Optimize = 1 << 2,
/// <summary>
/// Default set of options: load normals, load UVs, and optimize the mesh.
/// </summary>
Default = LoadNormals | LoadUVs | Optimize Default = LoadNormals | LoadUVs | Optimize
} }

View File

@@ -3,19 +3,40 @@ using OpenTK.Mathematics;
namespace Engine.Asset.Mesh.Loader; namespace Engine.Asset.Mesh.Loader;
/// <summary>
/// A mesh loader for loading meshes from OBJ file format.
/// </summary>
public class ObjMeshLoader : IMeshLoader public class ObjMeshLoader : IMeshLoader
{ {
/// <summary>
/// The singleton instance of the <see cref="ObjMeshLoader"/>.
/// </summary>
private static readonly ObjMeshLoader INSTANCE = new(); private static readonly ObjMeshLoader INSTANCE = new();
/// <summary>
/// Initializes a new instance of the <see cref="ObjMeshLoader"/> class.
/// </summary>
private ObjMeshLoader() private ObjMeshLoader()
{ {
} }
/// <summary>
/// Loads a mesh from the provided text reader in the OBJ format with optional parameters.
/// </summary>
/// <param name="parReader">The text reader to read the OBJ data from.</param>
/// <param name="parParameters">Optional parameters to control the mesh loading behavior.</param>
/// <returns>The loaded mesh.</returns>
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
return INSTANCE.LoadMesh(parReader, parParameters); return INSTANCE.LoadMesh(parReader, parParameters);
} }
/// <summary>
/// Loads a mesh from the provided text reader in the OBJ format with optional parameters.
/// </summary>
/// <param name="parReader">The text reader to read the OBJ data from.</param>
/// <param name="parParameters">Optional parameters to control the mesh loading behavior.</param>
/// <returns>The loaded mesh.</returns>
public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
var mesh = new Mesh(); var mesh = new Mesh();

View File

@@ -3,19 +3,40 @@ using OpenTK.Mathematics;
namespace Engine.Asset.Mesh.Loader; namespace Engine.Asset.Mesh.Loader;
/// <summary>
/// A mesh loader for loading meshes from STL file format.
/// </summary>
public class StlMeshLoader : IMeshLoader public class StlMeshLoader : IMeshLoader
{ {
/// <summary>
/// The singleton instance of the <see cref="StlMeshLoader"/>.
/// </summary>
private static readonly StlMeshLoader INSTANCE = new(); private static readonly StlMeshLoader INSTANCE = new();
/// <summary>
/// Initializes a new instance of the <see cref="StlMeshLoader"/> class.
/// </summary>
private StlMeshLoader() private StlMeshLoader()
{ {
} }
/// <summary>
/// Loads a mesh from the provided text reader in the STL format with optional parameters.
/// </summary>
/// <param name="parReader">The text reader to read the STL data from.</param>
/// <param name="parParameters">Optional parameters to control the mesh loading behavior.</param>
/// <returns>The loaded mesh.</returns>
public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public static Mesh Load(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
return INSTANCE.LoadMesh(parReader, parParameters); return INSTANCE.LoadMesh(parReader, parParameters);
} }
/// <summary>
/// Loads a mesh from the provided text reader in the STL format with optional parameters.
/// </summary>
/// <param name="parReader">The text reader to read the STL data from.</param>
/// <param name="parParameters">Optional parameters to control the mesh loading behavior.</param>
/// <returns>The loaded mesh.</returns>
public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default) public Mesh LoadMesh(TextReader parReader, MeshLoaderParameters parParameters = MeshLoaderParameters.Default)
{ {
var mesh = new Mesh(); var mesh = new Mesh();

View File

@@ -4,26 +4,68 @@ using OpenTK.Mathematics;
namespace Engine.Asset.Mesh; namespace Engine.Asset.Mesh;
/// <summary>
/// Represents a 3D mesh consisting of vertices and indices used for rendering.
/// </summary>
public class Mesh public class Mesh
{ {
/// <summary>
/// A read-only list of indices representing the mesh's geometry.
/// </summary>
public IReadOnlyList<uint> Indices => _indices; public IReadOnlyList<uint> Indices => _indices;
/// <summary>
/// A read-only list of vertices representing the mesh's geometry.
/// </summary>
public IReadOnlyList<Vertex> Vertices => _vertices; public IReadOnlyList<Vertex> Vertices => _vertices;
/// <summary>
/// Internal list of indices representing the mesh's geometry.
/// </summary>
internal IList<uint> IndicesInternal => _indices; internal IList<uint> IndicesInternal => _indices;
/// <summary>
/// Internal list of vertices representing the mesh's geometry.
/// </summary>
internal IList<Vertex> VerticesInternal => _vertices; internal IList<Vertex> VerticesInternal => _vertices;
/// <summary>
/// List of indices representing the mesh's geometry.
/// </summary>
private readonly List<uint> _indices = []; private readonly List<uint> _indices = [];
/// <summary>
/// List of vertices representing the mesh's geometry.
/// </summary>
private readonly List<Vertex> _vertices = []; private readonly List<Vertex> _vertices = [];
/// <summary>
/// Computes a hash code for the mesh based on its vertices and indices.
/// </summary>
/// <returns>The hash code for the mesh.</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(Indices, Vertices); return HashCode.Combine(Indices, Vertices);
} }
/// <summary>
/// Represents a vertex in a mesh, including position, normal, and UV coordinates.
/// </summary>
public record struct Vertex : IVertex public record struct Vertex : IVertex
{ {
/// <summary>
/// The position of the vertex in 3D space.
/// </summary>
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position; [Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
/// <summary>
/// The normal of the vertex for lighting calculations.
/// </summary>
[Vertex(VertexAttribType.Float, 3)] public Vector3 _normal; [Vertex(VertexAttribType.Float, 3)] public Vector3 _normal;
/// <summary>
/// The texture coordinates (UVs) of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv; [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
} }
} }

View File

@@ -20,17 +20,49 @@ using Debug = Engine.Graphics.Debug;
namespace Engine; namespace Engine;
/// <summary>
/// Represents the main engine class responsible for managing resources, rendering, input, and scenes.
/// </summary>
public sealed class Engine public sealed class Engine
{ {
/// <summary>
/// The input handler of the engine.
/// </summary>
public IInputHandler? InputHandler { get; internal set; } public IInputHandler? InputHandler { get; internal set; }
/// <summary>
/// The scene manager for managing and updating scenes.
/// </summary>
public SceneManager SceneManager { get; } = new(); public SceneManager SceneManager { get; } = new();
/// <summary>
/// The resource manager responsible for asset management.
/// </summary>
public IResourceManager AssetResourceManager { get; } public IResourceManager AssetResourceManager { get; }
/// <summary>
/// The path to the data folder used by the engine.
/// </summary>
public string DataFolder { get; } public string DataFolder { get; }
/// <summary>
/// The singleton instance of the engine.
/// </summary>
internal static Engine Instance { get; private set; } = null!; internal static Engine Instance { get; private set; } = null!;
/// <summary>
/// The resource manager for engine-specific resources.
/// </summary>
internal ResourceManager EngineResourceManager { get; } internal ResourceManager EngineResourceManager { get; }
/// <summary>
/// The renderer responsible for managing rendering operations.
/// </summary>
internal Renderer Renderer { get; } internal Renderer Renderer { get; }
/// <summary>
/// The presenter responsible for displaying rendered content.
/// </summary>
internal IPresenter? Presenter internal IPresenter? Presenter
{ {
get => _presenter; get => _presenter;
@@ -38,26 +70,54 @@ public sealed class Engine
{ {
if (_presenter != null) if (_presenter != null)
{ {
_presenter.Resize -= PresenterResize; _presenter.OnResize -= PresenterResize;
} }
_presenter = value; _presenter = value;
if (_presenter != null) if (_presenter != null)
{ {
_presenter.Resize += PresenterResize; _presenter.OnResize += PresenterResize;
} }
} }
} }
/// <summary>
/// The application window that holds rendering context.
/// </summary>
internal Window Window { get; } internal Window Window { get; }
/// <summary>
/// The logger instance used by the engine.
/// </summary>
private readonly ILogger _logger; private readonly ILogger _logger;
/// <summary>
/// The lock used to synchronize scene access.
/// </summary>
private readonly object _sceneLock = new(); private readonly object _sceneLock = new();
/// <summary>
/// The presenter used to display rendered content.
/// </summary>
private IPresenter? _presenter; private IPresenter? _presenter;
/// <summary>
/// The thread used to run the update loop.
/// </summary>
private Thread? _updateThread; private Thread? _updateThread;
/// <summary>
/// Initializes a new instance of the <see cref="Engine"/> class.
/// </summary>
/// <param name="parWidth">The width of the rendering window.</param>
/// <param name="parHeight">The height of the rendering window.</param>
/// <param name="parHeadless">Indicates whether the engine should run in headless mode.</param>
/// <param name="parTitle">The title of the application window.</param>
/// <param name="parAssetFolder">The path to the asset folder.</param>
/// <param name="parDataFolder">The path to the data folder.</param>
/// <param name="parLogger">The logger instance to use for logging.</param>
/// <exception cref="InvalidOperationException">Thrown if an engine instance is already running.</exception>
public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder, public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder,
string parDataFolder, string parDataFolder,
ILogger parLogger) ILogger parLogger)
@@ -96,6 +156,31 @@ public sealed class Engine
Window = new Window(this, Renderer.NativeWindow, parHeadless); Window = new Window(this, Renderer.NativeWindow, parHeadless);
} }
/// <summary>
/// Starts the engine's update and render threads.
/// </summary>
public void Run()
{
_updateThread = new Thread(RunUpdate) { Name = "UpdateThread" };
_updateThread.Start();
RunRender();
_updateThread.Join();
}
/// <summary>
/// Closes the engine and stops all running threads.
/// </summary>
public void Close()
{
Presenter?.Exit();
}
/// <summary>
/// Creates and initializes the engine resource manager.
/// </summary>
/// <returns>The initialized resource manager.</returns>
private static ResourceManager CreateEngineResourceManager() private static ResourceManager CreateEngineResourceManager()
{ {
var memoryStreamProvider = new MemoryResourceStreamProvider(); var memoryStreamProvider = new MemoryResourceStreamProvider();
@@ -109,6 +194,11 @@ public sealed class Engine
return resourceManager; return resourceManager;
} }
/// <summary>
/// Creates and initializes the asset resource manager.
/// </summary>
/// <param name="parAssetFolder">The path to the asset folder.</param>
/// <returns>The initialized resource manager.</returns>
private static ResourceManager CreateAssetResourceManager(string parAssetFolder) private static ResourceManager CreateAssetResourceManager(string parAssetFolder)
{ {
var filesystemStreamProvider = new FilesystemResourceStreamProvider(parAssetFolder); var filesystemStreamProvider = new FilesystemResourceStreamProvider(parAssetFolder);
@@ -119,6 +209,10 @@ public sealed class Engine
return resourceManager; return resourceManager;
} }
/// <summary>
/// Registers the default loaders for the resource manager.
/// </summary>
/// <param name="parResourceManager">The resource manager to register loaders for.</param>
private static void RegisterDefaultLoaders(ResourceManager parResourceManager) private static void RegisterDefaultLoaders(ResourceManager parResourceManager)
{ {
parResourceManager.RegisterLoader<Program>(new ProgramLoader()); parResourceManager.RegisterLoader<Program>(new ProgramLoader());
@@ -128,21 +222,9 @@ public sealed class Engine
parResourceManager.RegisterLoader<Font>(new FontLoader()); parResourceManager.RegisterLoader<Font>(new FontLoader());
} }
public void Run() /// <summary>
{ /// Runs the render loop for the engine.
_updateThread = new Thread(RunUpdate) { Name = "UpdateThread" }; /// </summary>
_updateThread.Start();
RunRender();
_updateThread.Join();
}
public void Close()
{
Presenter?.Exit();
}
private void RunRender() private void RunRender()
{ {
while (!Presenter?.IsExiting ?? false) while (!Presenter?.IsExiting ?? false)
@@ -184,6 +266,9 @@ public sealed class Engine
} }
} }
/// <summary>
/// Runs the update loop for the engine.
/// </summary>
private void RunUpdate() private void RunUpdate()
{ {
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
@@ -211,6 +296,10 @@ public sealed class Engine
} }
} }
/// <summary>
/// Handles the presenter resize event to adjust the renderer's viewport.
/// </summary>
/// <param name="parEventArgs">The resize event arguments.</param>
private void PresenterResize(ResizeEventArgs parEventArgs) private void PresenterResize(ResizeEventArgs parEventArgs)
{ {
if (parEventArgs.Width == 0 || parEventArgs.Height == 0) if (parEventArgs.Width == 0 || parEventArgs.Height == 0)

View File

@@ -7,36 +7,99 @@ using Serilog.Sinks.SystemConsole.Themes;
namespace Engine; namespace Engine;
/// <summary>
/// Provides a builder for creating and configuring an instance of the <see cref="Engine"/> class.
/// </summary>
public sealed class EngineBuilder public sealed class EngineBuilder
{ {
/// <summary>
/// The title of the application window.
/// </summary>
private string _title = ""; private string _title = "";
/// <summary>
/// Indicates whether the engine should run in headless mode.
/// </summary>
private bool _headless; private bool _headless;
/// <summary>
/// The width of the rendering window.
/// </summary>
private int _width = 1; private int _width = 1;
/// <summary>
/// The height of the rendering window.
/// </summary>
private int _height = 1; private int _height = 1;
/// <summary>
/// The path to the asset folder.
/// </summary>
private string _assetFolder = "./asset"; private string _assetFolder = "./asset";
/// <summary>
/// The path to the data folder.
/// </summary>
private string _dataFolder = "./data"; private string _dataFolder = "./data";
/// <summary>
/// The input handler factory.
/// </summary>
private Func<Engine, IInputHandler>? _inputHandlerFunc; private Func<Engine, IInputHandler>? _inputHandlerFunc;
/// <summary>
/// The presenter factory.
/// </summary>
private Func<Engine, IPresenter>? _presenterFunc; private Func<Engine, IPresenter>? _presenterFunc;
// Logging // Logging
/// <summary>
/// Indicates whether to log to the console.
/// </summary>
private bool _logToConsole; private bool _logToConsole;
/// <summary>
/// Indicates whether to log to a file.
/// </summary>
private bool _logToFile; private bool _logToFile;
/// <summary>
/// The path to the log file.
/// </summary>
private string? _logFilePath; private string? _logFilePath;
/// <summary>
/// The log level.
/// </summary>
private LogEventLevel _logLevel = LogEventLevel.Information; private LogEventLevel _logLevel = LogEventLevel.Information;
/// <summary>
/// Sets the title of the engine window.
/// </summary>
/// <param name="parTitle">The title to use for the engine window.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Title(string parTitle) public EngineBuilder Title(string parTitle)
{ {
_title = parTitle; _title = parTitle;
return this; return this;
} }
/// <summary>
/// Configures the engine to run in headless mode.
/// </summary>
/// <param name="parHeadless">Indicates whether to enable headless mode. Defaults to <c>true</c>.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Headless(bool parHeadless = true) public EngineBuilder Headless(bool parHeadless = true)
{ {
_headless = parHeadless; _headless = parHeadless;
return this; return this;
} }
/// <summary>
/// Sets the width of the engine window.
/// </summary>
/// <param name="parWidth">The width in pixels. Must be greater than zero.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Width(int parWidth) public EngineBuilder Width(int parWidth)
{ {
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parWidth);
@@ -45,6 +108,11 @@ public sealed class EngineBuilder
return this; return this;
} }
/// <summary>
/// Sets the height of the engine window.
/// </summary>
/// <param name="parHeight">The height in pixels. Must be greater than zero.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Height(int parHeight) public EngineBuilder Height(int parHeight)
{ {
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(parHeight);
@@ -53,36 +121,68 @@ public sealed class EngineBuilder
return this; return this;
} }
/// <summary>
/// Sets the folder path for assets used by the engine.
/// </summary>
/// <param name="parAssetFolder">The folder path containing asset files.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder AssetFolder(string parAssetFolder) public EngineBuilder AssetFolder(string parAssetFolder)
{ {
_assetFolder = parAssetFolder; _assetFolder = parAssetFolder;
return this; return this;
} }
/// <summary>
/// Sets the folder path for data files used by the engine.
/// </summary>
/// <param name="parDataFolder">The folder path containing data files.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder DataFolder(string parDataFolder) public EngineBuilder DataFolder(string parDataFolder)
{ {
_dataFolder = parDataFolder; _dataFolder = parDataFolder;
return this; return this;
} }
/// <summary>
/// Specifies the input handler to be used by the engine.
/// </summary>
/// <param name="parInputHandlerFunc">A function that creates an input handler for the engine.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc) public EngineBuilder InputHandler(Func<Engine, IInputHandler> parInputHandlerFunc)
{ {
_inputHandlerFunc = parInputHandlerFunc; _inputHandlerFunc = parInputHandlerFunc;
return this; return this;
} }
/// <summary>
/// Specifies the presenter to be used by the engine.
/// </summary>
/// <param name="parPresenterFunc">A function that creates a presenter for the engine.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc) public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
{ {
_presenterFunc = parPresenterFunc; _presenterFunc = parPresenterFunc;
return this; return this;
} }
/// <summary>
/// Configures logging to output to the console.
/// </summary>
/// <param name="parLogToConsole">Indicates whether to enable console logging. Defaults to <c>true</c>.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder LogToConsole(bool parLogToConsole = true) public EngineBuilder LogToConsole(bool parLogToConsole = true)
{ {
_logToConsole = parLogToConsole; _logToConsole = parLogToConsole;
return this; return this;
} }
/// <summary>
/// Configures logging to output to a file.
/// </summary>
/// <param name="parLogToFile">Indicates whether to enable file logging. Defaults to <c>true</c>.</param>
/// <param name="parLogFilePath">The path of the log file. Cannot be <c>null</c> if file logging is enabled.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="parLogFilePath"/> is <c>null</c> when <paramref name="parLogToFile"/> is <c>true</c>.</exception>
public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null) public EngineBuilder LogToFile(bool parLogToFile = true, string? parLogFilePath = null)
{ {
if (parLogToFile && parLogFilePath == null) if (parLogToFile && parLogFilePath == null)
@@ -95,12 +195,21 @@ public sealed class EngineBuilder
return this; return this;
} }
/// <summary>
/// Sets the minimum log level for logging.
/// </summary>
/// <param name="parLogLevel">The minimum level of log events to capture.</param>
/// <returns>The current instance of <see cref="EngineBuilder"/> for chaining.</returns>
public EngineBuilder LogLevel(LogEventLevel parLogLevel) public EngineBuilder LogLevel(LogEventLevel parLogLevel)
{ {
_logLevel = parLogLevel; _logLevel = parLogLevel;
return this; return this;
} }
/// <summary>
/// Builds and returns a new instance of the <see cref="Engine"/> class based on the configured settings.
/// </summary>
/// <returns>A fully configured instance of <see cref="Engine"/>.</returns>
public Engine Build() public Engine Build()
{ {
var logger = BuildLogger(); var logger = BuildLogger();
@@ -121,6 +230,10 @@ public sealed class EngineBuilder
return engine; return engine;
} }
/// <summary>
/// Configures and builds a logger based on the current logging settings.
/// </summary>
/// <returns>A configured instance of <see cref="Logger"/>.</returns>
private Logger BuildLogger() private Logger BuildLogger()
{ {
const string template = const string template =

View File

@@ -3,10 +3,21 @@ using Serilog;
namespace Engine.Graphics.Buffer; namespace Engine.Graphics.Buffer;
/// <summary>
/// Represents an OpenGL index buffer, used for storing indices for rendering.
/// </summary>
public class IndexBuffer : OpenGlObject public class IndexBuffer : OpenGlObject
{ {
/// <summary>
/// The number of indices in the buffer.
/// </summary>
internal int Count { get; } internal int Count { get; }
/// <summary>
/// Initializes a new index buffer with a specified number of elements.
/// </summary>
/// <param name="parCount">The number of indices the buffer will store.</param>
/// <param name="parFlags">Optional storage flags for the buffer.</param>
public IndexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None) public IndexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
{ {
Count = parCount; Count = parCount;
@@ -20,6 +31,11 @@ public class IndexBuffer : OpenGlObject
} }
/// <summary>
/// Initializes a new index buffer with the provided data.
/// </summary>
/// <param name="parData">The data to populate the buffer with.</param>
/// <param name="parFlags">Optional storage flags for the buffer.</param>
public IndexBuffer(uint[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None) public IndexBuffer(uint[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
{ {
Count = parData.Length; Count = parData.Length;
@@ -32,11 +48,21 @@ public class IndexBuffer : OpenGlObject
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count); Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
} }
/// <summary>
/// Uploads data to the buffer starting at offset 0.
/// </summary>
/// <param name="parData">The data to upload.</param>
public void UploadData(uint[] parData) public void UploadData(uint[] parData)
{ {
UploadData(0, parData); UploadData(0, parData);
} }
/// <summary>
/// Uploads data to the buffer at the specified offset.
/// </summary>
/// <param name="parOffset">The offset (in elements) where data should be uploaded.</param>
/// <param name="parData">The data to upload.</param>
/// <exception cref="ArgumentException">Thrown if the offset is invalid or the data exceeds buffer size.</exception>
public void UploadData(int parOffset, uint[] parData) public void UploadData(int parOffset, uint[] parData)
{ {
if (parOffset < 0) if (parOffset < 0)
@@ -52,16 +78,19 @@ public class IndexBuffer : OpenGlObject
GL.NamedBufferSubData(Handle, parOffset, parData.Length * sizeof(uint), parData); GL.NamedBufferSubData(Handle, parOffset, parData.Length * sizeof(uint), parData);
} }
/// <inheritdoc />
internal override void Bind() internal override void Bind()
{ {
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
} }
/// <inheritdoc />
internal override void Unbind() internal override void Unbind()
{ {
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
} }
/// <inheritdoc />
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteBuffer(Handle); GL.DeleteBuffer(Handle);

View File

@@ -4,18 +4,38 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Buffer.Vertex; namespace Engine.Graphics.Buffer.Vertex;
/// <summary>
/// Interface for a vertex structure used in graphics buffers.
/// Provides methods for reflecting on vertex fields and checking their validity.
/// </summary>
public interface IVertex public interface IVertex
{ {
/// <summary>
/// Retrieves the fields of a type implementing <see cref="IVertex"/>.
/// </summary>
/// <typeparam name="T">The type to retrieve the fields for.</typeparam>
/// <returns>An ordered enumerable of <see cref="FieldInfo"/> for the fields in the type.</returns>
public static IOrderedEnumerable<FieldInfo> GetFields<T>() public static IOrderedEnumerable<FieldInfo> GetFields<T>()
{ {
return GetFields(typeof(T)); return GetFields(typeof(T));
} }
/// <summary>
/// Retrieves the fields of a specified type implementing <see cref="IVertex"/>.
/// </summary>
/// <param name="parType">The type to retrieve the fields for.</param>
/// <returns>An ordered enumerable of <see cref="FieldInfo"/> for the fields in the type.</returns>
public static IOrderedEnumerable<FieldInfo> GetFields(Type parType) public static IOrderedEnumerable<FieldInfo> GetFields(Type parType)
{ {
return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken); return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken);
} }
/// <summary>
/// Validates if a type is a valid <see cref="IVertex"/>.
/// Checks if all fields are value types, have <see cref="VertexAttribute"/> attributes, and match their size.
/// </summary>
/// <param name="parType">The type to validate.</param>
/// <returns>True if the type is valid, otherwise false.</returns>
public static bool IsValid(Type parType) public static bool IsValid(Type parType)
{ {
if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex))) if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex)))
@@ -50,6 +70,11 @@ public interface IVertex
return totalSize == Marshal.SizeOf(parType); return totalSize == Marshal.SizeOf(parType);
} }
/// <summary>
/// Gets the size of a vertex attribute based on its OpenGL type.
/// </summary>
/// <param name="parType">The OpenGL type of the attribute.</param>
/// <returns>The size of the attribute in bytes.</returns>
public static int AttributeSize(VertexAttribType parType) public static int AttributeSize(VertexAttribType parType)
{ {
return parType switch return parType switch

View File

@@ -2,14 +2,40 @@
namespace Engine.Graphics.Buffer.Vertex; namespace Engine.Graphics.Buffer.Vertex;
/// <summary>
/// Attribute used to describe a vertex field.
/// Provides details about the attribute's type, component count, normalization, and repetition.
/// </summary>
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public class VertexAttribute : Attribute public class VertexAttribute : Attribute
{ {
/// <summary>
/// OpenGL type of the vertex attribute.
/// </summary>
public VertexAttribType Type { get; } public VertexAttribType Type { get; }
/// <summary>
/// Number of components in the vertex attribute.
/// </summary>
public int ComponentCount { get; } public int ComponentCount { get; }
/// <summary>
/// Whether the vertex attribute is normalized.
/// </summary>
public bool Normalized { get; } public bool Normalized { get; }
/// <summary>
/// Number of times the vertex attribute is repeated.
/// </summary>
public int RepeatCount { get; } public int RepeatCount { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VertexAttribute"/> class.
/// </summary>
/// <param name="parType">The OpenGL type of the vertex attribute.</param>
/// <param name="parComponentCount">The number of components in the vertex attribute.</param>
/// <param name="parRepeatCount">The repeat count for the vertex attribute.</param>
/// <param name="parNormalized">Whether the attribute is normalized.</param>
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, int parRepeatCount = 1, public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, int parRepeatCount = 1,
bool parNormalized = false bool parNormalized = false
) )

View File

@@ -6,10 +6,19 @@ using Serilog;
namespace Engine.Graphics.Buffer; namespace Engine.Graphics.Buffer;
/// <summary>
/// Represents a vertex array object in OpenGL, managing vertex buffer bindings and configurations.
/// </summary>
public class VertexArray : OpenGlObject public class VertexArray : OpenGlObject
{ {
/// <summary>
/// Stores the enabled attribute flags for this vertex array.
/// </summary>
private int _enabledAttributes; private int _enabledAttributes;
/// <summary>
/// Initializes a new instance of the <see cref="VertexArray"/> class, creating an OpenGL vertex array object.
/// </summary>
public VertexArray() public VertexArray()
{ {
GL.CreateVertexArrays(1, out int handle); GL.CreateVertexArrays(1, out int handle);
@@ -18,6 +27,10 @@ public class VertexArray : OpenGlObject
Log.Debug("Vertex array {Handle} created", Handle); Log.Debug("Vertex array {Handle} created", Handle);
} }
/// <summary>
/// Binds an index buffer to this vertex array.
/// </summary>
/// <param name="parBuffer">The index buffer to bind.</param>
public void BindIndexBuffer(IndexBuffer parBuffer) public void BindIndexBuffer(IndexBuffer parBuffer)
{ {
GL.VertexArrayElementBuffer(Handle, parBuffer.Handle); GL.VertexArrayElementBuffer(Handle, parBuffer.Handle);
@@ -25,6 +38,13 @@ public class VertexArray : OpenGlObject
Log.Debug("Index buffer {Buffer} bound to vertex array {Handle}", parBuffer.Handle, Handle); Log.Debug("Index buffer {Buffer} bound to vertex array {Handle}", parBuffer.Handle, Handle);
} }
/// <summary>
/// Binds a vertex buffer to this vertex array and configures its vertex attributes.
/// </summary>
/// <typeparam name="T">The type of the vertex buffer, which must implement the <see cref="IVertex"/> interface.</typeparam>
/// <param name="parBuffer">The vertex buffer to bind.</param>
/// <param name="parBindingIndex">The binding index for the vertex buffer. Defaults to 0.</param>
/// <param name="parDivisor">The divisor for instancing, which controls how often the vertex data is used. Defaults to 0.</param>
public void BindVertexBuffer<T>(VertexBuffer<T> parBuffer, int parBindingIndex = 0, int parDivisor = 0) public void BindVertexBuffer<T>(VertexBuffer<T> parBuffer, int parBindingIndex = 0, int parDivisor = 0)
where T : struct, IVertex where T : struct, IVertex
{ {
@@ -52,6 +72,13 @@ public class VertexArray : OpenGlObject
parBuffer.Handle, Handle, parBindingIndex, parDivisor); parBuffer.Handle, Handle, parBindingIndex, parDivisor);
} }
/// <summary>
/// Configures a vertex attribute for a vertex array.
/// </summary>
/// <param name="parAttribute">The vertex attribute to configure.</param>
/// <param name="parBaseLocation">The base location for the attribute.</param>
/// <param name="parBaseOffset">The base offset for the attribute.</param>
/// <param name="parBindingIndex">The binding index for the attribute.</param>
private void SetupAttribute(VertexAttribute parAttribute, int parBaseLocation, int parBaseOffset, int parBindingIndex) private void SetupAttribute(VertexAttribute parAttribute, int parBaseLocation, int parBaseOffset, int parBindingIndex)
{ {
var size = parAttribute.ComponentCount * IVertex.AttributeSize(parAttribute.Type); var size = parAttribute.ComponentCount * IVertex.AttributeSize(parAttribute.Type);
@@ -69,16 +96,19 @@ public class VertexArray : OpenGlObject
} }
} }
/// <inheritdoc />
internal override void Bind() internal override void Bind()
{ {
GL.BindVertexArray(Handle); GL.BindVertexArray(Handle);
} }
/// <inheritdoc />
internal override void Unbind() internal override void Unbind()
{ {
GL.BindVertexArray(0); GL.BindVertexArray(0);
} }
/// <inheritdoc />
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteVertexArray(Handle); GL.DeleteVertexArray(Handle);

View File

@@ -5,13 +5,29 @@ using Serilog;
namespace Engine.Graphics.Buffer; namespace Engine.Graphics.Buffer;
/// <summary>
/// Represents an OpenGL vertex buffer for storing vertex data.
/// </summary>
/// <typeparam name="T">The vertex structure type implementing <see cref="IVertex"/>.</typeparam>
public class VertexBuffer<T> : OpenGlObject public class VertexBuffer<T> : OpenGlObject
where T : struct, IVertex where T : struct, IVertex
{ {
/// <summary>
/// The number of vertices in the buffer.
/// </summary>
internal int Count { get; } internal int Count { get; }
/// <summary>
/// The size of a single vertex in bytes.
/// </summary>
private readonly int _stride = Marshal.SizeOf<T>(); private readonly int _stride = Marshal.SizeOf<T>();
/// <summary>
/// Initializes a new vertex buffer with a specified number of elements.
/// </summary>
/// <param name="parCount">The number of vertices the buffer will store.</param>
/// <param name="parFlags">Optional storage flags for the buffer.</param>
/// <exception cref="ArgumentException">Thrown if the type is not a valid vertex type or the count is invalid.</exception>
public VertexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None) public VertexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
{ {
if (!IVertex.IsValid(typeof(T))) if (!IVertex.IsValid(typeof(T)))
@@ -34,6 +50,12 @@ public class VertexBuffer<T> : OpenGlObject
Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name); Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
} }
/// <summary>
/// Initializes a new vertex buffer with the provided data.
/// </summary>
/// <param name="parData">The data to populate the buffer with.</param>
/// <param name="parFlags">Optional storage flags for the buffer.</param>
/// <exception cref="ArgumentException">Thrown if the type is not a valid vertex type or the data is invalid.</exception>
public VertexBuffer(T[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None) public VertexBuffer(T[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
{ {
if (!IVertex.IsValid(typeof(T))) if (!IVertex.IsValid(typeof(T)))
@@ -56,16 +78,32 @@ public class VertexBuffer<T> : OpenGlObject
Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name); Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
} }
/// <summary>
/// Uploads data to the buffer starting at offset 0.
/// </summary>
/// <param name="parData">The data to upload.</param>
public void UploadData(T[] parData) public void UploadData(T[] parData)
{ {
UploadData(0, parData, parData.Length); UploadData(0, parData, parData.Length);
} }
/// <summary>
/// Uploads data to the buffer starting at offset 0 with a specified count.
/// </summary>
/// <param name="parData">The data to upload.</param>
/// <param name="parCount">The number of elements to upload.</param>
public void UploadData(T[] parData, int parCount) public void UploadData(T[] parData, int parCount)
{ {
UploadData(0, parData, parCount); UploadData(0, parData, parCount);
} }
/// <summary>
/// Uploads data to the buffer at the specified offset.
/// </summary>
/// <param name="parOffset">The offset (in elements) where data should be uploaded.</param>
/// <param name="parData">The data to upload.</param>
/// <param name="parCount">The number of elements to upload.</param>
/// <exception cref="ArgumentException">Thrown if parameters are invalid or data exceeds buffer size.</exception>
public void UploadData(int parOffset, T[] parData, int parCount) public void UploadData(int parOffset, T[] parData, int parCount)
{ {
if (parOffset < 0) if (parOffset < 0)
@@ -91,16 +129,19 @@ public class VertexBuffer<T> : OpenGlObject
GL.NamedBufferSubData(Handle, parOffset * _stride, parCount * _stride, parData); GL.NamedBufferSubData(Handle, parOffset * _stride, parCount * _stride, parData);
} }
/// <inheritdoc />
internal override void Bind() internal override void Bind()
{ {
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
} }
/// <inheritdoc />
internal override void Unbind() internal override void Unbind()
{ {
GL.BindBuffer(BufferTarget.ArrayBuffer, 0); GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
} }
/// <inheritdoc />
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteBuffer(Handle); GL.DeleteBuffer(Handle);

View File

@@ -2,9 +2,23 @@
namespace Engine.Graphics.Camera; namespace Engine.Graphics.Camera;
/// <summary>
/// Defines the interface for camera functionality, including projection and view matrices.
/// </summary>
public interface ICamera public interface ICamera
{ {
/// <summary>
/// The matrix representing the camera's view transformation.
/// </summary>
Matrix4 View { get; } Matrix4 View { get; }
/// <summary>
/// The matrix representing the camera's projection transformation.
/// </summary>
Matrix4 Projection { get; } Matrix4 Projection { get; }
/// <summary>
/// The dimensions of the screen in pixels.
/// </summary>
Vector2i ScreenSize { get; internal set; } Vector2i ScreenSize { get; internal set; }
} }

View File

@@ -2,9 +2,17 @@
namespace Engine.Graphics.Camera; namespace Engine.Graphics.Camera;
/// <summary>
/// Represents a camera that uses a screenspace projection.
/// </summary>
public class ScreenspaceCamera : ICamera public class ScreenspaceCamera : ICamera
{ {
/// <inheritdoc/>
public Matrix4 View => Matrix4.Identity; public Matrix4 View => Matrix4.Identity;
/// <inheritdoc/>
public Matrix4 Projection => Matrix4.Identity; public Matrix4 Projection => Matrix4.Identity;
/// <inheritdoc/>
public Vector2i ScreenSize { get; set; } public Vector2i ScreenSize { get; set; }
} }

View File

@@ -6,31 +6,59 @@ using Serilog.Events;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Provides debugging utilities for OpenGL and integrates with RenderDoc for frame capturing.
/// </summary>
internal static class Debug internal static class Debug
{ {
/// <summary>
/// The RenderDoc API instance for capturing and debugging GPU frames.
/// </summary>
private static RenderDoc _renderDocApi; private static RenderDoc _renderDocApi;
/// <summary>
/// Configures OpenGL debugging and enables debug output.
/// </summary>
public static void Setup() public static void Setup()
{ {
GL.Enable(EnableCap.DebugOutput); GL.Enable(EnableCap.DebugOutput);
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero); GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
} }
/// <summary>
/// Initializes RenderDoc support for capturing GPU frames.
/// </summary>
public static void InitializeRenderDoc() public static void InitializeRenderDoc()
{ {
RenderDoc.Load(out _renderDocApi); RenderDoc.Load(out _renderDocApi);
} }
/// <summary>
/// Starts a new RenderDoc frame capture.
/// </summary>
public static void RenderDocStartFrame() public static void RenderDocStartFrame()
{ {
// _renderDocApi.API.StartFrameCapture(IntPtr.Zero, IntPtr.Zero); // _renderDocApi.API.StartFrameCapture(IntPtr.Zero, IntPtr.Zero);
} }
/// <summary>
/// Ends the current RenderDoc frame capture.
/// </summary>
public static void RenderDocEndFrame() public static void RenderDocEndFrame()
{ {
// _renderDocApi.API.EndFrameCapture(IntPtr.Zero, IntPtr.Zero); // _renderDocApi.API.EndFrameCapture(IntPtr.Zero, IntPtr.Zero);
} }
/// <summary>
/// Callback for handling OpenGL debug messages.
/// </summary>
/// <param name="parSource">The source of the debug message.</param>
/// <param name="parType">The type of the debug message.</param>
/// <param name="parId">The identifier of the debug message.</param>
/// <param name="parSeverity">The severity of the debug message.</param>
/// <param name="parLength">The length of the debug message string.</param>
/// <param name="parMessage">A pointer to the debug message string.</param>
/// <param name="parUserParam">A pointer to user-defined data.</param>
private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity, private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity,
int parLength, int parLength,
IntPtr parMessage, IntPtr parUserParam) IntPtr parMessage, IntPtr parUserParam)
@@ -73,6 +101,11 @@ internal static class Debug
); );
} }
/// <summary>
/// Maps OpenGL debug severity levels to Serilog logging levels.
/// </summary>
/// <param name="parSeverity">The severity of the OpenGL debug message.</param>
/// <returns>The corresponding Serilog log level.</returns>
private static LogEventLevel GetLogLevel(DebugSeverity parSeverity) private static LogEventLevel GetLogLevel(DebugSeverity parSeverity)
{ {
return parSeverity switch return parSeverity switch

View File

@@ -5,8 +5,15 @@ using Serilog;
namespace Engine.Graphics.Framebuffer; namespace Engine.Graphics.Framebuffer;
/// <summary>
/// Represents an OpenGL framebuffer object, which manages a collection of attachments,
/// such as color and depth buffers, and provides functionality for binding, resizing, and checking status.
/// </summary>
public class Framebuffer : OpenGlObject public class Framebuffer : OpenGlObject
{ {
/// <summary>
/// The width of the framebuffer.
/// </summary>
public int Width public int Width
{ {
get => _width; get => _width;
@@ -18,6 +25,9 @@ public class Framebuffer : OpenGlObject
} }
} }
/// <summary>
/// The height of the framebuffer.
/// </summary>
public int Height public int Height
{ {
get => _height; get => _height;
@@ -29,13 +39,32 @@ public class Framebuffer : OpenGlObject
} }
} }
/// <summary>
/// Retrieves the internal texture attached to the framebuffer at the color attachment point.
/// </summary>
internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0); internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0);
/// <summary>
/// A collection of framebuffer attachments, mapped by their attachment point.
/// </summary>
private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments; private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments;
/// <summary>
/// The width of the framebuffer.
/// </summary>
private int _width; private int _width;
/// <summary>
/// The height of the framebuffer.
/// </summary>
private int _height; private int _height;
/// <summary>
/// Initializes a new instance of the <see cref="Framebuffer"/> class with the specified width, height, and attachments.
/// </summary>
/// <param name="parWidth">The width of the framebuffer.</param>
/// <param name="parHeight">The height of the framebuffer.</param>
/// <param name="parAttachments">A dictionary of attachments to associate with the framebuffer.</param>
internal Framebuffer(int parWidth, int parHeight, internal Framebuffer(int parWidth, int parHeight,
IDictionary<FramebufferAttachment, IFramebufferAttachment> parAttachments) IDictionary<FramebufferAttachment, IFramebufferAttachment> parAttachments)
{ {
@@ -58,11 +87,23 @@ public class Framebuffer : OpenGlObject
} }
} }
/// <summary>
/// Creates a builder for creating a new framebuffer with the specified width and height.
/// </summary>
/// <param name="parWidth">The width of the framebuffer.</param>
/// <param name="parHeight">The height of the framebuffer.</param>
/// <returns>A <see cref="FramebufferBuilder"/> that can be used to customize the framebuffer.</returns>
public static FramebufferBuilder Builder(int parWidth, int parHeight) public static FramebufferBuilder Builder(int parWidth, int parHeight)
{ {
return new FramebufferBuilder(parWidth, parHeight); return new FramebufferBuilder(parWidth, parHeight);
} }
/// <summary>
/// Retrieves the attachment of the specified type from the framebuffer.
/// </summary>
/// <typeparam name="T">The type of the attachment to retrieve.</typeparam>
/// <param name="parAttachment">The attachment point to retrieve the attachment from.</param>
/// <returns>The attachment if found, otherwise <c>null</c>.</returns>
internal T? GetAttachment<T>(FramebufferAttachment parAttachment) where T : IFramebufferAttachment internal T? GetAttachment<T>(FramebufferAttachment parAttachment) where T : IFramebufferAttachment
{ {
if (!_attachments.TryGetValue(parAttachment, out var attachmentValue)) if (!_attachments.TryGetValue(parAttachment, out var attachmentValue))
@@ -78,6 +119,11 @@ public class Framebuffer : OpenGlObject
return default; return default;
} }
/// <summary>
/// Resizes the framebuffer and all its attachments to the specified width and height.
/// </summary>
/// <param name="parWidth">The new width of the framebuffer.</param>
/// <param name="parHeight">The new height of the framebuffer.</param>
public void Resize(int parWidth, int parHeight) public void Resize(int parWidth, int parHeight)
{ {
if (Width == parWidth && Height == parHeight) if (Width == parWidth && Height == parHeight)
@@ -96,16 +142,19 @@ public class Framebuffer : OpenGlObject
Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight); Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight);
} }
/// <inheritdoc/>
internal override void Bind() internal override void Bind()
{ {
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
} }
/// <inheritdoc/>
internal override void Unbind() internal override void Unbind()
{ {
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
} }
/// <inheritdoc/>
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteFramebuffer(Handle); GL.DeleteFramebuffer(Handle);

View File

@@ -4,13 +4,52 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Framebuffer; namespace Engine.Graphics.Framebuffer;
public class FramebufferBuilder(int parWidth, int parHeight) /// <summary>
/// A builder class used to construct framebuffers with custom attachments, such as color and depth buffers.
/// </summary>
public class FramebufferBuilder
{ {
/// <summary>
/// The width of the framebuffer.
/// </summary>
private readonly int _width;
/// <summary>
/// The height of the framebuffer.
/// </summary>
private readonly int _height;
/// <summary>
/// A list of attachments to be added to the framebuffer.
/// </summary>
private readonly List<AttachmentSpecification> _attachments = []; private readonly List<AttachmentSpecification> _attachments = [];
/// <summary>
/// A set of used attachment points.
/// </summary>
private readonly HashSet<FramebufferAttachment> _usedAttachments = []; private readonly HashSet<FramebufferAttachment> _usedAttachments = [];
/// <summary>
/// The current color attachment point.
/// </summary>
private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0; private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0;
/// <summary>
/// Initializes a new instance of the <see cref="FramebufferBuilder"/> class with the specified width and height.
/// </summary>
/// <param name="parWidth">The width of the framebuffer.</param>
/// <param name="parHeight">The height of the framebuffer.</param>
public FramebufferBuilder(int parWidth, int parHeight)
{
_width = parWidth;
_height = parHeight;
}
/// <summary>
/// Adds a color attachment to the framebuffer.
/// </summary>
/// <typeparam name="T">The pixel format to use for the color attachment.</typeparam>
/// <returns>The builder instance for method chaining.</returns>
public FramebufferBuilder AddColorAttachment<T>() public FramebufferBuilder AddColorAttachment<T>()
where T : struct, IPixel where T : struct, IPixel
{ {
@@ -30,6 +69,10 @@ public class FramebufferBuilder(int parWidth, int parHeight)
}); });
} }
/// <summary>
/// Adds a depth attachment to the framebuffer.
/// </summary>
/// <returns>The builder instance for method chaining.</returns>
public FramebufferBuilder AddDepthAttachment() public FramebufferBuilder AddDepthAttachment()
{ {
return AddAttachment(new RenderbufferAttachmentSpecification return AddAttachment(new RenderbufferAttachmentSpecification
@@ -39,6 +82,26 @@ public class FramebufferBuilder(int parWidth, int parHeight)
}); });
} }
/// <summary>
/// Builds the framebuffer based on the current configuration.
/// </summary>
/// <returns>A new <see cref="Framebuffer"/> instance.</returns>
public Framebuffer Build()
{
var attachments = _attachments
.ToDictionary(
parAttachment => parAttachment.FramebufferAttachment,
parAttachment => parAttachment.Create(_width, _height)
);
return new Framebuffer(_width, _height, attachments);
}
/// <summary>
/// Adds a custom attachment to the framebuffer.
/// </summary>
/// <param name="parAttachment">The attachment specification to add.</param>
/// <returns>The builder instance for method chaining.</returns>
private FramebufferBuilder AddAttachment(AttachmentSpecification parAttachment) private FramebufferBuilder AddAttachment(AttachmentSpecification parAttachment)
{ {
if (_usedAttachments.Contains(parAttachment.FramebufferAttachment)) if (_usedAttachments.Contains(parAttachment.FramebufferAttachment))
@@ -53,43 +116,67 @@ public class FramebufferBuilder(int parWidth, int parHeight)
return this; return this;
} }
public Framebuffer Build() /// <summary>
{ /// Represents a specification for a texture attachment in a framebuffer.
var attachments = _attachments /// </summary>
.ToDictionary(
parAttachment => parAttachment.FramebufferAttachment,
parAttachment => parAttachment.Create(parWidth, parHeight)
);
return new Framebuffer(parWidth, parHeight, attachments);
}
private class TextureAttachmentSpecification : AttachmentSpecification private class TextureAttachmentSpecification : AttachmentSpecification
{ {
/// <summary>
/// The pixel format for the texture attachment.
/// </summary>
public PixelFormat PixelFormat { get; init; } public PixelFormat PixelFormat { get; init; }
/// <summary>
/// The pixel type for the texture attachment.
/// </summary>
public PixelType PixelType { get; init; } public PixelType PixelType { get; init; }
/// <summary>
/// The internal format for the texture attachment.
/// </summary>
public PixelInternalFormat PixelInternalFormat { get; init; } public PixelInternalFormat PixelInternalFormat { get; init; }
/// <inheritdoc/>
public override DynamicTexture Create(int parWidth, int parHeight) public override DynamicTexture Create(int parWidth, int parHeight)
{ {
return new DynamicTexture(parWidth, parHeight, PixelFormat, PixelType, PixelInternalFormat); return new DynamicTexture(parWidth, parHeight, PixelFormat, PixelType, PixelInternalFormat);
} }
} }
/// <summary>
/// Represents a specification for a renderbuffer attachment in a framebuffer.
/// </summary>
private class RenderbufferAttachmentSpecification : AttachmentSpecification private class RenderbufferAttachmentSpecification : AttachmentSpecification
{ {
/// <summary>
/// The storage format for the renderbuffer attachment.
/// </summary>
public RenderbufferStorage RenderbufferStorage { get; init; } public RenderbufferStorage RenderbufferStorage { get; init; }
/// <inheritdoc/>
public override Renderbuffer Create(int parWidth, int parHeight) public override Renderbuffer Create(int parWidth, int parHeight)
{ {
return new Renderbuffer(parWidth, parHeight, RenderbufferStorage); return new Renderbuffer(parWidth, parHeight, RenderbufferStorage);
} }
} }
/// <summary>
/// Represents a base class for framebuffer attachment specifications.
/// Provides common functionality for creating attachments like textures or renderbuffers.
/// </summary>
private abstract class AttachmentSpecification private abstract class AttachmentSpecification
{ {
/// <summary>
/// The attachment point for the framebuffer.
/// </summary>
public FramebufferAttachment FramebufferAttachment { get; init; } public FramebufferAttachment FramebufferAttachment { get; init; }
/// <summary>
/// Creates a new attachment for the framebuffer based on the specific type of attachment.
/// </summary>
/// <param name="parWidth">The width of the attachment.</param>
/// <param name="parHeight">The height of the attachment.</param>
/// <returns>An instance of <see cref="IFramebufferAttachment"/> that represents the created attachment.</returns>
public abstract IFramebufferAttachment Create(int parWidth, int parHeight); public abstract IFramebufferAttachment Create(int parWidth, int parHeight);
} }
} }

View File

@@ -2,11 +2,27 @@
namespace Engine.Graphics.Framebuffer; namespace Engine.Graphics.Framebuffer;
/// <summary>
/// Interface representing a framebuffer attachment, providing methods to attach, resize, and manage the attachment's OpenGL handle.
/// </summary>
internal interface IFramebufferAttachment internal interface IFramebufferAttachment
{ {
/// <summary>
/// The handle that uniquely identifies the OpenGL object.
/// </summary>
int Handle { get; } int Handle { get; }
/// <summary>
/// Resizes the framebuffer attachment to the specified width and height.
/// </summary>
/// <param name="parWidth">The new width of the framebuffer attachment.</param>
/// <param name="parHeight">The new height of the framebuffer attachment.</param>
void Resize(int parWidth, int parHeight); void Resize(int parWidth, int parHeight);
/// <summary>
/// Attaches the framebuffer attachment to a specified framebuffer and attachment point.
/// </summary>
/// <param name="parFramebuffer">The framebuffer to attach to.</param>
/// <param name="parAttachment">The attachment point of the framebuffer to attach to.</param>
void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment); void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment);
} }

View File

@@ -2,13 +2,32 @@
namespace Engine.Graphics.Framebuffer; namespace Engine.Graphics.Framebuffer;
/// <summary>
/// Represents a renderbuffer object in OpenGL, which can be used as an attachment to a framebuffer.
/// </summary>
public class Renderbuffer : OpenGlObject, IFramebufferAttachment public class Renderbuffer : OpenGlObject, IFramebufferAttachment
{ {
/// <summary>
/// The width of the renderbuffer.
/// </summary>
public int Width { get; private set; } public int Width { get; private set; }
/// <summary>
/// The height of the renderbuffer.
/// </summary>
public int Height { get; private set; } public int Height { get; private set; }
/// <summary>
/// The format of the renderbuffer (e.g., depth, stencil, or color).
/// </summary>
private readonly RenderbufferStorage _format; private readonly RenderbufferStorage _format;
/// <summary>
/// Initializes a new instance of the <see cref="Renderbuffer"/> class with specified dimensions and format.
/// </summary>
/// <param name="parWidth">The width of the renderbuffer.</param>
/// <param name="parHeight">The height of the renderbuffer.</param>
/// <param name="parFormat">The format of the renderbuffer (storage format).</param>
public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat) public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat)
{ {
Width = parWidth; Width = parWidth;
@@ -21,6 +40,7 @@ public class Renderbuffer : OpenGlObject, IFramebufferAttachment
GL.NamedRenderbufferStorage(Handle, _format, Width, Height); GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
} }
/// <inheritdoc/>
public void Resize(int parWidth, int parHeight) public void Resize(int parWidth, int parHeight)
{ {
if (Width == parWidth && Height == parHeight) if (Width == parWidth && Height == parHeight)
@@ -34,21 +54,25 @@ public class Renderbuffer : OpenGlObject, IFramebufferAttachment
GL.NamedRenderbufferStorage(Handle, _format, Width, Height); GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
} }
/// <inheritdoc/>
public void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment) public void Attach(Framebuffer parFramebuffer, FramebufferAttachment parAttachment)
{ {
GL.NamedFramebufferRenderbuffer(parFramebuffer.Handle, parAttachment, RenderbufferTarget.Renderbuffer, Handle); GL.NamedFramebufferRenderbuffer(parFramebuffer.Handle, parAttachment, RenderbufferTarget.Renderbuffer, Handle);
} }
/// <inheritdoc/>
internal override void Bind() internal override void Bind()
{ {
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle); GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle);
} }
/// <inheritdoc/>
internal override void Unbind() internal override void Unbind()
{ {
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
} }
/// <inheritdoc/>
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteRenderbuffer(Handle); GL.DeleteRenderbuffer(Handle);

View File

@@ -7,21 +7,62 @@ using OpenTK.Mathematics;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// A generic renderer that supports rendering quads, meshes, and text.
/// </summary>
public class GenericRenderer : IRenderer public class GenericRenderer : IRenderer
{ {
/// <summary>
/// Provides functionality to render quads.
/// </summary>
public QuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(_engine, 1024 * 8); public QuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(_engine, 1024 * 8);
/// <summary>
/// Provides functionality to render any type of mesh.
/// </summary>
public AnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024); public AnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024);
/// <summary>
/// Provides functionality to render text.
/// </summary>
public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8); public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8);
/// <summary>
/// The framebuffer used for rendering.
/// </summary>
internal readonly Framebuffer.Framebuffer _framebuffer; internal readonly Framebuffer.Framebuffer _framebuffer;
/// <summary>
/// The engine instance associated with this renderer.
/// </summary>
private readonly Engine _engine; private readonly Engine _engine;
/// <summary>
/// The quad renderer instance, lazily initialized.
/// </summary>
private QuadRenderer? _quadRenderer; private QuadRenderer? _quadRenderer;
/// <summary>
/// The mesh renderer instance, lazily initialized.
/// </summary>
private AnyMeshRenderer? _anyMeshRenderer; private AnyMeshRenderer? _anyMeshRenderer;
/// <summary>
/// The text renderer instance, lazily initialized.
/// </summary>
private TextRenderer? _textRenderer; private TextRenderer? _textRenderer;
/// <summary>
/// Indicates whether the frame has been started.
/// </summary>
private bool _frameStarted; private bool _frameStarted;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRenderer"/> class.
/// </summary>
/// <param name="parEngine">The engine instance to use for rendering.</param>
/// <param name="parWidth">The initial width of the framebuffer.</param>
/// <param name="parHeight">The initial height of the framebuffer.</param>
public GenericRenderer(Engine parEngine, int parWidth, int parHeight) public GenericRenderer(Engine parEngine, int parWidth, int parHeight)
{ {
_engine = parEngine; _engine = parEngine;
@@ -32,11 +73,13 @@ public class GenericRenderer : IRenderer
.Build(); .Build();
} }
/// <inheritdoc/>
public void StartFrame() public void StartFrame()
{ {
_frameStarted = true; _frameStarted = true;
} }
/// <inheritdoc/>
public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{ {
if (!_frameStarted) if (!_frameStarted)
@@ -72,6 +115,7 @@ public class GenericRenderer : IRenderer
_frameStarted = false; _frameStarted = false;
} }
/// <inheritdoc/>
public void Resize(int parWidth, int parHeight) public void Resize(int parWidth, int parHeight)
{ {
_framebuffer.Resize(parWidth, parHeight); _framebuffer.Resize(parWidth, parHeight);

View File

@@ -4,14 +4,39 @@ using OpenTK.Windowing.Common;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Defines an interface for a presenter that handles updates, rendering, and presentation.
/// </summary>
public interface IPresenter : IUpdate, IRender public interface IPresenter : IUpdate, IRender
{ {
public event Action<ResizeEventArgs> Resize; /// <summary>
/// Occurs when the presenter is resized.
/// </summary>
public event Action<ResizeEventArgs> OnResize;
/// <summary>
/// Gets the current width of the presenter.
/// </summary>
public int Width { get; } public int Width { get; }
/// <summary>
/// Gets the current height of the presenter.
/// </summary>
public int Height { get; } public int Height { get; }
/// <summary>
/// Gets a value indicating whether the presenter is exiting.
/// </summary>
public bool IsExiting { get; } public bool IsExiting { get; }
/// <summary>
/// Presents a texture to the rendering surface.
/// </summary>
/// <param name="parTexture">The texture to present.</param>
public void Present(IConstTexture parTexture); public void Present(IConstTexture parTexture);
/// <summary>
/// Signals the presenter to exit.
/// </summary>
public void Exit(); public void Exit();
} }

View File

@@ -2,10 +2,27 @@
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Interface defining the essential functionality for a renderer.
/// </summary>
internal interface IRenderer internal interface IRenderer
{ {
/// <summary>
/// Prepares the renderer for a new frame.
/// </summary>
public void StartFrame(); public void StartFrame();
/// <summary>
/// Finalizes the rendering pipeline for the current frame.
/// </summary>
/// <param name="parProjectionMatrix">The projection matrix to use for rendering.</param>
/// <param name="parViewMatrix">The view matrix to use for rendering.</param>
public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix); public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix);
/// <summary>
/// Resizes the renderer to accommodate changes in viewport dimensions.
/// </summary>
/// <param name="parWidth">The new width of the viewport.</param>
/// <param name="parHeight">The new height of the viewport.</param>
public void Resize(int parWidth, int parHeight); public void Resize(int parWidth, int parHeight);
} }

View File

@@ -2,15 +2,34 @@
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Represents an abstract OpenGL object with a handle for managing GPU resources.
/// </summary>
public abstract class OpenGlObject public abstract class OpenGlObject
{ {
/// <summary>
/// The handle that uniquely identifies the OpenGL object.
/// </summary>
public int Handle { get; protected set; } = -1; public int Handle { get; protected set; } = -1;
/// <summary>
/// Binds the OpenGL object for rendering or other operations.
/// </summary>
internal abstract void Bind(); internal abstract void Bind();
/// <summary>
/// Unbinds the OpenGL object, restoring the previous state.
/// </summary>
internal abstract void Unbind(); internal abstract void Unbind();
/// <summary>
/// Releases the GPU resources associated with the OpenGL object.
/// </summary>
protected abstract void Destroy(); protected abstract void Destroy();
/// <summary>
/// Finalizer to ensure the OpenGL object is destroyed and resources are freed.
/// </summary>
~OpenGlObject() ~OpenGlObject()
{ {
if (Handle == -1) if (Handle == -1)

View File

@@ -1,32 +1,64 @@
namespace Engine.Graphics.Pipeline; namespace Engine.Graphics.Pipeline;
/// <summary>
/// Represents a render layer in the rendering pipeline, providing a way to order and compare render layers.
/// </summary>
public class RenderLayer : IComparable<RenderLayer> public class RenderLayer : IComparable<RenderLayer>
{ {
/// <summary>
/// The default render layer, with a priority of 0.
/// </summary>
public static readonly RenderLayer DEFAULT = new("default", 0); public static readonly RenderLayer DEFAULT = new("default", 0);
/// <summary>
/// The overlay render layer, with a priority of 1.
/// </summary>
public static readonly RenderLayer OVERLAY = new("overlay", 1); public static readonly RenderLayer OVERLAY = new("overlay", 1);
/// <summary>
/// The HUD render layer, with a priority of 2.
/// </summary>
public static readonly RenderLayer HUD = new("hud", 2); public static readonly RenderLayer HUD = new("hud", 2);
/// <summary>
/// A read-only list of all render layers.
/// </summary>
public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, OVERLAY, HUD }.AsReadOnly(); public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, OVERLAY, HUD }.AsReadOnly();
/// <summary>
/// The name of the render layer.
/// </summary>
public string Name { get; } public string Name { get; }
/// <summary>
/// The priority of the render layer.
/// </summary>
private readonly int _order; private readonly int _order;
/// <summary>
/// Initializes a new instance of the <see cref="RenderLayer"/> class with the specified name and priority.
/// </summary>
/// <param name="parName">The name of the render layer.</param>
/// <param name="parOrder">The priority of the render layer.</param>
private RenderLayer(string parName, int parOrder) private RenderLayer(string parName, int parOrder)
{ {
Name = parName; Name = parName;
_order = parOrder; _order = parOrder;
} }
/// <inheritdoc/>
public int CompareTo(RenderLayer? parOther) public int CompareTo(RenderLayer? parOther)
{ {
return parOther == null ? 1 : _order.CompareTo(parOther._order); return parOther == null ? 1 : _order.CompareTo(parOther._order);
} }
/// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return Name; return Name;
} }
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
return Name.GetHashCode(); return Name.GetHashCode();

View File

@@ -2,11 +2,29 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Pixel; namespace Engine.Graphics.Pixel;
/// <summary>
/// Defines the basic structure for a pixel type, which includes information
/// about the pixel's format, type, internal format, and sized internal format.
/// </summary>
public interface IPixel public interface IPixel
{ {
PixelFormat Format { get; } /// <summary>
PixelType Type { get; } /// The format of the pixel (e.g., Red, RGB, RGBA).
/// </summary>
public PixelFormat Format { get; }
PixelInternalFormat InternalFormat { get; } /// <summary>
SizedInternalFormat SizedInternalFormat { get; } /// The type of the pixel data (e.g., UnsignedByte).
/// </summary>
public PixelType Type { get; }
/// <summary>
/// The internal format for the pixel (e.g., R8, RGB8, etc.).
/// </summary>
public PixelInternalFormat InternalFormat { get; }
/// <summary>
/// The sized internal format for the pixel (e.g., R8, RGB8, etc.).
/// </summary>
public SizedInternalFormat SizedInternalFormat { get; }
} }

View File

@@ -3,14 +3,27 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Pixel; namespace Engine.Graphics.Pixel;
/// <summary>
/// Represents a single-channel red pixel with an 8-bit format.
/// Implements the <see cref="IPixel"/> interface for defining pixel properties.
/// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct R8 : IPixel public struct R8 : IPixel
{ {
/// <inheritdoc/>
public PixelFormat Format => PixelFormat.Red; public PixelFormat Format => PixelFormat.Red;
/// <inheritdoc/>
public PixelType Type => PixelType.UnsignedByte; public PixelType Type => PixelType.UnsignedByte;
/// <inheritdoc/>
public PixelInternalFormat InternalFormat => PixelInternalFormat.R8; public PixelInternalFormat InternalFormat => PixelInternalFormat.R8;
/// <inheritdoc/>
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8; public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8;
/// <summary>
/// The red channel of the pixel.
/// </summary>
public byte R; public byte R;
} }

View File

@@ -6,15 +6,33 @@ namespace Engine.Graphics.Pixel;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct Rgb8 : IPixel public struct Rgb8 : IPixel
{ {
/// <inheritdoc/>
public PixelFormat Format => PixelFormat.Rgb; public PixelFormat Format => PixelFormat.Rgb;
/// <inheritdoc/>
public PixelType Type => PixelType.UnsignedByte; public PixelType Type => PixelType.UnsignedByte;
/// <inheritdoc/>
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8; public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8;
/// <inheritdoc/>
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8; public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8;
/// <summary>
/// The red channel of the pixel.
/// </summary>
public byte R; public byte R;
/// <summary>
/// The green channel of the pixel.
/// </summary>
public byte G; public byte G;
/// <summary>
/// The blue channel of the pixel.
/// </summary>
public byte B; public byte B;
/// <inheritdoc/>
public override string ToString() => $"{R}, {G}, {B}"; public override string ToString() => $"{R}, {G}, {B}";
} }

View File

@@ -3,19 +3,45 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Pixel; namespace Engine.Graphics.Pixel;
/// <summary>
/// Represents a four-channel RGBA pixel with an 8-bit format for each channel.
/// Implements the <see cref="IPixel"/> interface for defining pixel properties.
/// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct Rgba8 : IPixel public struct Rgba8 : IPixel
{ {
/// <inheritdoc/>
public PixelFormat Format => PixelFormat.Rgba; public PixelFormat Format => PixelFormat.Rgba;
/// <inheritdoc/>
public PixelType Type => PixelType.UnsignedByte; public PixelType Type => PixelType.UnsignedByte;
/// <inheritdoc/>
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgba8; public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgba8;
/// <inheritdoc/>
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgba8; public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgba8;
/// <summary>
/// The red channel of the pixel.
/// </summary>
public byte R; public byte R;
/// <summary>
/// The green channel of the pixel.
/// </summary>
public byte G; public byte G;
/// <summary>
/// The blue channel of the pixel.
/// </summary>
public byte B; public byte B;
/// <summary>
/// The alpha channel of the pixel.
/// </summary>
public byte A; public byte A;
/// <inheritdoc/>
public override string ToString() => $"{R}, {G}, {B}, {A}"; public override string ToString() => $"{R}, {G}, {B}, {A}";
} }

View File

@@ -6,22 +6,70 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render; namespace Engine.Graphics.Render;
/// <summary>
/// Base class for rendering vertices with support for instancing.
/// This class is generic, allowing customization for the common vertex type <typeparamref name="C"/>
/// and the instance-specific vertex type <typeparamref name="I"/>.
/// </summary>
/// <typeparam name="C">The type of the common vertex, must implement <see cref="IVertex"/>.</typeparam>
/// <typeparam name="I">The type of the instance vertex, must implement <see cref="IVertex"/>.</typeparam>
public abstract class InstancedRenderer<C, I> public abstract class InstancedRenderer<C, I>
where C : struct, IVertex where C : struct, IVertex
where I : struct, IVertex where I : struct, IVertex
{ {
/// <summary>
/// The number of instances that can be rendered.
/// </summary>
protected readonly int _instanceCount; protected readonly int _instanceCount;
/// <summary>
/// The array of instance vertices, each vertex is of type <typeparamref name="I"/>.
/// </summary>
protected readonly I[] _instanceVertices; protected readonly I[] _instanceVertices;
/// <summary>
/// The count of instances that are queued for rendering.
/// </summary>
protected int _queuedInstanceCount; protected int _queuedInstanceCount;
/// <summary>
/// The type of primitive to be rendered (e.g., triangles, lines).
/// </summary>
private readonly PrimitiveType _primitiveType; private readonly PrimitiveType _primitiveType;
/// <summary>
/// The shader program used for rendering.
/// </summary>
private readonly Program _program; private readonly Program _program;
/// <summary>
/// The index buffer used for the rendering of indexed primitives.
/// </summary>
private readonly IndexBuffer _indexBuffer; private readonly IndexBuffer _indexBuffer;
/// <summary>
/// The buffer containing the common vertex data, of type <typeparamref name="C"/>.
/// </summary>
private readonly VertexBuffer<C> _commonVertexBuffer; private readonly VertexBuffer<C> _commonVertexBuffer;
/// <summary>
/// The buffer containing the instance-specific vertex data, of type <typeparamref name="I"/>.
/// </summary>
private readonly VertexBuffer<I> _instanceVertexBuffer; private readonly VertexBuffer<I> _instanceVertexBuffer;
/// <summary>
/// The vertex array object that binds all the buffers for rendering.
/// </summary>
private readonly VertexArray _vertexArray; private readonly VertexArray _vertexArray;
/// <summary>
/// Initializes a new instance of the <see cref="InstancedRenderer{C, I}"/> class.
/// </summary>
/// <param name="parPrimitiveType">The type of primitive to be rendered.</param>
/// <param name="parInstanceCount">The total number of instances to be rendered.</param>
/// <param name="parIndexBuffer">An array of indices for indexed rendering.</param>
/// <param name="parInstanceBuffer">An array of instance-specific vertex data.</param>
/// <param name="parProgram">The shader program to use for rendering.</param>
protected InstancedRenderer(PrimitiveType parPrimitiveType, int parInstanceCount, protected InstancedRenderer(PrimitiveType parPrimitiveType, int parInstanceCount,
uint[] parIndexBuffer, C[] parInstanceBuffer, uint[] parIndexBuffer, C[] parInstanceBuffer,
Program parProgram) Program parProgram)
@@ -43,6 +91,11 @@ public abstract class InstancedRenderer<C, I>
_vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1); _vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1);
} }
/// <summary>
/// Renders the instanced objects with the provided projection and view matrices.
/// </summary>
/// <param name="parProjectionMatrix">The projection matrix to be used in the shader.</param>
/// <param name="parViewMatrix">The view matrix to be used in the shader.</param>
public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{ {
if (_queuedInstanceCount <= 0) if (_queuedInstanceCount <= 0)
@@ -67,15 +120,26 @@ public abstract class InstancedRenderer<C, I>
_queuedInstanceCount); _queuedInstanceCount);
} }
/// <summary>
/// Resets the renderer, clearing the queued instance count.
/// </summary>
public virtual void Reset() public virtual void Reset()
{ {
_queuedInstanceCount = 0; _queuedInstanceCount = 0;
} }
/// <summary>
/// Allows derived classes to set additional uniforms for the shader program.
/// </summary>
/// <param name="parProgram">The shader program to set additional uniforms for.</param>
protected virtual void SetAdditionalUniforms(Program parProgram) protected virtual void SetAdditionalUniforms(Program parProgram)
{ {
} }
/// <summary>
/// Determines whether the instance data has changed and needs to be uploaded to the GPU.
/// </summary>
/// <returns><c>true</c> if the data has changed; otherwise, <c>false</c>.</returns>
protected virtual bool DataChanged() protected virtual bool DataChanged()
{ {
return true; return true;

View File

@@ -3,13 +3,50 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Mesh; namespace Engine.Graphics.Render.Mesh;
public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount) /// <summary>
/// A renderer class that manages multiple meshes and delegates rendering to individual mesh renderers.
/// Handles batching of mesh instances and ensures that only the necessary mesh renderers are created.
/// </summary>
public class AnyMeshRenderer
{ {
/// <summary>
/// A dictionary that maps each mesh to its corresponding <see cref="MeshRenderer"/>.
/// </summary>
private readonly Dictionary<Asset.Mesh.Mesh, MeshRenderer> _meshRenderers = new(); private readonly Dictionary<Asset.Mesh.Mesh, MeshRenderer> _meshRenderers = new();
/// <summary>
/// A hash set that tracks the meshes that were rendered in the current frame.
/// </summary>
private readonly HashSet<Asset.Mesh.Mesh> _frameMeshes = []; private readonly HashSet<Asset.Mesh.Mesh> _frameMeshes = [];
private readonly Program _program = parEngine.EngineResourceManager.Load<Program>("shader/mesh"); /// <summary>
/// The shader program used for rendering meshes.
/// </summary>
private readonly Program _program;
/// <summary>
/// The maximum number of instances that can be rendered in a single frame.
/// </summary>
private readonly int _maxInstanceCount;
/// <summary>
/// Initializes a new instance of the <see cref="AnyMeshRenderer"/> class.
/// </summary>
/// <param name="parEngine">The engine used to load resources.</param>
/// <param name="parMaxInstanceCount">The maximum number of instances to render per frame.</param>
public AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
{
_maxInstanceCount = parMaxInstanceCount;
_program = parEngine.EngineResourceManager.Load<Program>("shader/mesh");
}
/// <summary>
/// Commits an instance of a mesh to the renderer, adding it to the render queue with the specified model matrix and optional texture.
/// If the mesh is not already being tracked, a new <see cref="MeshRenderer"/> will be created for it.
/// </summary>
/// <param name="parMesh">The mesh to render.</param>
/// <param name="parModelMatrix">The model transformation matrix to apply to the mesh.</param>
/// <param name="parAlbedo">An optional texture to apply to the mesh. If null, no texture is applied.</param>
public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null) public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null)
{ {
if (_meshRenderers.TryGetValue(parMesh, out var meshRenderer)) if (_meshRenderers.TryGetValue(parMesh, out var meshRenderer))
@@ -18,7 +55,7 @@ public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
} }
else else
{ {
var newMeshRenderer = new MeshRenderer(parMesh, parMaxInstanceCount, _program); var newMeshRenderer = new MeshRenderer(parMesh, _maxInstanceCount, _program);
newMeshRenderer.Commit(parModelMatrix, parAlbedo); newMeshRenderer.Commit(parModelMatrix, parAlbedo);
_meshRenderers.Add(parMesh, newMeshRenderer); _meshRenderers.Add(parMesh, newMeshRenderer);
@@ -27,6 +64,11 @@ public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
_frameMeshes.Add(parMesh); _frameMeshes.Add(parMesh);
} }
/// <summary>
/// Renders all queued mesh instances by calling the <see cref="MeshRenderer.Render"/> method for each mesh renderer.
/// </summary>
/// <param name="parProjectionMatrix">The projection matrix to use for rendering.</param>
/// <param name="parViewMatrix">The view matrix to use for rendering.</param>
public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{ {
foreach (var meshRenderer in _meshRenderers.Values) foreach (var meshRenderer in _meshRenderers.Values)
@@ -35,6 +77,9 @@ public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
} }
} }
/// <summary>
/// Resets the state of the mesh renderers and removes any meshes that were not rendered in the current frame.
/// </summary>
public void Reset() public void Reset()
{ {
foreach (var meshRenderer in _meshRenderers.Values) foreach (var meshRenderer in _meshRenderers.Values)

View File

@@ -4,11 +4,23 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Mesh; namespace Engine.Graphics.Render.Mesh;
/// <summary>
/// Represents an instance vertex structure for a mesh.
/// This vertex structure includes texture ID and model matrix.
/// </summary>
public struct MeshInstanceVertex : IVertex public struct MeshInstanceVertex : IVertex
{ {
/// <summary>
/// The texture ID of the vertex.
/// </summary>
[Vertex(VertexAttribType.Int)] public int _textureId; [Vertex(VertexAttribType.Int)] public int _textureId;
/// <summary>
/// The model matrix of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix; [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(_textureId, _modelMatrix); return HashCode.Combine(_textureId, _modelMatrix);

View File

@@ -5,21 +5,53 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Mesh; namespace Engine.Graphics.Render.Mesh;
public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program parProgram) /// <summary>
: InstancedRenderer<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>( /// A renderer class for rendering meshes using instancing.
PrimitiveType.Triangles, /// Handles the instancing process for meshes, including texture binding and model transformations.
parInstanceCount, /// </summary>
public class MeshRenderer : InstancedRenderer<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>
{
/// <summary>
/// Maps textures to texture units with a limit of 16 texture units.
/// </summary>
private readonly TextureUnitMap _textureUnitMap = new(16);
/// <summary>
/// Stores the texture unit indices used for binding textures.
/// </summary>
private readonly int[] _textureUnitIndices = new int[16];
/// <summary>
/// Stores the hash of the current frame, used for detecting changes in instance data.
/// </summary>
private int _frameHash;
/// <summary>
/// Stores the hash of the previous frame.
/// </summary>
private int _previousHash;
/// <summary>
/// Initializes a new instance of the <see cref="MeshRenderer"/> class.
/// </summary>
/// <param name="parMesh">The mesh to render.</param>
/// <param name="parInstanceCount">The number of instances to render.</param>
/// <param name="parProgram">The shader program to use for rendering the mesh.</param>
public MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program parProgram)
: base(
PrimitiveType.Triangles, parInstanceCount,
parMesh.Indices.ToArray(), parMesh.Indices.ToArray(),
parMesh.Vertices.ToArray(), parMesh.Vertices.ToArray(),
parProgram parProgram
) )
{ {
private readonly TextureUnitMap _textureUnitMap = new(16); }
private readonly int[] _textureUnitIndices = new int[16];
private int _frameHash;
private int _previousHash;
/// <summary>
/// Commits an instance to the renderer, adding it to the queue with the specified model matrix and optional texture.
/// </summary>
/// <param name="parModelMatrix">The model transformation matrix for this instance.</param>
/// <param name="parTexture">An optional texture to apply to the mesh. If null, no texture is applied.</param>
public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null) public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null)
{ {
if (_queuedInstanceCount >= _instanceCount) if (_queuedInstanceCount >= _instanceCount)
@@ -41,6 +73,16 @@ public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program
_queuedInstanceCount++; _queuedInstanceCount++;
} }
/// <inheritdoc/>
public override void Reset()
{
base.Reset();
_textureUnitMap.Reset();
_previousHash = _frameHash;
_frameHash = 0;
}
/// <inheritdoc/>
protected override void SetAdditionalUniforms(Program parProgram) protected override void SetAdditionalUniforms(Program parProgram)
{ {
foreach (var (texture, unit) in _textureUnitMap.Textures) foreach (var (texture, unit) in _textureUnitMap.Textures)
@@ -52,16 +94,9 @@ public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program
parProgram.SetUniform("uTexture", _textureUnitIndices); parProgram.SetUniform("uTexture", _textureUnitIndices);
} }
/// <inheritdoc/>
protected override bool DataChanged() protected override bool DataChanged()
{ {
return _frameHash != _previousHash; return _frameHash != _previousHash;
} }
public override void Reset()
{
base.Reset();
_textureUnitMap.Reset();
_previousHash = _frameHash;
_frameHash = 0;
}
} }

View File

@@ -4,8 +4,19 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Quad; namespace Engine.Graphics.Render.Quad;
/// <summary>
/// Represents a common vertex structure for a quad.
/// This vertex structure includes position and UV coordinates.
/// </summary>
public struct QuadCommonVertex : IVertex public struct QuadCommonVertex : IVertex
{ {
/// <summary>
/// The position of the vertex in 3D space.
/// </summary>
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position; [Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
/// <summary>
/// The UV coordinates of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv; [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
} }

View File

@@ -4,12 +4,28 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Quad; namespace Engine.Graphics.Render.Quad;
/// <summary>
/// Represents an instance vertex structure for a quad.
/// This vertex structure includes color, texture ID, and model matrix.
/// </summary>
public struct QuadInstanceVertex : IVertex public struct QuadInstanceVertex : IVertex
{ {
/// <summary>
/// The color of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color; [Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
/// <summary>
/// The texture ID of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float)] public float _textureId; [Vertex(VertexAttribType.Float)] public float _textureId;
/// <summary>
/// The model matrix of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix; [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(_color, _textureId, _modelMatrix); return HashCode.Combine(_color, _textureId, _modelMatrix);

View File

@@ -5,14 +5,37 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Quad; namespace Engine.Graphics.Render.Quad;
/// <summary>
/// A renderer class for rendering quadrilaterals (quads) using instancing.
/// Supports dynamic texture binding and manages the state for rendering multiple instances of quads.
/// </summary>
public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex> public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex>
{ {
/// <summary>
/// Maps textures to texture units with a limit of 16 texture units.
/// </summary>
private readonly TextureUnitMap _textureUnitMap = new(16); private readonly TextureUnitMap _textureUnitMap = new(16);
/// <summary>
/// Stores the texture unit indices used for binding textures.
/// </summary>
private readonly int[] _textureUnitIndices = new int[16]; private readonly int[] _textureUnitIndices = new int[16];
/// <summary>
/// Stores the hash of the current frame, used for detecting changes in instance data.
/// </summary>
private int _frameHash; private int _frameHash;
/// <summary>
/// Stores the hash of the previous frame.
/// </summary>
private int _previousHash; private int _previousHash;
/// <summary>
/// Initializes a new instance of the <see cref="QuadRenderer"/> class.
/// </summary>
/// <param name="parEngine">The engine used to load resources.</param>
/// <param name="parInstanceCount">The number of instances to render.</param>
public QuadRenderer(Engine parEngine, int parInstanceCount) public QuadRenderer(Engine parEngine, int parInstanceCount)
: base(PrimitiveType.Triangles, parInstanceCount, [0, 2, 1, 2, 3, 1], [ : base(PrimitiveType.Triangles, parInstanceCount, [0, 2, 1, 2, 3, 1], [
new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) }, new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) },
@@ -24,6 +47,12 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
{ {
} }
/// <summary>
/// Commits an instance to the renderer, adding it to the queue with the specified model matrix, color, and optional texture.
/// </summary>
/// <param name="parModelMatrix">The model transformation matrix for this instance.</param>
/// <param name="parColor">The color to apply to this instance.</param>
/// <param name="parTexture">An optional texture to apply to the quad. If null, no texture is applied.</param>
public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture.Texture? parTexture = null) public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture.Texture? parTexture = null)
{ {
if (_queuedInstanceCount >= _instanceCount) if (_queuedInstanceCount >= _instanceCount)
@@ -46,6 +75,16 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
_queuedInstanceCount++; _queuedInstanceCount++;
} }
/// <inheritdoc/>
public override void Reset()
{
base.Reset();
_textureUnitMap.Reset();
_previousHash = _frameHash;
_frameHash = 0;
}
/// <inheritdoc/>
protected override void SetAdditionalUniforms(Program parProgram) protected override void SetAdditionalUniforms(Program parProgram)
{ {
foreach (var (texture, unit) in _textureUnitMap.Textures) foreach (var (texture, unit) in _textureUnitMap.Textures)
@@ -57,16 +96,9 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
parProgram.SetUniform("uTexture", _textureUnitIndices); parProgram.SetUniform("uTexture", _textureUnitIndices);
} }
/// <inheritdoc/>
protected override bool DataChanged() protected override bool DataChanged()
{ {
return _frameHash != _previousHash; return _frameHash != _previousHash;
} }
public override void Reset()
{
base.Reset();
_textureUnitMap.Reset();
_previousHash = _frameHash;
_frameHash = 0;
}
} }

View File

@@ -4,10 +4,29 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Text; namespace Engine.Graphics.Render.Text;
/// <summary>
/// Represents a common vertex structure for a glyph.
/// This vertex structure includes color, atlas ID, unit range, and model matrix.
/// </summary>
public struct GlyphCommonVertex : IVertex public struct GlyphCommonVertex : IVertex
{ {
/// <summary>
/// The color of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color; [Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
/// <summary>
/// The atlas ID of the vertex.
/// </summary>
[Vertex(VertexAttribType.Int)] public int _atlasId; [Vertex(VertexAttribType.Int)] public int _atlasId;
/// <summary>
/// The unit range of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange; [Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange;
/// <summary>
/// The model matrix of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix; [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
} }

View File

@@ -4,12 +4,39 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Text; namespace Engine.Graphics.Render.Text;
/// <summary>
/// Represents a vertex structure for a glyph.
/// This vertex structure includes position, UV coordinates, color, atlas ID, unit range, and model matrix.
/// </summary>
public struct GlyphVertex : IVertex public struct GlyphVertex : IVertex
{ {
/// <summary>
/// The position of the vertex in 2D space.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _position; [Vertex(VertexAttribType.Float, 2)] public Vector2 _position;
/// <summary>
/// The UV coordinates of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv; [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
/// <summary>
/// The color of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color; [Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
/// <summary>
/// The atlas ID of the vertex.
/// </summary>
[Vertex(VertexAttribType.Int)] public int _atlasId; [Vertex(VertexAttribType.Int)] public int _atlasId;
/// <summary>
/// The unit range of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange; [Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange;
/// <summary>
/// The model matrix of the vertex.
/// </summary>
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix; [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
} }

View File

@@ -7,21 +7,62 @@ using OpenTK.Mathematics;
namespace Engine.Graphics.Render.Text; namespace Engine.Graphics.Render.Text;
/// <summary>
/// A renderer class for rendering text using glyphs from a font atlas.
/// Handles dynamic font rendering with support for textures.
/// </summary>
public class TextRenderer public class TextRenderer
{ {
/// <summary>
/// The shader program used for rendering the text.
/// </summary>
private readonly Program _program; private readonly Program _program;
/// <summary>
/// The index buffer used for rendering the text glyphs.
/// </summary>
private readonly IndexBuffer _indexBuffer; private readonly IndexBuffer _indexBuffer;
/// <summary>
/// The buffer holding vertex data for all glyphs to be rendered.
/// </summary>
private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer; private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer;
/// <summary>
/// The vertex array object that binds the index and vertex buffers.
/// </summary>
private readonly VertexArray _vertexArray; private readonly VertexArray _vertexArray;
/// <summary>
/// The number of characters that can be rendered at once.
/// </summary>
private readonly int _characterCount; private readonly int _characterCount;
/// <summary>
/// The array of glyph vertices representing the text to be rendered.
/// </summary>
private readonly GlyphVertex[] _glyphVertices; private readonly GlyphVertex[] _glyphVertices;
/// <summary>
/// Maps textures to texture units for rendering the font atlas.
/// </summary>
private readonly TextureUnitMap _textureUnitMap = new(16); private readonly TextureUnitMap _textureUnitMap = new(16);
/// <summary>
/// Stores the texture unit indices for binding textures.
/// </summary>
private readonly int[] _textureUnitIndices = new int[16]; private readonly int[] _textureUnitIndices = new int[16];
/// <summary>
/// The number of characters that have been queued for rendering.
/// </summary>
private int _queuedCharacterCount; private int _queuedCharacterCount;
/// <summary>
/// Initializes a new instance of the <see cref="TextRenderer"/> class.
/// </summary>
/// <param name="parEngine">The engine used to load resources.</param>
/// <param name="parCharacterCount">The maximum number of characters to render.</param>
public TextRenderer(Engine parEngine, int parCharacterCount) public TextRenderer(Engine parEngine, int parCharacterCount)
{ {
_characterCount = parCharacterCount; _characterCount = parCharacterCount;
@@ -39,6 +80,13 @@ public class TextRenderer
_vertexArray.BindVertexBuffer(_glyphVertexBuffer); _vertexArray.BindVertexBuffer(_glyphVertexBuffer);
} }
/// <summary>
/// Commits a string of text to the renderer, creating the necessary glyphs and adding them to the render queue.
/// </summary>
/// <param name="parFont">The font to use for rendering the text.</param>
/// <param name="parText">The text string to render.</param>
/// <param name="parColor">The color to apply to the text.</param>
/// <param name="parModelMatrix">The model transformation matrix to apply to the text.</param>
public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix) public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix)
{ {
if (_queuedCharacterCount >= _characterCount) if (_queuedCharacterCount >= _characterCount)
@@ -66,6 +114,11 @@ public class TextRenderer
} }
} }
/// <summary>
/// Renders the queued text characters to the screen with the given projection and view matrices.
/// </summary>
/// <param name="parProjectionMatrix">The projection matrix to use for the render.</param>
/// <param name="parViewMatrix">The view matrix to use for the render.</param>
public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix) public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{ {
if (_queuedCharacterCount <= 0) if (_queuedCharacterCount <= 0)
@@ -90,12 +143,20 @@ public class TextRenderer
GL.DrawElements(PrimitiveType.Triangles, _queuedCharacterCount * 6, DrawElementsType.UnsignedInt, 0); GL.DrawElements(PrimitiveType.Triangles, _queuedCharacterCount * 6, DrawElementsType.UnsignedInt, 0);
} }
/// <summary>
/// Resets the text renderer, clearing all queued characters and texture unit mappings.
/// </summary>
public void Reset() public void Reset()
{ {
_textureUnitMap.Reset(); _textureUnitMap.Reset();
_queuedCharacterCount = 0; _queuedCharacterCount = 0;
} }
/// <summary>
/// Creates the indices for rendering the glyphs.
/// </summary>
/// <param name="parCharacterCount">The number of characters to render.</param>
/// <returns>An array of indices for rendering the glyphs.</returns>
private static uint[] CreateIndices(int parCharacterCount) private static uint[] CreateIndices(int parCharacterCount)
{ {
var indices = new uint[parCharacterCount * 6]; var indices = new uint[parCharacterCount * 6];

View File

@@ -7,22 +7,63 @@ using OpenTK.Windowing.Desktop;
namespace Engine.Graphics; namespace Engine.Graphics;
/// <summary>
/// Handles the rendering pipeline, manages render layers, and provides tools for rendering graphics in the engine.
/// </summary>
public class Renderer public class Renderer
{ {
/// <summary>
/// The width of the viewport.
/// </summary>
public int ViewportWidth => RenderFramebuffer.Width; public int ViewportWidth => RenderFramebuffer.Width;
/// <summary>
/// The height of the viewport.
/// </summary>
public int ViewportHeight => RenderFramebuffer.Height; public int ViewportHeight => RenderFramebuffer.Height;
/// <summary>
/// The framebuffer used for rendering.
/// </summary>
internal Framebuffer.Framebuffer RenderFramebuffer { get; } internal Framebuffer.Framebuffer RenderFramebuffer { get; }
/// <summary>
/// The texture of the render framebuffer.
/// </summary>
internal Texture.Texture RenderTexture => RenderFramebuffer.TextureInternal!; internal Texture.Texture RenderTexture => RenderFramebuffer.TextureInternal!;
/// <summary>
/// The native OpenGL window.
/// </summary>
internal NativeWindow NativeWindow { get; } internal NativeWindow NativeWindow { get; }
/// <summary>
/// Stores renderers for each render layer in a sorted dictionary.
/// </summary>
private readonly SortedDictionary<RenderLayer, GenericRenderer> _renderers = new(); private readonly SortedDictionary<RenderLayer, GenericRenderer> _renderers = new();
/// <summary>
/// The thread dedicated to rendering.
/// </summary>
private readonly Thread _renderThread; private readonly Thread _renderThread;
/// <summary>
/// Renderer for quad-based primitives.
/// </summary>
private QuadRenderer QuadRenderer { get; } private QuadRenderer QuadRenderer { get; }
/// <summary>
/// Queue of actions scheduled to run on the render thread.
/// </summary>
private readonly Queue<Action> _scheduleActions = new(); private readonly Queue<Action> _scheduleActions = new();
/// <summary>
/// Initializes the renderer with the specified engine, dimensions, and native window settings.
/// </summary>
/// <param name="parEngine">The engine instance.</param>
/// <param name="parWidth">The width of the rendering viewport.</param>
/// <param name="parHeight">The height of the rendering viewport.</param>
/// <param name="parSettings">Settings for the native window.</param>
public Renderer(Engine parEngine, int parWidth, int parHeight, NativeWindowSettings parSettings) public Renderer(Engine parEngine, int parWidth, int parHeight, NativeWindowSettings parSettings)
{ {
_renderThread = Thread.CurrentThread; _renderThread = Thread.CurrentThread;
@@ -48,6 +89,12 @@ public class Renderer
} }
} }
/// <summary>
/// Retrieves the renderer for the specified render layer.
/// </summary>
/// <param name="parRenderLayer">The render layer to retrieve.</param>
/// <returns>The <see cref="GenericRenderer"/> for the specified render layer.</returns>
/// <exception cref="InvalidOperationException">Thrown if the render layer does not exist.</exception>
public GenericRenderer this[RenderLayer parRenderLayer] public GenericRenderer this[RenderLayer parRenderLayer]
{ {
get get
@@ -61,24 +108,11 @@ public class Renderer
} }
} }
private void InitializeOpenGl(int parWidth, int parHeight) /// <summary>
{ /// Ensures that the current thread is the render thread.
#if DEBUG /// </summary>
Debug.Setup(); /// <exception cref="InvalidOperationException">Thrown if not on the render thread in debug mode.</exception>
#endif internal void EnsureRenderThread()
GL.Enable(EnableCap.DepthTest);
// GL.Enable(EnableCap.CullFace);
// GL.Enable(EnableCap.FramebufferSrgb);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Viewport(0, 0, parWidth, parHeight);
}
public void EnsureRenderThread()
{ {
#if DEBUG #if DEBUG
if (Thread.CurrentThread != _renderThread) if (Thread.CurrentThread != _renderThread)
@@ -88,11 +122,21 @@ public class Renderer
#endif #endif
} }
/// <summary>
/// Schedules an action to be executed on the render thread.
/// </summary>
/// <param name="parAction">The action to execute.</param>
internal void Schedule(Action parAction) internal void Schedule(Action parAction)
{ {
_scheduleActions.Enqueue(parAction); _scheduleActions.Enqueue(parAction);
} }
/// <summary>
/// Schedules a function to be executed on the render thread and returns a task representing the result.
/// </summary>
/// <typeparam name="T">The return type of the function.</typeparam>
/// <param name="parAction">The function to execute.</param>
/// <returns>A task representing the function's result.</returns>
internal Task<T> Schedule<T>(Func<T> parAction) internal Task<T> Schedule<T>(Func<T> parAction)
{ {
var completionSource = new TaskCompletionSource<T>(); var completionSource = new TaskCompletionSource<T>();
@@ -112,6 +156,9 @@ public class Renderer
return completionSource.Task; return completionSource.Task;
} }
/// <summary>
/// Prepares the rendering pipeline for a new frame.
/// </summary>
internal void StartFrame() internal void StartFrame()
{ {
EnsureRenderThread(); EnsureRenderThread();
@@ -122,6 +169,10 @@ public class Renderer
} }
} }
/// <summary>
/// Finalizes the rendering pipeline for the current frame.
/// </summary>
/// <param name="parMatrices">A dictionary of matrices for render layers.</param>
internal void EndFrame(Dictionary<RenderLayer, (Matrix4, Matrix4)> parMatrices) internal void EndFrame(Dictionary<RenderLayer, (Matrix4, Matrix4)> parMatrices)
{ {
EnsureRenderThread(); EnsureRenderThread();
@@ -158,6 +209,11 @@ public class Renderer
RenderFramebuffer.Unbind(); RenderFramebuffer.Unbind();
} }
/// <summary>
/// Resizes the rendering viewport and updates associated resources.
/// </summary>
/// <param name="parWidth">The new width of the viewport.</param>
/// <param name="parHeight">The new height of the viewport.</param>
internal void Resize(int parWidth, int parHeight) internal void Resize(int parWidth, int parHeight)
{ {
RenderFramebuffer.Resize(parWidth, parHeight); RenderFramebuffer.Resize(parWidth, parHeight);
@@ -169,6 +225,9 @@ public class Renderer
} }
} }
/// <summary>
/// Executes all scheduled actions on the render thread.
/// </summary>
internal void RunScheduledActions() internal void RunScheduledActions()
{ {
while (_scheduleActions.TryDequeue(out var action)) while (_scheduleActions.TryDequeue(out var action))
@@ -176,4 +235,26 @@ public class Renderer
action(); action();
} }
} }
/// <summary>
/// Initializes OpenGL settings for rendering.
/// </summary>
/// <param name="parWidth">The width of the viewport.</param>
/// <param name="parHeight">The height of the viewport.</param>
private void InitializeOpenGl(int parWidth, int parHeight)
{
#if DEBUG
Debug.Setup();
#endif
GL.Enable(EnableCap.DepthTest);
// GL.Enable(EnableCap.CullFace);
// GL.Enable(EnableCap.FramebufferSrgb);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Viewport(0, 0, parWidth, parHeight);
}
} }

View File

@@ -4,10 +4,22 @@ using Serilog;
namespace Engine.Graphics.Shader; namespace Engine.Graphics.Shader;
/// <summary>
/// Represents an OpenGL shader program that can be used for rendering.
/// The class handles the compilation, linking, and management of shaders, as well as setting uniform values.
/// </summary>
public class Program : OpenGlObject public class Program : OpenGlObject
{ {
/// <summary>
/// A dictionary mapping uniform names to their locations in the shader program.
/// </summary>
private readonly Dictionary<string, int> _uniforms = new(); private readonly Dictionary<string, int> _uniforms = new();
/// <summary>
/// Initializes a new instance of the <see cref="Program"/> class with the provided vertex and fragment shader sources.
/// </summary>
/// <param name="parVertexSource">The source code for the vertex shader.</param>
/// <param name="parFragmentSource">The source code for the fragment shader.</param>
public Program(string parVertexSource, string parFragmentSource) public Program(string parVertexSource, string parFragmentSource)
{ {
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader); var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
@@ -16,6 +28,13 @@ public class Program : OpenGlObject
Handle = LinkProgram(vertexShader, fragmentShader); Handle = LinkProgram(vertexShader, fragmentShader);
} }
/// <summary>
/// Sets a uniform variable in the shader program with the given value.
/// </summary>
/// <typeparam name="T">The type of the uniform value.</typeparam>
/// <param name="parName">The name of the uniform variable in the shader.</param>
/// <param name="parValue">The value to set for the uniform.</param>
/// <exception cref="ArgumentException">Thrown when an unsupported uniform type is provided.</exception>
public void SetUniform<T>(string parName, in T parValue) public void SetUniform<T>(string parName, in T parValue)
{ {
try try
@@ -62,21 +81,31 @@ public class Program : OpenGlObject
} }
} }
/// <inheritdoc/>
internal override void Bind() internal override void Bind()
{ {
GL.UseProgram(Handle); GL.UseProgram(Handle);
} }
/// <inheritdoc/>
internal override void Unbind() internal override void Unbind()
{ {
GL.UseProgram(0); GL.UseProgram(0);
} }
/// <inheritdoc/>
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteProgram(Handle); GL.DeleteProgram(Handle);
} }
/// <summary>
/// Retrieves the location of a uniform variable in the shader program.
/// If the location is not cached, it will be retrieved from OpenGL.
/// </summary>
/// <param name="parName">The name of the uniform variable.</param>
/// <returns>The location of the uniform variable.</returns>
/// <exception cref="ArgumentException">Thrown when the uniform is not found in the shader program.</exception>
private int GetUniformLocation(string parName) private int GetUniformLocation(string parName)
{ {
if (_uniforms.TryGetValue(parName, out var location)) if (_uniforms.TryGetValue(parName, out var location))
@@ -94,6 +123,13 @@ public class Program : OpenGlObject
return location; return location;
} }
/// <summary>
/// Compiles a shader from the given source code and shader type.
/// </summary>
/// <param name="parSource">The source code of the shader.</param>
/// <param name="parType">The type of shader (vertex or fragment).</param>
/// <returns>The compiled shader handle.</returns>
/// <exception cref="ShaderCompilationException">Thrown when the shader fails to compile.</exception>
private static int CompileSource(string parSource, ShaderType parType) private static int CompileSource(string parSource, ShaderType parType)
{ {
var shaderId = GL.CreateShader(parType); var shaderId = GL.CreateShader(parType);
@@ -112,6 +148,13 @@ public class Program : OpenGlObject
return shaderId; return shaderId;
} }
/// <summary>
/// Links a vertex and fragment shader into a single shader program.
/// </summary>
/// <param name="parVertexShader">The compiled vertex shader handle.</param>
/// <param name="parFragmentShader">The compiled fragment shader handle.</param>
/// <returns>The linked shader program handle.</returns>
/// <exception cref="ShaderLinkException">Thrown when the shader program fails to link.</exception>
private static int LinkProgram(int parVertexShader, int parFragmentShader) private static int LinkProgram(int parVertexShader, int parFragmentShader)
{ {
var programId = GL.CreateProgram(); var programId = GL.CreateProgram();
@@ -152,13 +195,52 @@ public class Program : OpenGlObject
} }
} }
public class ShaderCompilationException(ShaderType parType, string parMessage) /// <summary>
: Exception($"Failed to compile {parType} shader: {parMessage}") /// Exception thrown when a shader fails to compile.
/// </summary>
public class ShaderCompilationException : Exception
{ {
public ShaderType ShaderType { get; } = parType; /// <summary>
/// The type of the shader that failed to compile.
/// </summary>
public ShaderType ShaderType { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ShaderCompilationException"/> class.
/// </summary>
/// <param name="parType">The type of shader that failed to compile.</param>
/// <param name="parMessage">The error message from the shader compilation failure.</param>
public ShaderCompilationException(ShaderType parType, string parMessage) : base(
$"Failed to compile {parType} shader: {parMessage}")
{
ShaderType = parType;
}
} }
public class ShaderLinkException(string parMessage) : Exception($"Failed to link shader program: {parMessage}"); /// <summary>
/// Exception thrown when a shader program fails to link.
/// </summary>
public class ShaderLinkException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ShaderLinkException"/> class.
/// </summary>
/// <param name="parMessage">The error message from the shader linking failure.</param>
public ShaderLinkException(string parMessage) : base($"Failed to link shader program: {parMessage}")
{
}
}
public class ShaderValidationException(string parMessage) /// <summary>
: Exception($"Failed to validate shader program: {parMessage}"); /// Exception thrown when a shader program fails validation.
/// </summary>
public class ShaderValidationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ShaderValidationException"/> class.
/// </summary>
/// <param name="parMessage">The error message from the shader validation failure.</param>
public ShaderValidationException(string parMessage) : base($"Failed to validate shader program: {parMessage}")
{
}
}

View File

@@ -5,12 +5,35 @@ using Serilog;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
/// <summary>
/// Represents a dynamic texture that can be resized and attached to a framebuffer.
/// </summary>
public class DynamicTexture : Texture, IFramebufferAttachment public class DynamicTexture : Texture, IFramebufferAttachment
{ {
/// <summary>
/// The pixel format of the texture.
/// </summary>
private readonly PixelFormat _format; private readonly PixelFormat _format;
/// <summary>
/// The pixel type of the texture.
/// </summary>
private readonly PixelType _type; private readonly PixelType _type;
/// <summary>
/// The internal format of the texture.
/// </summary>
private readonly PixelInternalFormat _internalFormat; private readonly PixelInternalFormat _internalFormat;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicTexture"/> class.
/// The texture is created with the specified format, type, and internal format.
/// </summary>
/// <param name="parWidth">The width of the texture.</param>
/// <param name="parHeight">The height of the texture.</param>
/// <param name="parFormat">The format of the texture.</param>
/// <param name="parType">The type of the texture.</param>
/// <param name="parInternalFormat">The internal format of the texture.</param>
internal DynamicTexture(int parWidth, int parHeight, PixelFormat parFormat, PixelType parType, internal DynamicTexture(int parWidth, int parHeight, PixelFormat parFormat, PixelType parType,
PixelInternalFormat parInternalFormat) PixelInternalFormat parInternalFormat)
: base(parWidth, parHeight) : base(parWidth, parHeight)
@@ -24,6 +47,13 @@ public class DynamicTexture : Texture, IFramebufferAttachment
IntPtr.Zero); IntPtr.Zero);
} }
/// <summary>
/// Creates a new <see cref="DynamicTexture"/> with the specified width, height, and pixel type.
/// </summary>
/// <typeparam name="T">The type of pixel used for the texture.</typeparam>
/// <param name="parWidth">The width of the texture.</param>
/// <param name="parHeight">The height of the texture.</param>
/// <returns>A new instance of <see cref="DynamicTexture"/>.</returns>
public static DynamicTexture Create<T>(int parWidth, int parHeight) public static DynamicTexture Create<T>(int parWidth, int parHeight)
where T : struct, IPixel where T : struct, IPixel
{ {
@@ -31,6 +61,11 @@ public class DynamicTexture : Texture, IFramebufferAttachment
return new DynamicTexture(parWidth, parHeight, pixel.Format, pixel.Type, pixel.InternalFormat); return new DynamicTexture(parWidth, parHeight, pixel.Format, pixel.Type, pixel.InternalFormat);
} }
/// <summary>
/// Resizes the texture to the specified width and height.
/// </summary>
/// <param name="parWidth">The new width of the texture.</param>
/// <param name="parHeight">The new height of the texture.</param>
public void Resize(int parWidth, int parHeight) public void Resize(int parWidth, int parHeight)
{ {
if (Width == parWidth && Height == parHeight) if (Width == parWidth && Height == parHeight)
@@ -48,6 +83,7 @@ public class DynamicTexture : Texture, IFramebufferAttachment
Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height); Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height);
} }
/// <inheritdoc/>
public void Attach(Framebuffer.Framebuffer parFramebuffer, FramebufferAttachment parAttachment) public void Attach(Framebuffer.Framebuffer parFramebuffer, FramebufferAttachment parAttachment)
{ {
GL.NamedFramebufferTexture(parFramebuffer.Handle, parAttachment, Handle, 0); GL.NamedFramebufferTexture(parFramebuffer.Handle, parAttachment, Handle, 0);

View File

@@ -3,22 +3,60 @@ using Engine.Graphics.Pixel;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
/// <summary>
/// Represents a read-only texture with dimensions and pixel access capabilities.
/// </summary>
public interface IConstTexture public interface IConstTexture
{ {
/// <summary>
/// The width of the texture.
/// </summary>
public int Width { get; } public int Width { get; }
/// <summary>
/// The height of the texture.
/// </summary>
public int Height { get; } public int Height { get; }
/// <summary>
/// Reads pixels from the texture into the provided pixel array.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parX">The x-coordinate of the region to read.</param>
/// <param name="parY">The y-coordinate of the region to read.</param>
/// <param name="parWidth">The width of the region to read.</param>
/// <param name="parHeight">The height of the region to read.</param>
/// <param name="parPixels">The array to store the read pixels.</param>
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel; where T : struct, IPixel;
} }
/// <summary>
/// Provides extension methods for <see cref="IConstTexture"/>.
/// </summary>
public static class ConstTextureExtensions public static class ConstTextureExtensions
{ {
/// <summary>
/// Reads all pixels from the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to read from.</param>
/// <returns>A 2D array containing the pixels.</returns>
public static T[,] ReadPixels<T>(this IConstTexture parTexture) where T : struct, IPixel public static T[,] ReadPixels<T>(this IConstTexture parTexture) where T : struct, IPixel
{ {
return parTexture.ReadPixels<T>(0, 0, parTexture.Width, parTexture.Height); return parTexture.ReadPixels<T>(0, 0, parTexture.Width, parTexture.Height);
} }
/// <summary>
/// Reads a specified region of pixels from the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to read from.</param>
/// <param name="parX">The x-coordinate of the region to read.</param>
/// <param name="parY">The y-coordinate of the region to read.</param>
/// <param name="parWidth">The width of the region to read.</param>
/// <param name="parHeight">The height of the region to read.</param>
/// <returns>A 2D array containing the pixels.</returns>
public static T[,] ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight) public static T[,] ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight)
where T : struct, IPixel where T : struct, IPixel
{ {
@@ -28,17 +66,37 @@ public static class ConstTextureExtensions
return pixels; return pixels;
} }
/// <summary>
/// Reads all pixels from the texture into the specified image.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to read from.</param>
/// <param name="parImage">The image to store the pixels.</param>
public static void ReadPixels<T>(this IConstTexture parTexture, Image<T> parImage) where T : struct, IPixel public static void ReadPixels<T>(this IConstTexture parTexture, Image<T> parImage) where T : struct, IPixel
{ {
parTexture.ReadPixels(0, 0, parImage); parTexture.ReadPixels(0, 0, parImage);
} }
/// <summary>
/// Reads a specified region of pixels from the texture into the specified image.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to read from.</param>
/// <param name="parX">The x-coordinate of the region to read.</param>
/// <param name="parY">The y-coordinate of the region to read.</param>
/// <param name="parImage">The image to store the pixels.</param>
public static void ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, Image<T> parImage) public static void ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, Image<T> parImage)
where T : struct, IPixel where T : struct, IPixel
{ {
parTexture.ReadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels); parTexture.ReadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
} }
/// <summary>
/// Reads all pixels from the texture into the specified 2D array.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to read from.</param>
/// <param name="parPixels">The array to store the pixels.</param>
public static void ReadPixels<T>(this IConstTexture parTexture, T[,] parPixels) where T : struct, IPixel public static void ReadPixels<T>(this IConstTexture parTexture, T[,] parPixels) where T : struct, IPixel
{ {
parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels); parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);

View File

@@ -3,25 +3,60 @@ using Engine.Graphics.Pixel;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
/// <summary>
/// Represents a writable texture with dimensions and pixel access capabilities.
/// </summary>
public interface ITexture : IConstTexture public interface ITexture : IConstTexture
{ {
/// <summary>
/// Uploads pixels to the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parX">The x-coordinate of the region to write.</param>
/// <param name="parY">The y-coordinate of the region to write.</param>
/// <param name="parWidth">The width of the region to write.</param>
/// <param name="parHeight">The height of the region to write.</param>
/// <param name="parPixels">The pixel data to upload.</param>
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel; where T : struct, IPixel;
} }
/// <summary>
/// Provides extension methods for <see cref="ITexture"/>.
/// </summary>
public static class TextureExtensions public static class TextureExtensions
{ {
/// <summary>
/// Uploads the entire image to the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to upload to.</param>
/// <param name="parImage">The image containing the pixel data.</param>
public static void UploadPixels<T>(this ITexture parTexture, Image<T> parImage) where T : struct, IPixel public static void UploadPixels<T>(this ITexture parTexture, Image<T> parImage) where T : struct, IPixel
{ {
parTexture.UploadPixels(0, 0, parImage); parTexture.UploadPixels(0, 0, parImage);
} }
/// <summary>
/// Uploads a specified region of the image to the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to upload to.</param>
/// <param name="parX">The x-coordinate of the region in the image.</param>
/// <param name="parY">The y-coordinate of the region in the image.</param>
/// <param name="parImage">The image containing the pixel data.</param>
public static void UploadPixels<T>(this ITexture parTexture, int parX, int parY, Image<T> parImage) public static void UploadPixels<T>(this ITexture parTexture, int parX, int parY, Image<T> parImage)
where T : struct, IPixel where T : struct, IPixel
{ {
parTexture.UploadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels); parTexture.UploadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
} }
/// <summary>
/// Uploads the entire 2D pixel array to the texture.
/// </summary>
/// <typeparam name="T">The pixel type, which must implement <see cref="IPixel"/>.</typeparam>
/// <param name="parTexture">The texture to upload to.</param>
/// <param name="parPixels">The pixel data to upload.</param>
public static void UploadPixels<T>(this ITexture parTexture, T[,] parPixels) where T : struct, IPixel public static void UploadPixels<T>(this ITexture parTexture, T[,] parPixels) where T : struct, IPixel
{ {
parTexture.UploadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels); parTexture.UploadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);

View File

@@ -3,13 +3,30 @@ using OpenTK.Graphics.OpenGL;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
/// <summary>
/// Represents a static texture in OpenGL, created with fixed storage and format.
/// </summary>
public class StaticTexture : Texture public class StaticTexture : Texture
{ {
/// <summary>
/// Initializes a new instance of the <see cref="StaticTexture"/> class.
/// The texture is created with the specified width, height, and internal format.
/// </summary>
/// <param name="parWidth">The width of the texture.</param>
/// <param name="parHeight">The height of the texture.</param>
/// <param name="parFormat">The internal format of the texture.</param>
private StaticTexture(int parWidth, int parHeight, SizedInternalFormat parFormat) : base(parWidth, parHeight) private StaticTexture(int parWidth, int parHeight, SizedInternalFormat parFormat) : base(parWidth, parHeight)
{ {
GL.TextureStorage2D(Handle, 1, parFormat, Width, Height); GL.TextureStorage2D(Handle, 1, parFormat, Width, Height);
} }
/// <summary>
/// Creates a new <see cref="StaticTexture"/> with the specified width and height.
/// </summary>
/// <typeparam name="T">The type of pixel format used for the texture.</typeparam>
/// <param name="parWidth">The width of the texture.</param>
/// <param name="parHeight">The height of the texture.</param>
/// <returns>A new instance of <see cref="StaticTexture"/>.</returns>
public static StaticTexture Create<T>(int parWidth, int parHeight) public static StaticTexture Create<T>(int parWidth, int parHeight)
where T : struct, IPixel where T : struct, IPixel
{ {

View File

@@ -5,8 +5,13 @@ using Serilog;
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
/// <summary>
/// Represents an abstract texture object used in OpenGL for 2D textures.
/// Handles the creation, binding, and destruction of textures and provides functionality for uploading and reading pixel data.
/// </summary>
public abstract class Texture : OpenGlObject, ITexture public abstract class Texture : OpenGlObject, ITexture
{ {
/// <inheritdoc/>
public int Width public int Width
{ {
get => _width; get => _width;
@@ -21,6 +26,7 @@ public abstract class Texture : OpenGlObject, ITexture
} }
} }
/// <inheritdoc/>
public int Height public int Height
{ {
get => _height; get => _height;
@@ -35,9 +41,22 @@ public abstract class Texture : OpenGlObject, ITexture
} }
} }
/// <summary>
/// The width of the texture.
/// </summary>
private int _width; private int _width;
/// <summary>
/// The height of the texture.
/// </summary>
private int _height; private int _height;
/// <summary>
/// Initializes a new instance of the <see cref="Texture"/> class with the specified width and height.
/// Creates an OpenGL texture handle and sets default texture parameters.
/// </summary>
/// <param name="parWidth">The width of the texture.</param>
/// <param name="parHeight">The height of the texture.</param>
protected Texture(int parWidth, int parHeight) protected Texture(int parWidth, int parHeight)
{ {
Width = parWidth; Width = parWidth;
@@ -54,6 +73,7 @@ public abstract class Texture : OpenGlObject, ITexture
Log.Debug("Texture {Handle} created with {Width}x{Height}", Handle, Width, Height); Log.Debug("Texture {Handle} created with {Width}x{Height}", Handle, Width, Height);
} }
/// <inheritdoc/>
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel where T : struct, IPixel
{ {
@@ -83,6 +103,7 @@ public abstract class Texture : OpenGlObject, ITexture
GL.TextureSubImage2D(Handle, 0, parX, parY, parWidth, parHeight, format, type, parPixels); GL.TextureSubImage2D(Handle, 0, parX, parY, parWidth, parHeight, format, type, parPixels);
} }
/// <inheritdoc/>
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel where T : struct, IPixel
{ {
@@ -118,21 +139,28 @@ public abstract class Texture : OpenGlObject, ITexture
parPixels); parPixels);
} }
/// <summary>
/// Binds the texture to a texture unit.
/// </summary>
/// <param name="parUnit">The texture unit to which the texture should be bound.</param>
public void BindUnit(int parUnit = 0) public void BindUnit(int parUnit = 0)
{ {
GL.BindTextureUnit(parUnit, Handle); GL.BindTextureUnit(parUnit, Handle);
} }
/// <inheritdoc/>
internal override void Bind() internal override void Bind()
{ {
GL.BindTexture(TextureTarget.Texture2D, Handle); GL.BindTexture(TextureTarget.Texture2D, Handle);
} }
/// <inheritdoc/>
internal override void Unbind() internal override void Unbind()
{ {
GL.BindTexture(TextureTarget.Texture2D, 0); GL.BindTexture(TextureTarget.Texture2D, 0);
} }
/// <inheritdoc/>
protected override void Destroy() protected override void Destroy()
{ {
GL.DeleteTexture(Handle); GL.DeleteTexture(Handle);

View File

@@ -1,13 +1,48 @@
namespace Engine.Graphics.Texture; namespace Engine.Graphics.Texture;
public class TextureUnitMap(int parCapacity) /// <summary>
/// Manages the mapping of textures to texture units.
/// Allows tracking of textures and their associated texture units within a given capacity.
/// </summary>
public class TextureUnitMap
{ {
/// <summary>
/// The maximum number of textures that can be mapped.
/// </summary>
public int Capacity { get; }
/// <summary>
/// The current number of textures in the unit map.
/// </summary>
public int Size => _textures.Count; public int Size => _textures.Count;
public int Capacity => parCapacity;
/// <summary>
/// A read-only dictionary that holds the textures and their associated unit numbers.
/// </summary>
public IReadOnlyDictionary<Texture, int> Textures => _textures; public IReadOnlyDictionary<Texture, int> Textures => _textures;
/// <summary>
/// A dictionary that stores the textures and their corresponding texture units.
/// </summary>
private readonly Dictionary<Texture, int> _textures = new(); private readonly Dictionary<Texture, int> _textures = new();
/// <summary>
/// Initializes a new instance of the <see cref="TextureUnitMap"/> class with the specified capacity.
/// </summary>
/// <param name="parCapacity">The maximum number of textures that can be mapped in this unit map.</param>
public TextureUnitMap(int parCapacity)
{
Capacity = parCapacity;
}
/// <summary>
/// Retrieves the texture unit assigned to the specified texture.
/// If the texture is not already mapped, a new unit is assigned.
/// Throws an exception if the map has reached its capacity.
/// </summary>
/// <param name="parTexture">The texture for which to retrieve the assigned unit.</param>
/// <returns>The texture unit assigned to the texture.</returns>
/// <exception cref="InvalidOperationException">Thrown when the map has reached its capacity.</exception>
public int GetUnit(Texture parTexture) public int GetUnit(Texture parTexture)
{ {
if (_textures.TryGetValue(parTexture, out var unit)) if (_textures.TryGetValue(parTexture, out var unit))
@@ -15,7 +50,7 @@ public class TextureUnitMap(int parCapacity)
return unit; return unit;
} }
if (_textures.Count >= parCapacity) if (_textures.Count >= Capacity)
{ {
throw new InvalidOperationException("Texture unit map is full"); throw new InvalidOperationException("Texture unit map is full");
} }
@@ -26,6 +61,9 @@ public class TextureUnitMap(int parCapacity)
return unit; return unit;
} }
/// <summary>
/// Clears all textures from the unit map, effectively resetting it.
/// </summary>
public void Reset() public void Reset()
{ {
_textures.Clear(); _textures.Clear();

View File

@@ -4,15 +4,46 @@ using OpenTK.Mathematics;
namespace Engine.Input; namespace Engine.Input;
/// <summary>
/// Represents an input handler interface that processes keyboard and mouse input.
/// </summary>
public interface IInputHandler : IUpdate public interface IInputHandler : IUpdate
{ {
CultureInfo CurrentInputLanguage { get; } /// <summary>
/// The current input language.
/// </summary>
public CultureInfo CurrentInputLanguage { get; }
Vector2 MousePosition { get; } /// <summary>
/// The current mouse position in screen coordinates.
/// </summary>
public Vector2 MousePosition { get; }
bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode); /// <summary>
bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode); /// Checks if a specific keyboard button is currently pressed.
/// </summary>
/// <param name="parKeyboardButtonCode">The keyboard button code to check.</param>
/// <returns>True if the button is pressed; otherwise, false.</returns>
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode);
bool IsMouseButtonPressed(MouseButtonCode parButtonCode); /// <summary>
bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode); /// Checks if a specific keyboard button was just pressed in the current frame.
/// </summary>
/// <param name="parKeyboardButtonCode">The keyboard button code to check.</param>
/// <returns>True if the button was just pressed; otherwise, false.</returns>
public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode);
/// <summary>
/// Checks if a specific mouse button is currently pressed.
/// </summary>
/// <param name="parButtonCode">The mouse button code to check.</param>
/// <returns>True if the button is pressed; otherwise, false.</returns>
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode);
/// <summary>
/// Checks if a specific mouse button was just pressed in the current frame.
/// </summary>
/// <param name="parButtonCode">The mouse button code to check.</param>
/// <returns>True if the button was just pressed; otherwise, false.</returns>
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode);
} }

View File

@@ -61,22 +61,42 @@ public enum KeyboardButtonCode
PageUp, PageUp,
PageDown, PageDown,
/// <summary>
/// Represents the total count of keyboard button codes.
/// </summary>
TotalCount = PageDown + 1 TotalCount = PageDown + 1
} }
/// <summary>
/// Provides helper methods for working with keyboard button codes.
/// </summary>
public static class KeyboardButtonCodeHelper public static class KeyboardButtonCodeHelper
{ {
/// <summary>
/// Gets a list of all printable keyboard button codes.
/// </summary>
/// <returns>A list of printable keyboard button codes.</returns>
public static List<KeyboardButtonCode> GetAllPrintableKeys() public static List<KeyboardButtonCode> GetAllPrintableKeys()
{ {
return Enum.GetValues<KeyboardButtonCode>().Where(parX => parX.IsPrintableKey()).ToList(); return Enum.GetValues<KeyboardButtonCode>().Where(parX => parX.IsPrintableKey()).ToList();
} }
/// <summary>
/// Determines whether a keyboard button code is printable.
/// </summary>
/// <param name="parKey">The keyboard button code to check.</param>
/// <returns>True if the key is printable; otherwise, false.</returns>
public static bool IsPrintableKey(this KeyboardButtonCode parKey) public static bool IsPrintableKey(this KeyboardButtonCode parKey)
{ {
return parKey is >= KeyboardButtonCode.A and <= KeyboardButtonCode.Z return parKey is >= KeyboardButtonCode.A and <= KeyboardButtonCode.Z
or >= KeyboardButtonCode.D1 and <= KeyboardButtonCode.D0 or KeyboardButtonCode.Space; or >= KeyboardButtonCode.D1 and <= KeyboardButtonCode.D0 or KeyboardButtonCode.Space;
} }
/// <summary>
/// Gets the character representation of a keyboard button code based on the current input language.
/// </summary>
/// <param name="parKey">The keyboard button code to convert.</param>
/// <returns>The character representation of the key.</returns>
public static char GetChar(this KeyboardButtonCode parKey) public static char GetChar(this KeyboardButtonCode parKey)
{ {
return Engine.Instance.InputHandler!.CurrentInputLanguage.Name switch return Engine.Instance.InputHandler!.CurrentInputLanguage.Name switch
@@ -87,6 +107,11 @@ public static class KeyboardButtonCodeHelper
}; };
} }
/// <summary>
/// Gets the character representation of a keyboard button code in English.
/// </summary>
/// <param name="parKey">The keyboard button code to convert.</param>
/// <returns>The character representation of the key.</returns>
private static char GetEnChar(this KeyboardButtonCode parKey) private static char GetEnChar(this KeyboardButtonCode parKey)
{ {
return parKey switch return parKey switch
@@ -134,6 +159,11 @@ public static class KeyboardButtonCodeHelper
}; };
} }
/// <summary>
/// Gets the character representation of a keyboard button code in Russian.
/// </summary>
/// <param name="parKey">The keyboard button code to convert.</param>
/// <returns>The character representation of the key.</returns>
private static char GetRuChar(this KeyboardButtonCode parKey) private static char GetRuChar(this KeyboardButtonCode parKey)
{ {
return parKey switch return parKey switch

View File

@@ -1,10 +1,27 @@
namespace Engine.Input; namespace Engine.Input;
/// <summary>
/// Represents the different mouse button codes.
/// </summary>
public enum MouseButtonCode public enum MouseButtonCode
{ {
/// <summary>
/// Represents the left mouse button.
/// </summary>
Left, Left,
/// <summary>
/// Represents the right mouse button.
/// </summary>
Right, Right,
/// <summary>
/// Represents the middle mouse button.
/// </summary>
Middle, Middle,
/// <summary>
/// Represents the total count of mouse button codes.
/// </summary>
TotalCount = Middle + 1 TotalCount = Middle + 1
} }

View File

@@ -4,47 +4,98 @@ using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine.Input; namespace Engine.Input;
public class WindowInputHandler(Window parWindow) : IInputHandler /// <summary>
/// Handles input for a window, processing keyboard and mouse input states.
/// </summary>
public class WindowInputHandler : IInputHandler
{ {
/// <inheritdoc/>
public CultureInfo CurrentInputLanguage => new(1033); public CultureInfo CurrentInputLanguage => new(1033);
public Vector2 MousePosition => parWindow.NativeWindow.MouseState.Position;
private KeyboardState _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); /// <inheritdoc/>
private KeyboardState _previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); public Vector2 MousePosition => _window.NativeWindow.MouseState.Position;
private MouseState _mouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
private MouseState _previousMouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
/// <summary>
/// The window to capture input from.
/// </summary>
private readonly Window _window;
/// <summary>
/// Keyboard state in the current frame.
/// </summary>
private KeyboardState _keyboardState;
/// <summary>
/// Keyboard state in the previous frame.
/// </summary>
private KeyboardState _previousKeyboardState;
/// <summary>
/// Mouse state in the current frame.
/// </summary>
private MouseState _mouseState;
/// <summary>
/// Mouse state in the previous frame.
/// </summary>
private MouseState _previousMouseState;
/// <summary>
/// Initializes a new instance of the <see cref="WindowInputHandler"/> class.
/// </summary>
/// <param name="parWindow">The window to capture input from.</param>
public WindowInputHandler(Window parWindow)
{
_window = parWindow;
_keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
_previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
_mouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
_previousMouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
}
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
_previousKeyboardState = _keyboardState; _previousKeyboardState = _keyboardState;
_keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot(); _keyboardState = _window.NativeWindow.KeyboardState.GetSnapshot();
_previousMouseState = _mouseState; _previousMouseState = _mouseState;
_mouseState = parWindow.NativeWindow.MouseState.GetSnapshot(); _mouseState = _window.NativeWindow.MouseState.GetSnapshot();
} }
/// <inheritdoc/>
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode) public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode)
{ {
return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)); return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode));
} }
/// <inheritdoc/>
public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode) public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode)
{ {
return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)) && return _keyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)) &&
!_previousKeyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode)); !_previousKeyboardState.IsKeyDown(MapKeyCode(parKeyboardButtonCode));
} }
/// <inheritdoc/>
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode) public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
{ {
return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)); return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode));
} }
/// <inheritdoc/>
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode) public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
{ {
return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)) && return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)) &&
!_previousMouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)); !_previousMouseState.IsButtonDown(MapMouseButtonCode(parButtonCode));
} }
/// <summary>
/// Maps a custom <see cref="MouseButtonCode"/> to the GLFW <see cref="MouseButton"/>.
/// </summary>
/// <param name="parButton">The custom mouse button code.</param>
/// <returns>The corresponding GLFW mouse button.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the mouse button code is not recognized.</exception>
private static MouseButton MapMouseButtonCode(MouseButtonCode parButton) private static MouseButton MapMouseButtonCode(MouseButtonCode parButton)
{ {
return parButton switch return parButton switch
@@ -56,6 +107,12 @@ public class WindowInputHandler(Window parWindow) : IInputHandler
}; };
} }
/// <summary>
/// Maps a custom <see cref="KeyboardButtonCode"/> to the GLFW <see cref="Keys"/>.
/// </summary>
/// <param name="parKeyboardButtonCode">The custom keyboard button code.</param>
/// <returns>The corresponding GLFW key.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown if the keyboard button code is not recognized.</exception>
private static Keys MapKeyCode(KeyboardButtonCode parKeyboardButtonCode) private static Keys MapKeyCode(KeyboardButtonCode parKeyboardButtonCode)
{ {
return parKeyboardButtonCode switch return parKeyboardButtonCode switch

View File

@@ -1,9 +1,31 @@
namespace Engine.Resource; namespace Engine.Resource;
public class FilesystemResourceStreamProvider(string parBasePath) : IResourceStreamProvider /// <summary>
/// Provides resource streams from the file system, using a specified base path.
/// </summary>
public class FilesystemResourceStreamProvider : IResourceStreamProvider
{ {
/// <summary>
/// The base path for locating resources.
/// </summary>
private readonly string _basePath;
/// <summary>
/// Initializes a new instance of the <see cref="FilesystemResourceStreamProvider"/> class.
/// </summary>
/// <param name="parBasePath">The base path for locating resources.</param>
public FilesystemResourceStreamProvider(string parBasePath)
{
_basePath = parBasePath;
}
/// <summary>
/// Retrieves a stream for the resource located at the specified path, relative to the base path.
/// </summary>
/// <param name="parPath">The relative path to the resource.</param>
/// <returns>A stream providing access to the resource data.</returns>
public Stream GetStream(string parPath) public Stream GetStream(string parPath)
{ {
return File.OpenRead(Path.Combine(parBasePath, parPath)); return File.OpenRead(Path.Combine(_basePath, parPath));
} }
} }

View File

@@ -1,6 +1,15 @@
namespace Engine.Resource; namespace Engine.Resource;
/// <summary>
/// Defines the interface for a resource loader that can load objects from a path using a stream provider.
/// </summary>
public interface IResourceLoader public interface IResourceLoader
{ {
object Load(string parPath, IResourceStreamProvider parStreamProvider); /// <summary>
/// Loads a resource object from the specified path using the provided stream provider.
/// </summary>
/// <param name="parPath">The path to the resource.</param>
/// <param name="parStreamProvider">The stream provider used to access the resource data.</param>
/// <returns>The loaded resource object.</returns>
public object Load(string parPath, IResourceStreamProvider parStreamProvider);
} }

View File

@@ -1,6 +1,15 @@
namespace Engine.Resource; namespace Engine.Resource;
/// <summary>
/// Defines the interface for a resource manager that handles loading resources of specific types.
/// </summary>
public interface IResourceManager public interface IResourceManager
{ {
T Load<T>(string parPath) where T : class; /// <summary>
/// Loads a resource of the specified type from the given path.
/// </summary>
/// <typeparam name="T">The type of the resource to load.</typeparam>
/// <param name="parPath">The path to the resource.</param>
/// <returns>The loaded resource as an instance of type T.</returns>
public T Load<T>(string parPath) where T : class;
} }

View File

@@ -1,6 +1,14 @@
namespace Engine.Resource; namespace Engine.Resource;
/// <summary>
/// Defines the interface for a resource stream provider, responsible for retrieving streams to access resource data.
/// </summary>
public interface IResourceStreamProvider public interface IResourceStreamProvider
{ {
Stream GetStream(string parPath); /// <summary>
/// Retrieves a stream for the resource located at the specified path.
/// </summary>
/// <param name="parPath">The path to the resource.</param>
/// <returns>A stream providing access to the resource data.</returns>
public Stream GetStream(string parPath);
} }

View File

@@ -4,8 +4,18 @@ using Engine.Asset.Font.Metadata;
namespace Engine.Resource.Loader; namespace Engine.Resource.Loader;
/// <summary>
/// Loads font resources, including metadata and texture atlas.
/// </summary>
public class FontLoader : IResourceLoader public class FontLoader : IResourceLoader
{ {
/// <summary>
/// Loads a font resource by loading its metadata and texture atlas.
/// </summary>
/// <param name="parPath">The path to the font resource directory.</param>
/// <param name="parStreamProvider">The stream provider used to read files.</param>
/// <returns>A <see cref="Font"/> instance containing the loaded metadata and texture atlas.</returns>
/// <exception cref="InvalidOperationException">Thrown if the metadata cannot be loaded.</exception>
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
var metadataPath = Path.Combine(parPath, "metadata.json"); var metadataPath = Path.Combine(parPath, "metadata.json");

View File

@@ -6,14 +6,29 @@ using SixLabors.ImageSharp.Processing;
namespace Engine.Resource.Loader; namespace Engine.Resource.Loader;
/// <summary>
/// Loads image resources, including processing the image data.
/// </summary>
public class ImageLoader : IResourceLoader public class ImageLoader : IResourceLoader
{ {
/// <summary>
/// Loads the image from the specified path using the provided stream provider.
/// </summary>
/// <param name="parPath">The path to the image resource.</param>
/// <param name="parStreamProvider">The provider for accessing resource streams.</param>
/// <returns>The loaded image resource.</returns>
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
using var stream = parStreamProvider.GetStream(parPath); using var stream = parStreamProvider.GetStream(parPath);
return Load(stream); return Load(stream);
} }
/// <summary>
/// Loads the image from a stream and processes it.
/// </summary>
/// <param name="parStream">The stream containing the image data.</param>
/// <returns>The processed image resource.</returns>
/// <exception cref="InvalidOperationException">Thrown if the image cannot be loaded from the stream.</exception>
internal static Asset.Image<Rgba8> Load(Stream parStream) internal static Asset.Image<Rgba8> Load(Stream parStream)
{ {
var sharpImage = Image.Load<Rgba32>(parStream); var sharpImage = Image.Load<Rgba32>(parStream);

View File

@@ -2,8 +2,18 @@
namespace Engine.Resource.Loader; namespace Engine.Resource.Loader;
/// <summary>
/// Loads mesh resources from various formats, such as .obj and .stl.
/// </summary>
public class MeshLoader : IResourceLoader public class MeshLoader : IResourceLoader
{ {
/// <summary>
/// Loads a mesh resource based on the file extension and stream provider.
/// </summary>
/// <param name="parPath">The path to the mesh resource.</param>
/// <param name="parStreamProvider">The provider for accessing resource streams.</param>
/// <returns>The loaded mesh resource.</returns>
/// <exception cref="InvalidOperationException">Thrown if the mesh format is unsupported.</exception>
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
var extension = Path.GetExtension(parPath); var extension = Path.GetExtension(parPath);

View File

@@ -4,8 +4,17 @@ using Engine.Graphics.Shader;
namespace Engine.Resource.Loader; namespace Engine.Resource.Loader;
/// <summary>
/// Loads shader program resources, including vertex and fragment shaders.
/// </summary>
public partial class ProgramLoader : IResourceLoader public partial class ProgramLoader : IResourceLoader
{ {
/// <summary>
/// Loads a shader program from the specified path and stream provider, separating vertex and fragment shaders.
/// </summary>
/// <param name="parPath">The path to the shader program resource.</param>
/// <param name="parStreamProvider">The provider for accessing resource streams.</param>
/// <returns>The loaded shader program.</returns>
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
var textReader = new StreamReader(parStreamProvider.GetStream(parPath)); var textReader = new StreamReader(parStreamProvider.GetStream(parPath));

View File

@@ -1,7 +1,16 @@
namespace Engine.Resource.Loader; namespace Engine.Resource.Loader;
/// <summary>
/// Loads texture resources, including image processing and conversion to static textures.
/// </summary>
public class TextureLoader : IResourceLoader public class TextureLoader : IResourceLoader
{ {
/// <summary>
/// Loads the texture from the specified path and stream provider.
/// </summary>
/// <param name="parPath">The path to the texture resource.</param>
/// <param name="parStreamProvider">The provider for accessing resource streams.</param>
/// <returns>The loaded texture resource.</returns>
public object Load(string parPath, IResourceStreamProvider parStreamProvider) public object Load(string parPath, IResourceStreamProvider parStreamProvider)
{ {
using var stream = parStreamProvider.GetStream(parPath); using var stream = parStreamProvider.GetStream(parPath);

View File

@@ -1,14 +1,30 @@
namespace Engine.Resource; namespace Engine.Resource;
/// <summary>
/// Provides resource streams from in-memory storage.
/// </summary>
public class MemoryResourceStreamProvider : IResourceStreamProvider public class MemoryResourceStreamProvider : IResourceStreamProvider
{ {
/// <summary>
/// A dictionary mapping resource paths to their byte data.
/// </summary>
private readonly Dictionary<string, byte[]> _resources = new(); private readonly Dictionary<string, byte[]> _resources = new();
/// <summary>
/// Retrieves a stream for the resource stored in memory at the specified path.
/// </summary>
/// <param name="parPath">The path to the in-memory resource.</param>
/// <returns>A stream providing access to the resource data.</returns>
public Stream GetStream(string parPath) public Stream GetStream(string parPath)
{ {
return new MemoryStream(_resources[parPath]); return new MemoryStream(_resources[parPath]);
} }
/// <summary>
/// Adds a resource to the in-memory storage.
/// </summary>
/// <param name="parPath">The path to associate with the resource.</param>
/// <param name="parData">The byte array representing the resource data.</param>
internal void AddResource(string parPath, byte[] parData) internal void AddResource(string parPath, byte[] parData)
{ {
_resources.Add(parPath, parData); _resources.Add(parPath, parData);

View File

@@ -1,17 +1,42 @@
namespace Engine.Resource; namespace Engine.Resource;
/// <summary>
/// Manages loading and caching of resources.
/// </summary>
public class ResourceManager : IResourceManager public class ResourceManager : IResourceManager
{ {
/// <summary>
/// The provider for resource streams.
/// </summary>
internal IResourceStreamProvider StreamProvider { get; } internal IResourceStreamProvider StreamProvider { get; }
/// <summary>
/// A dictionary mapping resource types to their respective loaders.
/// </summary>
private readonly Dictionary<Type, IResourceLoader> _loaders = new(); private readonly Dictionary<Type, IResourceLoader> _loaders = new();
/// <summary>
/// A dictionary mapping resource types to their cached resources.
/// </summary>
private readonly Dictionary<Type, ResourceStorage> _storages = new(); private readonly Dictionary<Type, ResourceStorage> _storages = new();
/// <summary>
/// Initializes a new instance of the <see cref="ResourceManager"/> class with the specified stream provider.
/// </summary>
/// <param name="parStreamProvider">The stream provider used for resource loading.</param>
public ResourceManager(IResourceStreamProvider parStreamProvider) public ResourceManager(IResourceStreamProvider parStreamProvider)
{ {
StreamProvider = parStreamProvider; StreamProvider = parStreamProvider;
} }
/// <summary>
/// Loads a resource of the specified type from the given path, using the appropriate loader.
/// If the resource is already cached, it is returned from the cache.
/// </summary>
/// <typeparam name="T">The type of the resource to load.</typeparam>
/// <param name="parPath">The path to the resource.</param>
/// <returns>The loaded resource.</returns>
/// <exception cref="InvalidOperationException">Thrown when no loader is registered for the specified resource type.</exception>
public T Load<T>(string parPath) where T : class public T Load<T>(string parPath) where T : class
{ {
if (!_storages.TryGetValue(typeof(T), out var storage)) if (!_storages.TryGetValue(typeof(T), out var storage))
@@ -38,30 +63,60 @@ public class ResourceManager : IResourceManager
return (T)resource; return (T)resource;
} }
/// <summary>
/// Registers a loader for a specific resource type.
/// </summary>
/// <typeparam name="T">The type of resource the loader is responsible for.</typeparam>
/// <param name="parLoader">The loader to register.</param>
internal void RegisterLoader<T>(IResourceLoader parLoader) where T : class internal void RegisterLoader<T>(IResourceLoader parLoader) where T : class
{ {
_loaders.Add(typeof(T), parLoader); _loaders.Add(typeof(T), parLoader);
} }
/// <summary>
/// Clears all cached resources.
/// </summary>
internal void Reset() internal void Reset()
{ {
_storages.Clear(); _storages.Clear();
} }
/// <summary>
/// A helper class for storing resources in memory, indexed by their path.
/// </summary>
private class ResourceStorage private class ResourceStorage
{ {
/// <summary>
/// A dictionary mapping resource paths to the resources themselves.
/// </summary>
private readonly Dictionary<string, object> _resources = new(); private readonly Dictionary<string, object> _resources = new();
/// <summary>
/// Adds a resource to the storage.
/// </summary>
/// <typeparam name="T">The type of resource to store.</typeparam>
/// <param name="parPath">The path of the resource.</param>
/// <param name="parResource">The resource to store.</param>
public void Add<T>(string parPath, T parResource) where T : class public void Add<T>(string parPath, T parResource) where T : class
{ {
_resources.Add(parPath, parResource); _resources.Add(parPath, parResource);
} }
/// <summary>
/// Retrieves a resource from storage by its path.
/// </summary>
/// <typeparam name="T">The type of resource to retrieve.</typeparam>
/// <param name="parPath">The path of the resource.</param>
/// <returns>The resource, or <c>null</c> if not found.</returns>
public T? Get<T>(string parPath) where T : class public T? Get<T>(string parPath) where T : class
{ {
return _resources.TryGetValue(parPath, out var resource) ? (T)resource : null; return _resources.TryGetValue(parPath, out var resource) ? (T)resource : null;
} }
/// <summary>
/// Removes a resource from storage by its path.
/// </summary>
/// <param name="parPath">The path of the resource to remove.</param>
public void Remove(string parPath) public void Remove(string parPath)
{ {
_resources.Remove(parPath); _resources.Remove(parPath);

View File

@@ -4,22 +4,18 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn; namespace Engine.Scene.Component.BuiltIn;
public abstract class Camera( /// <summary>
float parNearPlane, /// Abstract base class for cameras that provide view and projection matrices.
float parFarPlane /// </summary>
) : Component, ICamera public abstract class Camera : Component, ICamera
{ {
public float AspectRatio { get; private set; } = 1; /// <inheritdoc/>
public float NearPlane { get; set; } = parNearPlane;
public float FarPlane { get; set; } = parFarPlane;
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
private Vector2i _screenSize = new(1, 1);
public abstract Matrix4 View { get; } public abstract Matrix4 View { get; }
/// <inheritdoc/>
public abstract Matrix4 Projection { get; } public abstract Matrix4 Projection { get; }
/// <inheritdoc/>
public Vector2i ScreenSize public Vector2i ScreenSize
{ {
get => _screenSize; get => _screenSize;
@@ -30,6 +26,54 @@ public abstract class Camera(
} }
} }
/// <summary>
/// The aspect ratio of the camera, calculated as width divided by height.
/// </summary>
public float AspectRatio { get; private set; } = 1;
/// <summary>
/// The near clipping plane distance.
/// </summary>
public float NearPlane { get; set; }
/// <summary>
/// The far clipping plane distance.
/// </summary>
public float FarPlane { get; set; }
/// <summary>
/// The render layer for the camera.
/// </summary>
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
/// <summary>
/// The screen size of the camera.
/// </summary>
private Vector2i _screenSize = new(1, 1);
/// <summary>
/// Initializes a new instance of the <see cref="Camera"/> class.
/// </summary>
/// <param name="parNearPlane">The near clipping plane distance.</param>
/// <param name="parFarPlane">The far clipping plane distance.</param>
protected Camera(float parNearPlane, float parFarPlane)
{
NearPlane = parNearPlane;
FarPlane = parFarPlane;
}
/// <summary>
/// Converts a screen position to a world position in 3D space.
/// </summary>
/// <param name="parScreenPosition">The screen position to convert.</param>
/// <returns>The corresponding world position.</returns>
public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition); public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition);
/// <summary>
/// Converts a world position to a screen position in 2D space.
/// </summary>
/// <param name="parWorldPosition">The world position to convert.</param>
/// <returns>The corresponding screen position.</returns>
public abstract Vector2 WorldToScreen(Vector3 parWorldPosition); public abstract Vector2 WorldToScreen(Vector3 parWorldPosition);
} }

View File

@@ -3,33 +3,56 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn; namespace Engine.Scene.Component.BuiltIn;
public class OrthographicCamera( /// <summary>
float parNearPlane = -10000f, /// Represents a camera using an orthographic projection.
float parFarPlane = 10000f, /// </summary>
float parSize = 10f, public class OrthographicCamera : Camera
OrthographicCamera.Axis parAxis = OrthographicCamera.Axis.Y
)
: Camera(parNearPlane, parFarPlane)
{ {
/// <summary>
/// The axis fixed during scaling.
/// </summary>
public enum Axis public enum Axis
{ {
X, X,
Y Y
} }
/// <inheritdoc/>
public override Matrix4 Projection => GetProjectionMatrix(); public override Matrix4 Projection => GetProjectionMatrix();
/// <inheritdoc/>
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted(); public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted();
public float Size { get; set; } = parSize; /// <summary>
public bool UseScreenSize { get; set; } = false; /// The size of the orthographic view.
public Axis FixedAxis { get; set; } = parAxis; /// </summary>
public float Size { get; set; }
private Matrix4 GetProjectionMatrix() /// <summary>
/// Indicates whether to scale based on screen dimensions.
/// </summary>
public bool UseScreenSize { get; set; } = false;
/// <summary>
/// The axis to keep fixed when scaling the view.
/// </summary>
public Axis FixedAxis { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OrthographicCamera"/> class.
/// </summary>
/// <param name="parNearPlane">The near clipping plane distance.</param>
/// <param name="parFarPlane">The far clipping plane distance.</param>
/// <param name="parSize">The size of the orthographic view.</param>
/// <param name="parAxis">The fixed axis for scaling.</param>
public OrthographicCamera(float parNearPlane = -10000f, float parFarPlane = 10000f, float parSize = 10f,
Axis parAxis = Axis.Y) : base(parNearPlane, parFarPlane)
{ {
var size = GameObject.Transform.Size.Xy; Size = parSize;
return Matrix4.CreateOrthographic(size.X, size.Y, -NearPlane, -FarPlane); FixedAxis = parAxis;
} }
/// <inheritdoc/>
public override Vector3 ScreenToWorld(Vector2 parScreenPosition) public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{ {
var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f); var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f);
@@ -42,6 +65,7 @@ public class OrthographicCamera(
.Xyz; .Xyz;
} }
/// <inheritdoc/>
public override Vector2 WorldToScreen(Vector3 parWorldPosition) public override Vector2 WorldToScreen(Vector3 parWorldPosition)
{ {
var normalized = new Vector4(parWorldPosition, 1) var normalized = new Vector4(parWorldPosition, 1)
@@ -55,6 +79,7 @@ public class OrthographicCamera(
return (normalized + new Vector2(0.5f)) * ScreenSize; return (normalized + new Vector2(0.5f)) * ScreenSize;
} }
/// <inheritdoc/>
public override void Update(double parDeltaTime) public override void Update(double parDeltaTime)
{ {
if (UseScreenSize) if (UseScreenSize)
@@ -75,4 +100,14 @@ public class OrthographicCamera(
} }
} }
} }
/// <summary>
/// Generates the orthographic projection matrix for the camera.
/// </summary>
/// <returns>The orthographic projection matrix.</returns>
private Matrix4 GetProjectionMatrix()
{
var size = GameObject.Transform.Size.Xy;
return Matrix4.CreateOrthographic(size.X, size.Y, -NearPlane, -FarPlane);
}
} }

View File

@@ -3,33 +3,42 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn; namespace Engine.Scene.Component.BuiltIn;
public class PerspectiveCamera( /// <summary>
float parFieldOfView = 60.0f, /// Represents a camera using a perspective projection.
float parNearPlane = 0.01f, /// </summary>
float parFarPlane = 1000f public class PerspectiveCamera : Camera
)
: Camera(parNearPlane, parFarPlane)
{ {
public override Matrix4 View /// <inheritdoc/>
{ public override Matrix4 View => GetProjectionMatrix();
get
{
var transformMatrix = GameObject.Transform.TransformMatrix;
var forward = new Vector4(0, 1, 0, 1).MulProject(in transformMatrix);
var eye = new Vector4(0, 0, 0, 1).MulProject(in transformMatrix);
var up = (new Vector4(0, 0, 1, 1).MulProject(in transformMatrix) - eye).Normalized();
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
}
}
/// <inheritdoc/>
public override Matrix4 Projection => public override Matrix4 Projection =>
Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(FieldOfView), AspectRatio, Matrix4.CreatePerspectiveFieldOfView(MathHelper.DegreesToRadians(FieldOfView), AspectRatio,
NearPlane, FarPlane); NearPlane, FarPlane);
/// <summary>
/// The forward direction of the camera.
/// </summary>
public Vector3 Forward => new Vector4(0, 1, 0, 1).MulProject(GameObject.Transform.TransformMatrix).Xyz; public Vector3 Forward => new Vector4(0, 1, 0, 1).MulProject(GameObject.Transform.TransformMatrix).Xyz;
public float FieldOfView { get; set; } = parFieldOfView;
/// <summary>
/// The vertical field of view of the camera, in degrees.
/// </summary>
public float FieldOfView { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PerspectiveCamera"/> class.
/// </summary>
/// <param name="parFieldOfView">The vertical field of view, in degrees.</param>
/// <param name="parNearPlane">The near clipping plane distance.</param>
/// <param name="parFarPlane">The far clipping plane distance.</param>
public PerspectiveCamera(float parFieldOfView = 60.0f, float parNearPlane = 0.01f, float parFarPlane = 1000f) : base(
parNearPlane, parFarPlane)
{
FieldOfView = parFieldOfView;
}
/// <inheritdoc/>
public override Vector3 ScreenToWorld(Vector2 parScreenPosition) public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{ {
var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f); var normalized = (parScreenPosition / ScreenSize) - new Vector2(0.5f);
@@ -42,6 +51,7 @@ public class PerspectiveCamera(
.Xyz; .Xyz;
} }
/// <inheritdoc/>
public override Vector2 WorldToScreen(Vector3 parWorldPosition) public override Vector2 WorldToScreen(Vector3 parWorldPosition)
{ {
var normalized = new Vector4(parWorldPosition, 1) var normalized = new Vector4(parWorldPosition, 1)
@@ -54,4 +64,18 @@ public class PerspectiveCamera(
return (normalized + new Vector2(0.5f)) * ScreenSize; return (normalized + new Vector2(0.5f)) * ScreenSize;
} }
/// <summary>
/// Generates the perspective projection matrix for the camera.
/// </summary>
/// <returns>The perspective projection matrix.</returns>
private Matrix4 GetProjectionMatrix()
{
var transformMatrix = GameObject.Transform.TransformMatrix;
var forward = new Vector4(0, 1, 0, 1).MulProject(in transformMatrix);
var eye = new Vector4(0, 0, 0, 1).MulProject(in transformMatrix);
var up = (new Vector4(0, 0, 1, 1).MulProject(in transformMatrix) - eye).Normalized();
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
}
} }

View File

@@ -4,14 +4,32 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn.Renderer; namespace Engine.Scene.Component.BuiltIn.Renderer;
/// <summary>
/// A component for rendering 2D boxes with an optional texture and color.
/// </summary>
public class Box2DRenderer : Component public class Box2DRenderer : Component
{ {
/// <summary>
/// The color of the box.
/// </summary>
public ref Vector4 Color => ref _color; public ref Vector4 Color => ref _color;
/// <summary>
/// The texture of the box.
/// </summary>
public Texture? Texture { get; set; } public Texture? Texture { get; set; }
/// <summary>
/// The render layer for the box.
/// </summary>
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT; public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
/// <summary>
/// The color of the box.
/// </summary>
private Vector4 _color = Vector4.One; private Vector4 _color = Vector4.One;
/// <inheritdoc/>
public override void Render() public override void Render()
{ {
Engine.Instance.Renderer[RenderLayer].QuadRenderer Engine.Instance.Renderer[RenderLayer].QuadRenderer

View File

@@ -4,12 +4,27 @@ using Engine.Graphics.Texture;
namespace Engine.Scene.Component.BuiltIn.Renderer; namespace Engine.Scene.Component.BuiltIn.Renderer;
/// <summary>
/// A component for rendering 3D meshes with optional albedo textures.
/// </summary>
public class MeshRenderer : Component public class MeshRenderer : Component
{ {
/// <summary>
/// The mesh to be rendered.
/// </summary>
public Mesh Mesh { get; set; } = null!; public Mesh Mesh { get; set; } = null!;
/// <summary>
/// The albedo texture for the mesh.
/// </summary>
public Texture? Albedo { get; set; } = null; public Texture? Albedo { get; set; } = null;
/// <summary>
/// The render layer for the mesh.
/// </summary>
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT; public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
/// <inheritdoc/>
public override void Render() public override void Render()
{ {
Engine.Instance.Renderer[RenderLayer].AnyMeshRenderer Engine.Instance.Renderer[RenderLayer].AnyMeshRenderer

View File

@@ -4,15 +4,37 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn.Renderer; namespace Engine.Scene.Component.BuiltIn.Renderer;
/// <summary>
/// A component for rendering text with a specified font, color, and render layer.
/// </summary>
public class TextRenderer : Component public class TextRenderer : Component
{ {
/// <summary>
/// The font used for rendering the text.
/// </summary>
public Font Font { get; set; } = null!; public Font Font { get; set; } = null!;
/// <summary>
/// The color of the text.
/// </summary>
public ref Vector4 Color => ref _color; public ref Vector4 Color => ref _color;
/// <summary>
/// The text to be rendered.
/// </summary>
public string? Text { get; set; } public string? Text { get; set; }
/// <summary>
/// The render layer for the text.
/// </summary>
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT; public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
/// <summary>
/// The color of the text.
/// </summary>
private Vector4 _color = Vector4.One; private Vector4 _color = Vector4.One;
/// <inheritdoc/>
public override void Render() public override void Render()
{ {
if (Text == null) if (Text == null)

View File

@@ -2,20 +2,51 @@
namespace Engine.Scene.Component.BuiltIn; namespace Engine.Scene.Component.BuiltIn;
/// <summary>
/// Represents the transform component of a game object, encapsulating its position, rotation, scale, and size.
/// </summary>
public class Transform : Component public class Transform : Component
{ {
/// <summary>
/// The size of the object in local space.
/// </summary>
public ref Vector3 Size => ref _size; public ref Vector3 Size => ref _size;
/// <summary>
/// The scaling factor applied to the object in local space.
/// </summary>
public ref Vector3 Scale => ref _scale; public ref Vector3 Scale => ref _scale;
/// <summary>
/// The rotation of the object in local space, represented as a quaternion.
/// </summary>
public ref Quaternion Rotation => ref _rotation; public ref Quaternion Rotation => ref _rotation;
/// <summary>
/// The translation (position) of the object in local space.
/// </summary>
public ref Vector3 Translation => ref _translation; public ref Vector3 Translation => ref _translation;
/// <summary>
/// The full transformation matrix that combines size, scaling, rotation, and translation.
/// </summary>
public Matrix4 FullTransformMatrix => Matrix4.CreateScale(Size) * TransformMatrix; public Matrix4 FullTransformMatrix => Matrix4.CreateScale(Size) * TransformMatrix;
/// <summary>
/// The combined transformation matrix, accounting for local and parent transformations.
/// </summary>
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix; public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
/// <summary>
/// The local transformation matrix, derived from scale, rotation, and translation.
/// </summary>
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) * public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
Matrix4.CreateFromQuaternion(Rotation) * Matrix4.CreateFromQuaternion(Rotation) *
Matrix4.CreateTranslation(Translation); Matrix4.CreateTranslation(Translation);
/// <summary>
/// The parent transformation matrix, retrieved from the parent object in the hierarchy.
/// </summary>
private Matrix4 ParentTransformMatrix private Matrix4 ParentTransformMatrix
{ {
get get
@@ -25,16 +56,39 @@ public class Transform : Component
} }
} }
/// <summary>
/// The size of the object in local space.
/// </summary>
private Vector3 _size = Vector3.One; private Vector3 _size = Vector3.One;
/// <summary>
/// The scaling factor applied to the object in local space.
/// </summary>
private Vector3 _scale = Vector3.One; private Vector3 _scale = Vector3.One;
/// <summary>
/// The rotation of the object in local space, represented as a quaternion.
/// </summary>
private Quaternion _rotation = Quaternion.Identity; private Quaternion _rotation = Quaternion.Identity;
/// <summary>
/// The translation (position) of the object in local space.
/// </summary>
private Vector3 _translation = Vector3.Zero; private Vector3 _translation = Vector3.Zero;
/// <summary>
/// Retrieves the full translation of the object in world space.
/// </summary>
/// <returns>A <see cref="Vector3"/> representing the object's world position.</returns>
public Vector3 GetFullTranslation() public Vector3 GetFullTranslation()
{ {
return FullTransformMatrix.ExtractTranslation(); return FullTransformMatrix.ExtractTranslation();
} }
/// <summary>
/// Creates a clone of the current transform.
/// </summary>
/// <returns>A new <see cref="Transform"/> instance with the same properties as the original.</returns>
public Transform Clone() public Transform Clone()
{ {
var clone = var clone =

View File

@@ -1,46 +1,89 @@
namespace Engine.Scene.Component; namespace Engine.Scene.Component;
/// <summary>
/// Base class for all components, defining common lifecycle and behavior methods.
/// </summary>
public abstract class Component : IUpdate, IRender public abstract class Component : IUpdate, IRender
{ {
/// <summary>
/// A unique identifier for the component.
/// </summary>
public Guid Id { get; } = Guid.NewGuid(); public Guid Id { get; } = Guid.NewGuid();
/// <summary>
/// The game object to which this component is attached.
/// </summary>
public GameObject GameObject { get; internal set; } public GameObject GameObject { get; internal set; }
/// <summary>
/// Called when the component is initialized.
/// </summary>
public virtual void Awake() public virtual void Awake()
{ {
} }
/// <summary>
/// Called once at the start of the component's lifecycle.
/// </summary>
public virtual void Start() public virtual void Start()
{ {
} }
/// <summary>
/// Called before the main update loop.
/// </summary>
/// <param name="parDeltaTime">Time elapsed since the last frame.</param>
public virtual void PreUpdate(double parDeltaTime) public virtual void PreUpdate(double parDeltaTime)
{ {
} }
/// <summary>
/// Called during the main update loop.
/// </summary>
/// <param name="parDeltaTime">Time elapsed since the last frame.</param>
public virtual void Update(double parDeltaTime) public virtual void Update(double parDeltaTime)
{ {
} }
/// <summary>
/// Called during the main render loop.
/// </summary>
public virtual void Render() public virtual void Render()
{ {
} }
/// <summary>
/// Called when the component is destroyed.
/// </summary>
public virtual void Destroy() public virtual void Destroy()
{ {
} }
/// <summary>
/// Enables the component, allowing it to participate in updates and rendering.
/// </summary>
public virtual void Enable() public virtual void Enable()
{ {
} }
/// <summary>
/// Disables the component, stopping it from participating in updates and rendering.
/// </summary>
public virtual void Disable() public virtual void Disable()
{ {
} }
} }
public static class ComponentTypeExtensions /// <summary>
/// Provides extension methods for component type analysis.
/// </summary>
internal static class ComponentTypeExtensions
{ {
/// <summary>
/// Retrieves the base type of a component, accounting for inheritance chains.
/// </summary>
/// <param name="parType">The type of the component.</param>
/// <returns>The base <see cref="Type"/> of the component.</returns>
internal static Type GetComponentBaseType(this Type parType) internal static Type GetComponentBaseType(this Type parType)
{ {
var baseType = parType.BaseType; var baseType = parType.BaseType;

View File

@@ -4,32 +4,83 @@ using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Represents a game object in the scene, containing components and providing lifecycle management.
/// </summary>
public sealed class GameObject : IUpdate, IRender public sealed class GameObject : IUpdate, IRender
{ {
/// <summary>
/// A unique identifier of the game object.
/// </summary>
public Guid Id { get; } = Guid.NewGuid(); public Guid Id { get; } = Guid.NewGuid();
/// <summary>
/// Indicates whether the game object is enabled based on its own state and its parent's state.
/// </summary>
public bool IsEnabled public bool IsEnabled
{ {
get => IsSelfEnabled && IsParentEnabled; get => IsSelfEnabled && IsParentEnabled;
set => _nextIsSelfEnabled = value; set => _nextIsSelfEnabled = value;
} }
/// <summary>
/// The transform component associated with this game object.
/// </summary>
public Transform Transform { get; } public Transform Transform { get; }
/// <summary>
/// The scene this game object belongs to.
/// </summary>
internal Scene? Scene { get; set; } internal Scene? Scene { get; set; }
/// <summary>
/// Indicates whether game object's parent is enabled.
/// </summary>
private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true; private bool IsParentEnabled => Scene?.Hierarchy.GetParent(this)?.IsEnabled ?? true;
/// <summary>
/// Indicates whether game object is enabled.
/// </summary>
private bool IsSelfEnabled { get; set; } = true; private bool IsSelfEnabled { get; set; } = true;
private readonly HashSet<Component.Component> _addedComponents = []; /// <summary>
/// A list of component types added to the game object.
/// </summary>
private readonly HashSet<Type> _addedComponentTypes = []; private readonly HashSet<Type> _addedComponentTypes = [];
private readonly Queue<Action> _componentActions = new();
/// <summary>
/// A list of components attached to the game object.
/// </summary>
private readonly List<Component.Component> _components = []; private readonly List<Component.Component> _components = [];
/// <summary>
/// A queue of actions to be performed on the game object.
/// </summary>
private readonly Queue<Action> _componentActions = new();
/// <summary>
/// A list of components to be added to the game object.
/// </summary>
private readonly HashSet<Component.Component> _addedComponents = [];
/// <summary>
/// A list of components to be removed from the game object.
/// </summary>
private readonly HashSet<Component.Component> _removedComponents = []; private readonly HashSet<Component.Component> _removedComponents = [];
/// <summary>
/// Indicates whether the game object is going to be enabled next frame.
/// </summary>
private bool _nextIsSelfEnabled = true; private bool _nextIsSelfEnabled = true;
/// <summary>
/// Indicates whether the game object was enabled last frame.
/// </summary>
private bool _prevIsSelfEnabled = true; private bool _prevIsSelfEnabled = true;
/// <summary>
/// Initializes a new instance of the <see cref="GameObject"/> class with a default transform component.
/// </summary>
public GameObject() public GameObject()
{ {
AddComponent<Transform>(); AddComponent<Transform>();
@@ -38,6 +89,10 @@ public sealed class GameObject : IUpdate, IRender
Transform = GetComponent<Transform>()!; Transform = GetComponent<Transform>()!;
} }
/// <summary>
/// Initializes a new instance of the <see cref="GameObject"/> class with a specified transform.
/// </summary>
/// <param name="parTransform">The transform to associate with the game object.</param>
public GameObject(Transform parTransform) public GameObject(Transform parTransform)
{ {
AddComponent(parTransform.Clone()); AddComponent(parTransform.Clone());
@@ -46,6 +101,10 @@ public sealed class GameObject : IUpdate, IRender
Transform = GetComponent<Transform>()!; Transform = GetComponent<Transform>()!;
} }
/// <summary>
/// Performs pre-update operations for all components.
/// </summary>
/// <param name="parDeltaTime">The time delta since the last update.</param>
public void PreUpdate(double parDeltaTime) public void PreUpdate(double parDeltaTime)
{ {
ProcessAddedComponents(); ProcessAddedComponents();
@@ -57,6 +116,7 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (!IsEnabled) if (!IsEnabled)
@@ -91,6 +151,7 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
/// <inheritdoc/>
public void Render() public void Render()
{ {
if (!IsEnabled) if (!IsEnabled)
@@ -104,6 +165,9 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
/// <summary>
/// Destroys the game object and its components.
/// </summary>
public void Destroy() public void Destroy()
{ {
foreach (var component in _components) foreach (var component in _components)
@@ -112,6 +176,11 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
/// <summary>
/// Retrieves a component of a specified type if it exists on the game object.
/// </summary>
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
/// <returns>The component if found, otherwise null.</returns>
public T? GetComponent<T>() where T : Component.Component public T? GetComponent<T>() where T : Component.Component
{ {
if (!HasComponent<T>()) if (!HasComponent<T>())
@@ -130,6 +199,11 @@ public sealed class GameObject : IUpdate, IRender
return null; return null;
} }
/// <summary>
/// Retrieves a component of a specified type from the game object or its children.
/// </summary>
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
/// <returns>The component if found, otherwise null.</returns>
public T? GetComponentAny<T>() where T : Component.Component public T? GetComponentAny<T>() where T : Component.Component
{ {
var component = GetComponent<T>(); var component = GetComponent<T>();
@@ -142,6 +216,11 @@ public sealed class GameObject : IUpdate, IRender
return component; return component;
} }
/// <summary>
/// Retrieves a component of a specified type from the game object's children.
/// </summary>
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
/// <returns>The component if found, otherwise null.</returns>
public T? GetComponentInChildren<T>() where T : Component.Component public T? GetComponentInChildren<T>() where T : Component.Component
{ {
var children = Scene!.Hierarchy.GetChildren(this); var children = Scene!.Hierarchy.GetChildren(this);
@@ -164,12 +243,21 @@ public sealed class GameObject : IUpdate, IRender
return null; return null;
} }
/// <summary>
/// Adds a new component of a specified type to the game object.
/// </summary>
/// <typeparam name="T">The type of the component to add.</typeparam>
public void AddComponent<T>() where T : Component.Component, new() public void AddComponent<T>() where T : Component.Component, new()
{ {
var component = new T(); var component = new T();
AddComponent(component); AddComponent(component);
} }
/// <summary>
/// Adds a new component of a specified type to the game object with optional constructor arguments.
/// </summary>
/// <typeparam name="T">The type of the component to add.</typeparam>
/// <param name="parArgs">The arguments to pass to the component's constructor.</param>
public void AddComponent<T>(params object?[] parArgs) where T : Component.Component public void AddComponent<T>(params object?[] parArgs) where T : Component.Component
{ {
var component = (T?)Activator.CreateInstance( var component = (T?)Activator.CreateInstance(
@@ -189,6 +277,11 @@ public sealed class GameObject : IUpdate, IRender
AddComponent(component); AddComponent(component);
} }
/// <summary>
/// Adds an existing component to the game object.
/// </summary>
/// <typeparam name="T">The type of the component to add.</typeparam>
/// <param name="parComponent">The component to add.</param>
public void AddComponent<T>(T parComponent) where T : Component.Component public void AddComponent<T>(T parComponent) where T : Component.Component
{ {
parComponent.GameObject = this; parComponent.GameObject = this;
@@ -206,6 +299,10 @@ public sealed class GameObject : IUpdate, IRender
}); });
} }
/// <summary>
/// Removes a component of a specified type from the game object.
/// </summary>
/// <typeparam name="T">The type of the component to remove.</typeparam>
public void RemoveComponent<T>() where T : Component.Component public void RemoveComponent<T>() where T : Component.Component
{ {
if (typeof(T) == typeof(Transform)) if (typeof(T) == typeof(Transform))
@@ -232,12 +329,20 @@ public sealed class GameObject : IUpdate, IRender
}); });
} }
/// <summary>
/// Checks whether the game object contains a component of a specified type.
/// </summary>
/// <typeparam name="T">The type of the component to check for.</typeparam>
/// <returns>True if the component exists, otherwise false.</returns>
public bool HasComponent<T>() where T : Component.Component public bool HasComponent<T>() where T : Component.Component
{ {
var baseType = typeof(T).GetComponentBaseType(); var baseType = typeof(T).GetComponentBaseType();
return _addedComponentTypes.Contains(baseType); return _addedComponentTypes.Contains(baseType);
} }
/// <summary>
/// Processes changes to the game object's components.
/// </summary>
internal void ProcessChanges() internal void ProcessChanges()
{ {
IsSelfEnabled = _nextIsSelfEnabled; IsSelfEnabled = _nextIsSelfEnabled;
@@ -248,6 +353,9 @@ public sealed class GameObject : IUpdate, IRender
} }
} }
/// <summary>
/// Initializes newly added components.
/// </summary>
private void ProcessAddedComponents() private void ProcessAddedComponents()
{ {
foreach (var component in _addedComponents) foreach (var component in _addedComponents)
@@ -259,6 +367,9 @@ public sealed class GameObject : IUpdate, IRender
_addedComponents.Clear(); _addedComponents.Clear();
} }
/// <summary>
/// Cleans up and removes components that were marked for removal.
/// </summary>
private void ProcessRemovedComponents() private void ProcessRemovedComponents()
{ {
foreach (var component in _removedComponents) foreach (var component in _removedComponents)
@@ -270,16 +381,19 @@ public sealed class GameObject : IUpdate, IRender
_removedComponents.Clear(); _removedComponents.Clear();
} }
/// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return Id.ToString(); return Id.ToString();
} }
/// <inheritdoc/>
public override bool Equals(object? parObj) public override bool Equals(object? parObj)
{ {
return parObj is GameObject gameObject && Id == gameObject.Id; return parObj is GameObject gameObject && Id == gameObject.Id;
} }
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(Id); return HashCode.Combine(Id);

View File

@@ -3,20 +3,47 @@ using Engine.Util;
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Manages a hierarchical relationship between objects, supporting parent-child relationships
/// and enabling traversal, adding/removing of objects within the hierarchy.
/// </summary>
/// <typeparam name="T">The type of the objects in the hierarchy. Must be a reference type.</typeparam>
public class Hierarchy<T> public class Hierarchy<T>
where T : class where T : class
{ {
/// <summary>
/// A collection of all objects in the hierarchy (all keys in the parent lookup dictionary).
/// </summary>
internal Dictionary<T, T?>.KeyCollection Objects => _parentLookup.Keys; internal Dictionary<T, T?>.KeyCollection Objects => _parentLookup.Keys;
/// <summary>
/// A dictionary mapping objects to their children.
/// </summary>
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new(); private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
/// <summary>
/// A dictionary mapping objects to their parent.
/// </summary>
private readonly Dictionary<T, T?> _parentLookup = new(); private readonly Dictionary<T, T?> _parentLookup = new();
/// <summary>
/// A queue of actions to be processed.
/// </summary>
private readonly ConcurrentQueue<Action> _hierarchyActions = new(); private readonly ConcurrentQueue<Action> _hierarchyActions = new();
/// <summary>
/// Initializes a new instance of the <see cref="Hierarchy{T}"/> class.
/// Adds a root element with a null value to represent the root node.
/// </summary>
public Hierarchy() public Hierarchy()
{ {
_childrenLookup.Add(new NullableObject<T>(), new List<T>()); _childrenLookup.Add(new NullableObject<T>(), new List<T>());
} }
/// <summary>
/// Processes queued actions related to changes in the hierarchy (e.g., adding/removing objects).
/// This method is invoked to ensure the changes are executed in the proper order.
/// </summary>
internal void ProcessChanges() internal void ProcessChanges()
{ {
while (_hierarchyActions.TryDequeue(out var action)) while (_hierarchyActions.TryDequeue(out var action))
@@ -25,6 +52,11 @@ public class Hierarchy<T>
} }
} }
/// <summary>
/// Adds an object to the hierarchy. This method will create a new entry in the hierarchy
/// with the object having no parent initially, and will add it as a child of the root.
/// </summary>
/// <param name="parObj">The object to add to the hierarchy.</param>
public void Add(T parObj) public void Add(T parObj)
{ {
_hierarchyActions.Enqueue(() => _hierarchyActions.Enqueue(() =>
@@ -40,6 +72,11 @@ public class Hierarchy<T>
}); });
} }
/// <summary>
/// Removes an object from the hierarchy and all its descendants.
/// The object will be deleted, and its references will be removed from both parent and child lookup dictionaries.
/// </summary>
/// <param name="parObj">The object to remove from the hierarchy.</param>
public void Remove(T parObj) public void Remove(T parObj)
{ {
foreach (var child in GetChildren(parObj)) foreach (var child in GetChildren(parObj))
@@ -62,11 +99,23 @@ public class Hierarchy<T>
}); });
} }
/// <summary>
/// Adds a child object to a specific parent in the hierarchy.
/// This sets the given object's parent and adds it to the parent's list of children.
/// </summary>
/// <param name="parParent">The parent object.</param>
/// <param name="parChild">The child object to add to the parent.</param>
public void AddChild(T parParent, T parChild) public void AddChild(T parParent, T parChild)
{ {
SetParent(parChild, parParent); SetParent(parChild, parParent);
} }
/// <summary>
/// Sets the parent of an object in the hierarchy.
/// This method will remove the object from its current parent and add it to the new parent.
/// </summary>
/// <param name="parChild">The child object whose parent is being changed.</param>
/// <param name="parParent">The new parent of the child object.</param>
private void SetParent(T parChild, T? parParent) private void SetParent(T parChild, T? parParent)
{ {
if (parChild.Equals(parParent)) if (parChild.Equals(parParent))
@@ -89,11 +138,22 @@ public class Hierarchy<T>
}); });
} }
/// <summary>
/// Determines if an object exists in the hierarchy.
/// </summary>
/// <param name="parObj">The object to check for presence in the hierarchy.</param>
/// <returns>True if the object exists in the hierarchy, otherwise false.</returns>
public bool Contains(T parObj) public bool Contains(T parObj)
{ {
return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj); return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj);
} }
/// <summary>
/// Gets the parent of a given child object in the hierarchy.
/// </summary>
/// <param name="parChild">The child object whose parent is to be retrieved.</param>
/// <returns>The parent object of the given child.</returns>
/// <exception cref="InvalidOperationException">Thrown if the child is not found in the hierarchy.</exception>
public T? GetParent(T parChild) public T? GetParent(T parChild)
{ {
return _parentLookup.TryGetValue(parChild, out var parent) return _parentLookup.TryGetValue(parChild, out var parent)
@@ -101,11 +161,22 @@ public class Hierarchy<T>
: throw new InvalidOperationException($"Child {parChild} is not in hierarchy"); : throw new InvalidOperationException($"Child {parChild} is not in hierarchy");
} }
/// <summary>
/// Gets the children of a given parent object in the hierarchy.
/// If no parent is provided, it retrieves the children of the root node.
/// </summary>
/// <param name="parParent">The parent object whose children are to be retrieved. Null refers to the root.</param>
/// <returns>A collection of child objects of the specified parent.</returns>
public IEnumerable<T> GetChildren(T? parParent = null) public IEnumerable<T> GetChildren(T? parParent = null)
{ {
return _childrenLookup.TryGetValue(parParent, out var children) ? children : Enumerable.Empty<T>(); return _childrenLookup.TryGetValue(parParent, out var children) ? children : Enumerable.Empty<T>();
} }
/// <summary>
/// Gets all descendants of a given parent object in the hierarchy, recursively.
/// </summary>
/// <param name="parParent">The parent object whose descendants are to be retrieved. Null refers to the root.</param>
/// <returns>A collection of all descendant objects of the specified parent.</returns>
public IEnumerable<T> GetAllChildren(T? parParent = null) public IEnumerable<T> GetAllChildren(T? parParent = null)
{ {
var children = GetChildren(parParent); var children = GetChildren(parParent);
@@ -121,6 +192,12 @@ public class Hierarchy<T>
} }
} }
/// <summary>
/// Checks if a given child object is in the hierarchy of a given ancestor object.
/// </summary>
/// <param name="parAncestor">The ancestor object to check.</param>
/// <param name="parChild">The child object to check for inclusion in the hierarchy.</param>
/// <returns>True if the child is in the hierarchy of the ancestor, otherwise false.</returns>
public bool IsInHierarchy(T? parAncestor, T? parChild) public bool IsInHierarchy(T? parAncestor, T? parChild)
{ {
if (parChild == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent if (parChild == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent

View File

@@ -1,6 +1,12 @@
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Defines an interface for rendering objects.
/// </summary>
public interface IRender public interface IRender
{ {
void Render(); /// <summary>
/// Renders the object.
/// </summary>
public void Render();
} }

View File

@@ -1,6 +1,13 @@
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Defines an interface for updating logic over time.
/// </summary>
public interface IUpdate public interface IUpdate
{ {
void Update(double parDeltaTime); /// <summary>
/// Updates the state of the object.
/// </summary>
/// <param name="parDeltaTime">The time elapsed since the last update, in seconds.</param>
public void Update(double parDeltaTime);
} }

View File

@@ -4,17 +4,43 @@ using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Represents a scene in the game, managing the scene hierarchy and rendering the scene.
/// </summary>
public class Scene : IUpdate, IRender public class Scene : IUpdate, IRender
{ {
/// <summary>
/// Determines whether the scene is currently playing.
/// </summary>
public bool IsPlaying { get; private set; } public bool IsPlaying { get; private set; }
/// <summary>
/// The time scale for updating the scene. A value of 1.0 means normal speed.
/// </summary>
public float TimeScale { get; set; } = 1.0f; public float TimeScale { get; set; } = 1.0f;
/// <summary>
/// A hierarchy of game objects in the scene.
/// </summary>
internal Hierarchy<GameObject> Hierarchy { get; } = new(); internal Hierarchy<GameObject> Hierarchy { get; } = new();
/// <summary>
/// A read-only dictionary of cameras, categorized by render layer.
/// </summary>
internal IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras; internal IReadOnlyDictionary<RenderLayer, ICamera> Cameras => _cameras;
/// <summary>
/// A dictionary of cameras, categorized by render layer.
/// </summary>
private readonly Dictionary<RenderLayer, ICamera> _cameras = new(); private readonly Dictionary<RenderLayer, ICamera> _cameras = new();
/// <summary>
/// A private queue used to enqueue scene-related actions that should be processed later.
/// Actions are executed when the scene is processed for changes.
/// </summary>
private readonly Queue<Action> _sceneActions = []; private readonly Queue<Action> _sceneActions = [];
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (!IsPlaying) if (!IsPlaying)
@@ -37,6 +63,7 @@ public class Scene : IUpdate, IRender
} }
} }
/// <inheritdoc/>
public void Render() public void Render()
{ {
if (!IsPlaying) if (!IsPlaying)
@@ -50,6 +77,12 @@ public class Scene : IUpdate, IRender
} }
} }
/// <summary>
/// Finds all components of type <typeparamref name="T"/> in the scene.
/// </summary>
/// <typeparam name="T">The type of component to find.</typeparam>
/// <param name="parOnlyEnabled">If true, only enabled components are returned.</param>
/// <returns>A list of components of type <typeparamref name="T"/>.</returns>
public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component public List<T> FindAllComponents<T>(bool parOnlyEnabled = true) where T : Component.Component
{ {
return Hierarchy.Objects return Hierarchy.Objects
@@ -60,24 +93,42 @@ public class Scene : IUpdate, IRender
.ToList()!; .ToList()!;
} }
/// <summary>
/// Finds the first component of type <typeparamref name="T"/> in the scene.
/// </summary>
/// <typeparam name="T">The type of component to find.</typeparam>
/// <returns>The first component of type <typeparamref name="T"/>, or null if not found.</returns>
public T? FindFirstComponent<T>() where T : Component.Component public T? FindFirstComponent<T>() where T : Component.Component
{ {
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>()) return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>())
.FirstOrDefault(parComponent => parComponent != null); .FirstOrDefault(parComponent => parComponent != null);
} }
/// <summary>
/// Adds a game object to the scene.
/// </summary>
/// <param name="parGameObject">The game object to add.</param>
public void Add(GameObject parGameObject) public void Add(GameObject parGameObject)
{ {
parGameObject.Scene = this; parGameObject.Scene = this;
Hierarchy.Add(parGameObject); Hierarchy.Add(parGameObject);
} }
/// <summary>
/// Adds a child game object to the specified parent game object in the scene.
/// </summary>
/// <param name="parParent">The parent game object.</param>
/// <param name="parChild">The child game object to add.</param>
public void AddChild(GameObject parParent, GameObject parChild) public void AddChild(GameObject parParent, GameObject parChild)
{ {
Add(parChild); Add(parChild);
Hierarchy.AddChild(parParent, parChild); Hierarchy.AddChild(parParent, parChild);
} }
/// <summary>
/// Removes a game object from the scene.
/// </summary>
/// <param name="parGameObject">The game object to remove.</param>
public void Remove(GameObject parGameObject) public void Remove(GameObject parGameObject)
{ {
var children = Hierarchy.GetAllChildren(parGameObject).ToList(); var children = Hierarchy.GetAllChildren(parGameObject).ToList();
@@ -97,11 +148,20 @@ public class Scene : IUpdate, IRender
}); });
} }
/// <summary>
/// Gets the children of a parent game object.
/// </summary>
/// <param name="parParent">The parent game object.</param>
/// <param name="parRecursive">If true, recursively retrieves all children; otherwise, retrieves only direct children.</param>
/// <returns>An enumeration of the child game objects.</returns>
public IEnumerable<GameObject> GetChildren(GameObject parParent, bool parRecursive = false) public IEnumerable<GameObject> GetChildren(GameObject parParent, bool parRecursive = false)
{ {
return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent); return parRecursive ? Hierarchy.GetAllChildren(parParent) : Hierarchy.GetChildren(parParent);
} }
/// <summary>
/// Enters the scene by adding all cameras and setting the scene state to playing.
/// </summary>
internal void Enter() internal void Enter()
{ {
if (IsPlaying) if (IsPlaying)
@@ -120,6 +180,9 @@ public class Scene : IUpdate, IRender
IsPlaying = true; IsPlaying = true;
} }
/// <summary>
/// Exits the scene and destroys all game objects in the scene
/// </summary>
internal void Exit() internal void Exit()
{ {
if (!IsPlaying) if (!IsPlaying)
@@ -135,6 +198,9 @@ public class Scene : IUpdate, IRender
IsPlaying = false; IsPlaying = false;
} }
/// <summary>
/// Processes changes in the hierarchy and scene actions.
/// </summary>
private void ProcessChanges() private void ProcessChanges()
{ {
Hierarchy.ProcessChanges(); Hierarchy.ProcessChanges();

View File

@@ -1,11 +1,22 @@
namespace Engine.Scene; namespace Engine.Scene;
/// <summary>
/// Manages the current scene in the game, handles scene transitions, and facilitates updating and rendering the current scene.
/// </summary>
public class SceneManager : IUpdate, IRender public class SceneManager : IUpdate, IRender
{ {
/// <summary>
/// The current scene being managed by the scene manager.
/// </summary>
public Scene? CurrentScene { get; private set; } public Scene? CurrentScene { get; private set; }
/// <summary>
/// A function to be called to transition to the next scene.
/// This is set by the scene manager when transitioning to a new scene and reset after the transition is complete.
/// </summary>
private Func<Scene>? _nextScene; private Func<Scene>? _nextScene;
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
if (_nextScene != null) if (_nextScene != null)
@@ -22,11 +33,16 @@ public class SceneManager : IUpdate, IRender
} }
} }
/// <inheritdoc/>
public void Render() public void Render()
{ {
CurrentScene?.Render(); CurrentScene?.Render();
} }
/// <summary>
/// Queues a scene transition by setting the next scene to transition to.
/// </summary>
/// <param name="parScene">A function that provides the next scene to transition to.</param>
public void TransitionTo(Func<Scene>? parScene) public void TransitionTo(Func<Scene>? parScene)
{ {
_nextScene = parScene; _nextScene = parScene;

View File

@@ -4,19 +4,45 @@ using Engine.Scene;
namespace Engine.Util; namespace Engine.Util;
/// <summary>
/// Provides utility functions and properties for interacting with the engine's core services,
/// including input handling, scene management, and asset management.
/// </summary>
public static class EngineUtil public static class EngineUtil
{ {
/// <summary>
/// The engine's input handler, which processes user input.
/// </summary>
public static IInputHandler InputHandler => Engine.Instance.InputHandler!; public static IInputHandler InputHandler => Engine.Instance.InputHandler!;
/// <summary>
/// The engine's scene manager, which handles the current scene and scene transitions.
/// </summary>
public static SceneManager SceneManager => Engine.Instance.SceneManager; public static SceneManager SceneManager => Engine.Instance.SceneManager;
/// <summary>
/// The engine's asset resource manager, which handles loading and caching of assets.
/// </summary>
public static IResourceManager AssetResourceManager => Engine.Instance.AssetResourceManager; public static IResourceManager AssetResourceManager => Engine.Instance.AssetResourceManager;
/// <summary>
/// The engine's data folder, which contains assets and other data files.
/// </summary>
public static string DataFolder => Engine.Instance.DataFolder; public static string DataFolder => Engine.Instance.DataFolder;
/// <summary>
/// Creates a game object and adds it to the current scene.
/// </summary>
/// <param name="parGameObject">The game object to be added to the scene.</param>
public static void CreateObject(GameObject parGameObject) public static void CreateObject(GameObject parGameObject)
{ {
var scene = Engine.Instance.SceneManager.CurrentScene!; var scene = Engine.Instance.SceneManager.CurrentScene!;
scene.Add(parGameObject); scene.Add(parGameObject);
} }
/// <summary>
/// Closes the engine, shutting down any running systems and freeing resources.
/// </summary>
public static void Close() public static void Close()
{ {
Engine.Instance.Close(); Engine.Instance.Close();

View File

@@ -2,8 +2,17 @@
namespace Engine.Util; namespace Engine.Util;
/// <summary>
/// Contains mathematical utility methods.
/// </summary>
public static class Math public static class Math
{ {
/// <summary>
/// Multiplies a <see cref="Vector4"/> by a <see cref="Matrix4"/> and performs projective division.
/// </summary>
/// <param name="parA">The vector to be multiplied.</param>
/// <param name="parM">The matrix to multiply the vector with.</param>
/// <returns>A transformed <see cref="Vector4"/> after multiplication and projective division.</returns>
public static Vector4 MulProject(this Vector4 parA, in Matrix4 parM) public static Vector4 MulProject(this Vector4 parA, in Matrix4 parM)
{ {
var result = parA * parM; var result = parA * parM;

View File

@@ -1,35 +1,67 @@
namespace Engine.Util; namespace Engine.Util;
public readonly struct NullableObject<T>(T? parValue) : IEquatable<NullableObject<T>> /// <summary>
/// A struct that wraps nullable reference types, providing a way to check if a value is null or not.
/// </summary>
/// <typeparam name="T">The type of the wrapped object, constrained to be a class type.</typeparam>
public readonly struct NullableObject<T> : IEquatable<NullableObject<T>>
where T : class where T : class
{ {
/// <summary>
/// Determines if the wrapped object is null.
/// </summary>
public bool IsNull => Value == null; public bool IsNull => Value == null;
public T? Value { get; } = parValue;
/// <summary>
/// The wrapped object.
/// </summary>
public T? Value { get; }
/// <summary>
/// Constructs a <see cref="NullableObject{T}"/> with a null value.
/// </summary>
public NullableObject() : this(null) public NullableObject() : this(null)
{ {
} }
/// <summary>
/// Constructs a <see cref="NullableObject{T}"/> with the specified value.
/// </summary>
/// <param name="parValue">The value to wrap.</param>
public NullableObject(T? parValue)
{
Value = parValue;
}
/// <summary>
/// Implicitly converts the <see cref="NullableObject{T}"/> to a nullable value of type <typeparamref name="T"/>.
/// </summary>
public static implicit operator T?(NullableObject<T> parNullableObject) public static implicit operator T?(NullableObject<T> parNullableObject)
{ {
return parNullableObject.Value; return parNullableObject.Value;
} }
/// <summary>
/// Implicitly converts a nullable value of type <typeparamref name="T"/> to a <see cref="NullableObject{T}"/>.
/// </summary>
public static implicit operator NullableObject<T>(T? parValue) public static implicit operator NullableObject<T>(T? parValue)
{ {
return new NullableObject<T>(parValue); return new NullableObject<T>(parValue);
} }
/// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
return Value?.ToString() ?? "null"; return Value?.ToString() ?? "null";
} }
/// <inheritdoc/>
public override bool Equals(object? parObj) public override bool Equals(object? parObj)
{ {
return parObj is NullableObject<T> other && Equals(other); return parObj is NullableObject<T> other && Equals(other);
} }
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
{ {
if (IsNull) if (IsNull)
@@ -47,16 +79,23 @@ public readonly struct NullableObject<T>(T? parValue) : IEquatable<NullableObjec
return hashCode; return hashCode;
} }
/// <inheritdoc/>
public bool Equals(NullableObject<T> parOther) public bool Equals(NullableObject<T> parOther)
{ {
return EqualityComparer<T?>.Default.Equals(Value, parOther.Value); return EqualityComparer<T?>.Default.Equals(Value, parOther.Value);
} }
/// <summary>
/// Determines whether two <see cref="NullableObject{T}"/> instances are equal.
/// </summary>
public static bool operator ==(NullableObject<T> parLeft, NullableObject<T> parRight) public static bool operator ==(NullableObject<T> parLeft, NullableObject<T> parRight)
{ {
return parLeft.Equals(parRight); return parLeft.Equals(parRight);
} }
/// <summary>
/// Determines whether two <see cref="NullableObject{T}"/> instances are not equal.
/// </summary>
public static bool operator !=(NullableObject<T> parLeft, NullableObject<T> parRight) public static bool operator !=(NullableObject<T> parLeft, NullableObject<T> parRight)
{ {
return !(parLeft == parRight); return !(parLeft == parRight);

View File

@@ -1,12 +1,27 @@
namespace Engine.Util; using Engine.Scene;
public class TickableTimer namespace Engine.Util;
public class TickableTimer : IUpdate
{ {
/// <summary>
/// Event that is invoked on every update, passing the current time.
/// </summary>
public event Action<double>? OnUpdate; public event Action<double>? OnUpdate;
/// <summary>
/// Event that is invoked when the timer reaches zero and is finished.
/// </summary>
public event Action? OnFinished; public event Action? OnFinished;
/// <summary>
/// Indicates whether the timer has finished.
/// </summary>
public bool IsFinished => _currentTime <= 0; public bool IsFinished => _currentTime <= 0;
/// <summary>
/// The current time remaining on the timer.
/// </summary>
public double CurrentTime public double CurrentTime
{ {
get => _currentTime; get => _currentTime;
@@ -37,6 +52,9 @@ public class TickableTimer
} }
} }
/// <summary>
/// The total duration of the timer.
/// </summary>
public double TotalTime public double TotalTime
{ {
get => _totalTime; get => _totalTime;
@@ -52,9 +70,20 @@ public class TickableTimer
} }
} }
/// <summary>
/// The current time remaining on the timer.
/// </summary>
private double _currentTime; private double _currentTime;
/// <summary>
/// The total duration of the timer.
/// </summary>
private double _totalTime; private double _totalTime;
/// <summary>
/// Initializes a new <see cref="TickableTimer"/> with a specified total time.
/// </summary>
/// <param name="parTotalTime">The total duration of the timer.</param>
public TickableTimer(double parTotalTime) public TickableTimer(double parTotalTime)
{ {
if (parTotalTime <= 0) if (parTotalTime <= 0)
@@ -66,11 +95,15 @@ public class TickableTimer
_currentTime = parTotalTime; _currentTime = parTotalTime;
} }
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
CurrentTime -= parDeltaTime; CurrentTime -= parDeltaTime;
} }
/// <summary>
/// Resets the timer back to its total duration.
/// </summary>
public void Reset() public void Reset()
{ {
CurrentTime = TotalTime; CurrentTime = TotalTime;

View File

@@ -7,19 +7,44 @@ using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine; namespace Engine;
/// <summary>
/// Represents a window used for rendering and interaction with the user.
/// </summary>
public class Window : IPresenter public class Window : IPresenter
{ {
public event Action<ResizeEventArgs>? Resize; /// <inheritdoc/>
public event Action<ResizeEventArgs>? OnResize;
/// <inheritdoc/>
public int Width { get; private set; } public int Width { get; private set; }
/// <inheritdoc/>
public int Height { get; private set; } public int Height { get; private set; }
/// <inheritdoc/>
public bool IsExiting => NativeWindow.IsExiting; public bool IsExiting => NativeWindow.IsExiting;
/// <summary>
/// The internal native window instance used for rendering and event handling.
/// </summary>
internal NativeWindow NativeWindow { get; } internal NativeWindow NativeWindow { get; }
/// <summary>
/// The reference to the engine instance associated with this window.
/// </summary>
private readonly Engine _engine; private readonly Engine _engine;
/// <summary>
/// Indicates whether the window is running in headless mode.
/// </summary>
private readonly bool _headless; private readonly bool _headless;
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
/// <param name="parEngine">The engine instance managing this window.</param>
/// <param name="parWindow">The native window instance.</param>
/// <param name="parHeadless">Indicates whether the window is headless.</param>
public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless) public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless)
{ {
_engine = parEngine; _engine = parEngine;
@@ -33,16 +58,18 @@ public class Window : IPresenter
{ {
Width = parArgs.Width; Width = parArgs.Width;
Height = parArgs.Height; Height = parArgs.Height;
Resize?.Invoke(parArgs); OnResize?.Invoke(parArgs);
}; };
NativeWindow.VSync = VSyncMode.On; NativeWindow.VSync = VSyncMode.On;
} }
/// <inheritdoc/>
public void Update(double parDeltaTime) public void Update(double parDeltaTime)
{ {
} }
/// <inheritdoc/>
public void Render() public void Render()
{ {
if (_headless) if (_headless)
@@ -55,6 +82,7 @@ public class Window : IPresenter
NativeWindow.SwapBuffers(); NativeWindow.SwapBuffers();
} }
/// <inheritdoc/>
public void Present(IConstTexture parTexture) public void Present(IConstTexture parTexture)
{ {
if (_headless) if (_headless)
@@ -70,14 +98,22 @@ public class Window : IPresenter
); );
} }
/// <inheritdoc/>
public void Exit() public void Exit()
{ {
NativeWindow.Close(); NativeWindow.Close();
} }
} }
/// <summary>
/// Provides extension methods for the <see cref="NativeWindow"/> class.
/// </summary>
public static class NativeWindowExtensions public static class NativeWindowExtensions
{ {
/// <summary>
/// Swaps the buffers of the specified native window.
/// </summary>
/// <param name="parWindow">The native window instance.</param>
public static unsafe void SwapBuffers(this NativeWindow parWindow) public static unsafe void SwapBuffers(this NativeWindow parWindow)
{ {
GLFW.SwapBuffers(parWindow.WindowPtr); GLFW.SwapBuffers(parWindow.WindowPtr);

View File

@@ -14,7 +14,7 @@ namespace PresenterConsole;
public class ConsolePresenter : IPresenter public class ConsolePresenter : IPresenter
{ {
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? OnResize;
public int Width { get; private set; } = 2; public int Width { get; private set; } = 2;
public int Height { get; private set; } = 1; public int Height { get; private set; } = 1;
@@ -104,7 +104,7 @@ public class ConsolePresenter : IPresenter
Width = consoleWidth; Width = consoleWidth;
Height = consoleHeight; Height = consoleHeight;
Resize?.Invoke(new ResizeEventArgs(Width / 2 * 4, Height * 4)); OnResize?.Invoke(new ResizeEventArgs(Width / 2 * 4, Height * 4));
_framebuffer.Resize(Width / 2, Height); _framebuffer.Resize(Width / 2, Height);
_consoleOutput.Resize(Width, Height); _consoleOutput.Resize(Width, Height);

View File

@@ -91,7 +91,7 @@ public partial class App : Application
private class PresenterWrapper : IPresenter private class PresenterWrapper : IPresenter
{ {
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? OnResize;
public IPresenter? Presenter public IPresenter? Presenter
{ {
@@ -100,12 +100,12 @@ public partial class App : Application
{ {
if (_presenter != null) if (_presenter != null)
{ {
_presenter.Resize -= PresenterResize; _presenter.OnResize -= PresenterResize;
} }
if (value != null) if (value != null)
{ {
value.Resize += PresenterResize; value.OnResize += PresenterResize;
} }
_presenter = value; _presenter = value;
@@ -140,7 +140,7 @@ public partial class App : Application
private void PresenterResize(ResizeEventArgs e) private void PresenterResize(ResizeEventArgs e)
{ {
Resize?.Invoke(e); OnResize?.Invoke(e);
} }
} }
} }

View File

@@ -12,7 +12,7 @@ namespace PresenterWpf;
public partial class MainWindow : Window, IPresenter public partial class MainWindow : Window, IPresenter
{ {
public event Action<ResizeEventArgs>? Resize; public event Action<ResizeEventArgs>? OnResize;
public new int Width { get; private set; } public new int Width { get; private set; }
public new int Height { get; private set; } public new int Height { get; private set; }
@@ -46,7 +46,7 @@ public partial class MainWindow : Window, IPresenter
if (_scheduledResize) if (_scheduledResize)
{ {
_scheduledResize = false; _scheduledResize = false;
Resize?.Invoke(new ResizeEventArgs(Width, Height)); OnResize?.Invoke(new ResizeEventArgs(Width, Height));
} }
} }