apply formatting

This commit is contained in:
2024-12-11 07:29:30 +03:00
parent 00fec5e053
commit 44bb459c1b
54 changed files with 2326 additions and 1558 deletions

282
.editorconfig Normal file
View File

@@ -0,0 +1,282 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 2
indent_style = space
tab_width = 2
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = true
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
# Code-block preferences
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_pattern_local_over_anonymous_function = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_range_operator = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.delegate_should_be_begins_with_d.severity = suggestion
dotnet_naming_rule.delegate_should_be_begins_with_d.symbols = delegate
dotnet_naming_rule.delegate_should_be_begins_with_d.style = begins_with_d
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.constant_should_be_constant_style.severity = suggestion
dotnet_naming_rule.constant_should_be_constant_style.symbols = constant
dotnet_naming_rule.constant_should_be_constant_style.style = constant_style
dotnet_naming_rule.all_fields_should_be_fields_style.severity = suggestion
dotnet_naming_rule.all_fields_should_be_fields_style.symbols = all_fields
dotnet_naming_rule.all_fields_should_be_fields_style.style = fields_style
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.parameter_should_be_begins_with_par.severity = suggestion
dotnet_naming_rule.parameter_should_be_begins_with_par.symbols = parameter
dotnet_naming_rule.parameter_should_be_begins_with_par.style = begins_with_par
dotnet_naming_rule.local_should_be_camel_case.severity = suggestion
dotnet_naming_rule.local_should_be_camel_case.symbols = local
dotnet_naming_rule.local_should_be_camel_case.style = camel_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.delegate.applicable_kinds = delegate
dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.delegate.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.all_fields.applicable_kinds = field
dotnet_naming_symbols.all_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.all_fields.required_modifiers =
dotnet_naming_symbols.parameter.applicable_kinds = parameter
dotnet_naming_symbols.parameter.applicable_accessibilities = *
dotnet_naming_symbols.parameter.required_modifiers =
dotnet_naming_symbols.constant.applicable_kinds = field
dotnet_naming_symbols.constant.applicable_accessibilities = *
dotnet_naming_symbols.constant.required_modifiers = const
dotnet_naming_symbols.local.applicable_kinds = local
dotnet_naming_symbols.local.applicable_accessibilities = *
dotnet_naming_symbols.local.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.camel_case.required_prefix =
dotnet_naming_style.camel_case.required_suffix =
dotnet_naming_style.camel_case.word_separator =
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.fields_style.required_prefix = _
dotnet_naming_style.fields_style.required_suffix =
dotnet_naming_style.fields_style.word_separator =
dotnet_naming_style.fields_style.capitalization = camel_case
dotnet_naming_style.constant_style.required_prefix =
dotnet_naming_style.constant_style.required_suffix =
dotnet_naming_style.constant_style.word_separator = _
dotnet_naming_style.constant_style.capitalization = all_upper
dotnet_naming_style.begins_with_d.required_prefix = d
dotnet_naming_style.begins_with_d.required_suffix =
dotnet_naming_style.begins_with_d.word_separator =
dotnet_naming_style.begins_with_d.capitalization = pascal_case
dotnet_naming_style.begins_with_par.required_prefix = par
dotnet_naming_style.begins_with_par.required_suffix =
dotnet_naming_style.begins_with_par.word_separator =
dotnet_naming_style.begins_with_par.capitalization = pascal_case

View File

@@ -4,12 +4,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatch", "DoomDeath
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{5EE134DE-2275-40C0-8B9D-4EFF22474F63}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterConsole", "PresenterConsole\PresenterConsole.csproj", "{B9A652EE-4267-4D6B-B1A6-2447F870A06D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterWPF", "PresenterWPF\PresenterWPF.csproj", "{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{CC28C26C-0998-4C13-8855-978658B8B0D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterWpf", "PresenterWpf\PresenterWpf.csproj", "{4CC7FEAF-D738-472F-9515-D22C7E88007C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterConsole", "PresenterConsole\PresenterConsole.csproj", "{85AA55C5-F8AF-4C38-8874-702684BCAFBB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,17 +24,17 @@ Global
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Release|Any CPU.Build.0 = Release|Any CPU
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Release|Any CPU.Build.0 = Release|Any CPU
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Release|Any CPU.Build.0 = Release|Any CPU
{CC28C26C-0998-4C13-8855-978658B8B0D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC28C26C-0998-4C13-8855-978658B8B0D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC28C26C-0998-4C13-8855-978658B8B0D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC28C26C-0998-4C13-8855-978658B8B0D6}.Release|Any CPU.Build.0 = Release|Any CPU
{4CC7FEAF-D738-472F-9515-D22C7E88007C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CC7FEAF-D738-472F-9515-D22C7E88007C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CC7FEAF-D738-472F-9515-D22C7E88007C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CC7FEAF-D738-472F-9515-D22C7E88007C}.Release|Any CPU.Build.0 = Release|Any CPU
{85AA55C5-F8AF-4C38-8874-702684BCAFBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85AA55C5-F8AF-4C38-8874-702684BCAFBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85AA55C5-F8AF-4C38-8874-702684BCAFBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85AA55C5-F8AF-4C38-8874-702684BCAFBB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -3,16 +3,16 @@ using Engine.Renderer.Texture;
namespace Engine.Asset;
public class Image<T>(T[,] pixels)
public class Image<T>(T[,] parPixels)
where T : struct, IPixel
{
public int Width { get; } = pixels.GetLength(0);
public int Height { get; } = pixels.GetLength(1);
public T[,] Pixels { get; } = pixels;
public int Width { get; } = parPixels.GetLength(0);
public int Height { get; } = parPixels.GetLength(1);
public T[,] Pixels { get; } = parPixels;
public T this[int x, int y] => Pixels[x, y];
public T this[int parX, int parY] => Pixels[parX, parY];
public Image(int width, int height) : this(new T[width, height])
public Image(int parWidth, int parHeight) : this(new T[parWidth, parHeight])
{
}

View File

@@ -2,18 +2,20 @@
public interface IMeshLoader
{
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default);
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default);
public static Mesh Optimize(Mesh mesh)
public static Mesh Optimize(Mesh parMesh)
{
var optimizedMesh = new Mesh();
var vertexMap = new Dictionary<Mesh.Vertex, uint>();
uint index = 0;
foreach (var vertex in mesh.Vertices)
foreach (var vertex in parMesh.Vertices)
{
if (vertexMap.TryGetValue(vertex, out var existingIndex))
{
optimizedMesh.IndicesInternal.Add(existingIndex);
}
else
{
vertexMap.Add(vertex, index);

View File

@@ -3,10 +3,10 @@
[Flags]
public enum MeshLoaderParameters
{
None = 0,
LoadNormals = 1 << 0,
LoadUVs = 1 << 1,
Optimize = 1 << 2,
_none = 0,
_loadNormals = 1 << 0,
_loadUVs = 1 << 1,
_optimize = 1 << 2,
Default = LoadNormals | LoadUVs | Optimize
_default = _loadNormals | _loadUVs | _optimize
}

View File

@@ -5,12 +5,14 @@ namespace Engine.Asset.Mesh.Loader;
public class ObjMeshLoader : IMeshLoader
{
private static readonly ObjMeshLoader Instance = new();
private static readonly ObjMeshLoader _instance = new();
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
=> Instance.LoadMesh(path, parameters);
public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
{
return _instance.LoadMesh(parPath, parAmeters);
}
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
{
var mesh = new Mesh();
@@ -19,16 +21,18 @@ public class ObjMeshLoader : IMeshLoader
var tempUVs = new List<Vector2>();
var index = 0u;
var loadNormals = parameters.HasFlag(MeshLoaderParameters.LoadNormals);
var loadUVs = parameters.HasFlag(MeshLoaderParameters.LoadUVs);
var loadNormals = parAmeters.HasFlag(MeshLoaderParameters._loadNormals);
var loadUVs = parAmeters.HasFlag(MeshLoaderParameters._loadUVs);
using var reader = new StreamReader(path);
using var reader = new StreamReader(parPath);
while (reader.ReadLine() is { } line)
{
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0 || parts[0].StartsWith('#'))
{
continue;
}
switch (parts[0])
{
@@ -58,20 +62,17 @@ public class ObjMeshLoader : IMeshLoader
case "f":
for (var i = 1; i <= 3; i++)
{
var faceComponents = parts[i].Split('/');
var meshVertex = new Mesh.Vertex
{
Position = tempVertices[int.Parse(faceComponents[0]) - 1]
};
string[]? faceComponents = parts[i].Split('/');
var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] };
if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "")
{
meshVertex.Uv = tempUVs[int.Parse(faceComponents[1]) - 1];
meshVertex._uv = tempUVs[int.Parse(faceComponents[1]) - 1];
}
if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "")
{
meshVertex.Normal = tempNormals[int.Parse(faceComponents[2]) - 1];
meshVertex._normal = tempNormals[int.Parse(faceComponents[2]) - 1];
}
mesh.VerticesInternal.Add(meshVertex);
@@ -82,8 +83,10 @@ public class ObjMeshLoader : IMeshLoader
}
}
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
if (parAmeters.HasFlag(MeshLoaderParameters._optimize))
{
mesh = IMeshLoader.Optimize(mesh);
}
return mesh;
}

View File

@@ -5,34 +5,41 @@ namespace Engine.Asset.Mesh.Loader;
public class StlMeshLoader : IMeshLoader
{
private static readonly StlMeshLoader Instance = new();
private static readonly StlMeshLoader _instance = new();
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
=> Instance.LoadMesh(path, parameters);
public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
{
return _instance.LoadMesh(parPath, parAmeters);
}
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
{
var mesh = new Mesh();
var currentNormal = new Vector3();
var index = 0u;
using var reader = new StreamReader(path);
using var reader = new StreamReader(parPath);
while (reader.ReadLine() is { } line)
{
line = line.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("solid") || line.StartsWith("outer loop") ||
line.StartsWith("endloop"))
{
continue;
if (line.StartsWith("endsolid"))
break;
}
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (line.StartsWith("endsolid"))
{
break;
}
string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
switch (parts[0])
{
case "facet" when parts[1] == "normal" && parameters.HasFlag(MeshLoaderParameters.LoadNormals):
case "facet" when parts[1] == "normal" && parAmeters.HasFlag(MeshLoaderParameters._loadNormals):
currentNormal = new Vector3(
float.Parse(parts[2], CultureInfo.InvariantCulture),
float.Parse(parts[3], CultureInfo.InvariantCulture),
@@ -47,19 +54,17 @@ public class StlMeshLoader : IMeshLoader
float.Parse(parts[3], CultureInfo.InvariantCulture)
);
mesh.VerticesInternal.Add(new Mesh.Vertex
{
Position = vertex,
Normal = currentNormal
});
mesh.VerticesInternal.Add(new Mesh.Vertex { _position = vertex, _normal = currentNormal });
mesh.IndicesInternal.Add(index++);
break;
}
}
}
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
if (parAmeters.HasFlag(MeshLoaderParameters._optimize))
{
mesh = IMeshLoader.Optimize(mesh);
}
return mesh;
}

View File

@@ -17,13 +17,10 @@ public class Mesh
public record struct Vertex : IVertex
{
[Vertex(VertexAttribType.Float, 3)]
public Vector3 Position;
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
[Vertex(VertexAttribType.Float, 3)]
public Vector3 Normal;
[Vertex(VertexAttribType.Float, 3)] public Vector3 _normal;
[Vertex(VertexAttribType.Float, 2)]
public Vector2 Uv;
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
}
}

View File

@@ -1,4 +1,7 @@
using OpenTK.Graphics.OpenGL;
using System.Diagnostics;
using Engine.Input;
using Engine.Scene;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
@@ -10,24 +13,29 @@ namespace Engine;
public sealed class Engine
{
public Renderer.Renderer Renderer => _renderer;
public SceneManager SceneManager => _sceneManager;
private readonly Window _window;
private readonly Renderer.Renderer _renderer;
private readonly SceneManager _sceneManager = new();
private readonly ILogger _logger;
private readonly IInputHandler _inputHandler;
public Engine(int width, int height, bool headless = false, string title = "")
private Thread? _updateThread;
public Engine(int parWidth, int parHeight, bool parHeadless = false, string parTitle = "")
{
var settings = new NativeWindowSettings
{
ClientSize = headless ? new Vector2i(1, 1) : new Vector2i(width, height),
Title = title,
StartVisible = !headless,
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
Title = parTitle,
StartVisible = !parHeadless,
APIVersion = new Version(4, 5),
Profile = ContextProfile.Compatability
};
_window = new Window(this, new NativeWindow(settings), headless);
_renderer = new Renderer.Renderer(width, height);
_window = new Window(this, new NativeWindow(settings), parHeadless);
_renderer = new Renderer.Renderer(parWidth, parHeight);
Thread.CurrentThread.Name = "RendererThread";
@@ -47,6 +55,9 @@ public sealed class Engine
public void Run()
{
_updateThread = new Thread(RunUpdate);
_updateThread.Start();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Viewport(0, 0, _window.Width, _window.Height);
@@ -55,5 +66,18 @@ public sealed class Engine
{
_window.Update();
}
_updateThread.Join();
}
private void RunUpdate()
{
var timer = Stopwatch.StartNew();
while (!_window.IsExiting)
{
_window.Update();
_sceneManager.Update(timer.Elapsed.TotalSeconds);
timer.Restart();
}
}
}

View File

@@ -1,11 +1,13 @@
namespace Engine.Input;
using Engine.Scene;
public interface IInputHandler
namespace Engine.Input;
public interface IInputHandler : IUpdate
{
bool IsKeyPressed(Key key);
bool IsKeyJustPressed(Key key);
bool IsKeyRepeat(Key key);
bool IsKeyPressed(KeyCode parKeyCode);
bool IsKeyJustPressed(KeyCode parKeyCode);
bool IsKeyRepeat(KeyCode parKeyCode);
bool IsMouseButtonPressed(MouseButton button);
bool IsMouseButtonJustPressed(MouseButton button);
bool IsMouseButtonPressed(MouseButton parButton);
bool IsMouseButtonJustPressed(MouseButton parButton);
}

View File

@@ -1,52 +0,0 @@
namespace Engine.Input;
public enum Key
{
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Ctrl,
Alt,
Shift,
Up,
Down,
Left,
Right,
Escape,
Enter,
Space,
Tab,
Backspace,
Delete,
Insert,
Home,
End,
PageUp,
PageDown,
}

View File

@@ -0,0 +1,52 @@
namespace Engine.Input;
public enum KeyCode
{
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Ctrl,
Alt,
Shift,
Up,
Down,
Left,
Right,
Escape,
Enter,
Space,
Tab,
Backspace,
Delete,
Insert,
Home,
End,
PageUp,
PageDown
}

View File

@@ -2,12 +2,12 @@
public enum MouseButton
{
Left,
Right,
Middle,
_left,
_right,
_middle,
Button4,
Button5,
Button6,
Button7,
_button4,
_button5,
_button6,
_button7
}

View File

@@ -7,40 +7,44 @@ public class IndexBuffer : OpenGlObject
{
internal int Count { get; }
public IndexBuffer(int count, BufferStorageFlags flags)
public IndexBuffer(int parCount, BufferStorageFlags parFlags)
{
Count = count;
Count = parCount;
GL.CreateBuffers(1, out int handle);
Handle = handle;
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, flags);
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, parFlags);
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
}
public void UploadData(uint[] data)
public void UploadData(uint[] parData)
{
UploadData(0, data);
UploadData(0, parData);
}
public void UploadData(int offset, uint[] data)
public void UploadData(int parOffset, uint[] parData)
{
if (parOffset < 0)
{
if (offset < 0)
throw new ArgumentException("Offset must be greater than 0");
if (data.Length + offset > Count)
throw new ArgumentException("Data array is too large");
GL.NamedBufferSubData(Handle, offset, data.Length * sizeof(uint), data);
}
internal override void Bind()
if (parData.Length + parOffset > Count)
{
throw new ArgumentException("Data array is too large");
}
GL.NamedBufferSubData(Handle, parOffset, parData.Length * sizeof(uint), parData);
}
public override void Bind()
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
}

View File

@@ -6,38 +6,53 @@ namespace Engine.Renderer.Buffer.Vertex;
public interface IVertex
{
public static IOrderedEnumerable<FieldInfo> GetFields<T>() => GetFields(typeof(T));
public static IOrderedEnumerable<FieldInfo> GetFields(Type type) =>
type.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(f => f.MetadataToken);
public static bool IsValid(Type type)
public static IOrderedEnumerable<FieldInfo> GetFields<T>()
{
if (!type.IsValueType || !type.IsAssignableTo(typeof(IVertex)))
return false;
return GetFields(typeof(T));
}
var fields = GetFields(type);
public static IOrderedEnumerable<FieldInfo> GetFields(Type parType)
{
return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken);
}
public static bool IsValid(Type parType)
{
if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex)))
{
return false;
}
IOrderedEnumerable<FieldInfo>? fields = GetFields(parType);
var totalSize = 0;
foreach (var field in fields)
{
if (!field.FieldType.IsValueType)
{
return false;
}
var attribute = field.GetCustomAttribute<VertexAttribute>();
if (attribute == null)
{
return false;
}
var size = AttributeSize(attribute.Type) * attribute.ComponentCount * attribute.RepeatCount;
if (size != Marshal.SizeOf(field.FieldType))
{
return false;
}
totalSize += size;
}
return totalSize == Marshal.SizeOf(type);
return totalSize == Marshal.SizeOf(parType);
}
public static int AttributeSize(VertexAttribType type) => type switch
public static int AttributeSize(VertexAttribType parType)
{
return parType switch
{
VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte),
VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short),
@@ -48,3 +63,4 @@ public interface IVertex
_ => 0
};
}
}

View File

@@ -10,17 +10,22 @@ public class VertexAttribute : Attribute
public bool Normalized { get; }
public int RepeatCount { get; }
public VertexAttribute(VertexAttribType type, int componentCount = 1, bool normalized = false, int repeatCount = 1)
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
int parRepeatCount = 1)
{
if (parComponentCount <= 0)
{
if (componentCount <= 0)
throw new ArgumentException("Count must be greater than 0");
}
if (repeatCount <= 0)
if (parRepeatCount <= 0)
{
throw new ArgumentException("Repeat must be greater than 0");
}
Type = type;
ComponentCount = componentCount;
Normalized = normalized;
RepeatCount = repeatCount;
Type = parType;
ComponentCount = parComponentCount;
Normalized = parNormalized;
RepeatCount = parRepeatCount;
}
}

View File

@@ -17,26 +17,30 @@ public class VertexArray : OpenGlObject
Handle = handle;
}
public void BindIndexBuffer(IndexBuffer buffer)
public void BindIndexBuffer(IndexBuffer parBuffer)
{
GL.VertexArrayElementBuffer(Handle, buffer.Handle);
GL.VertexArrayElementBuffer(Handle, parBuffer.Handle);
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, buffer.Handle);
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, parBuffer.Handle);
}
public void BindVertexBuffer<T>(VertexBuffer<T> buffer, int bindingIndex = 0, int divisor = 0)
public void BindVertexBuffer<T>(VertexBuffer<T> parBuffer, int parBindingIndex = 0, int parDivisor = 0)
where T : struct, IVertex
{
if (bindingIndex < 0)
if (parBindingIndex < 0)
{
throw new ArgumentException("Binding index must be greater than 0");
}
if (divisor < 0)
if (parDivisor < 0)
{
throw new ArgumentException("Divisor must be greater than 0");
}
var stride = Marshal.SizeOf<T>();
var fields = IVertex.GetFields<T>();
IOrderedEnumerable<FieldInfo>? fields = IVertex.GetFields<T>();
GL.VertexArrayVertexBuffer(Handle, bindingIndex, buffer.Handle, 0, stride);
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
var location = 0;
foreach (var field in fields)
@@ -44,40 +48,41 @@ public class VertexArray : OpenGlObject
var attribute = field.GetCustomAttribute<VertexAttribute>()!;
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
SetupAttribute(attribute, location, offset, bindingIndex);
SetupAttribute(attribute, location, offset, parBindingIndex);
location += attribute.RepeatCount;
}
GL.VertexArrayBindingDivisor(Handle, bindingIndex, divisor);
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);
Log.Debug(
"Vertex array {Handle} bound to vertex buffer {Buffer} at {BindingIndex} binding with {Divisor} divisor",
Handle, buffer.Handle, bindingIndex, divisor);
Handle, parBuffer.Handle, parBindingIndex, parDivisor);
}
private void SetupAttribute(VertexAttribute attribute, int baseLocation, int baseOffset, int bindingIndex)
private void SetupAttribute(VertexAttribute parAttribute, int parBaseLocation, int parBaseOffset, int parBindingIndex)
{
var size = attribute.ComponentCount * IVertex.AttributeSize(attribute.Type);
var size = parAttribute.ComponentCount * IVertex.AttributeSize(parAttribute.Type);
for (var i = 0; i < attribute.RepeatCount; i++)
for (var i = 0; i < parAttribute.RepeatCount; i++)
{
var location = baseLocation + i;
var offset = baseOffset + i * size;
var location = parBaseLocation + i;
var offset = parBaseOffset + (i * size);
GL.EnableVertexArrayAttrib(Handle, location);
GL.VertexArrayAttribFormat(Handle, location, attribute.ComponentCount, attribute.Type, attribute.Normalized,
GL.VertexArrayAttribFormat(Handle, location, parAttribute.ComponentCount, parAttribute.Type,
parAttribute.Normalized,
offset);
GL.VertexArrayAttribBinding(Handle, location, bindingIndex);
GL.VertexArrayAttribBinding(Handle, location, parBindingIndex);
}
}
internal override void Bind()
public override void Bind()
{
GL.BindVertexArray(Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindVertexArray(0);
}

View File

@@ -12,46 +12,54 @@ public class VertexBuffer<T> : OpenGlObject
private readonly int _stride = Marshal.SizeOf<T>();
public VertexBuffer(int count, BufferStorageFlags flags)
public VertexBuffer(int parCount, BufferStorageFlags parFlags)
{
if (!IVertex.IsValid(typeof(T)))
{
throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
}
if (count <= 0)
if (parCount <= 0)
{
throw new ArgumentException("Count must be greater than 0");
}
Count = count;
Count = parCount;
GL.CreateBuffers(1, out int handle);
Handle = handle;
GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, flags);
GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, parFlags);
Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
}
public void UploadData(T[] data)
public void UploadData(T[] parData)
{
UploadData(0, data);
UploadData(0, parData);
}
public void UploadData(int offset, T[] data)
public void UploadData(int parOffset, T[] parData)
{
if (parOffset < 0)
{
if (offset < 0)
throw new ArgumentException("Offset must be greater than 0");
if (data.Length + offset > Count)
throw new ArgumentException("Data array is too large");
GL.NamedBufferSubData(Handle, offset * _stride, data.Length * _stride, data);
}
internal override void Bind()
if (parData.Length + parOffset > Count)
{
throw new ArgumentException("Data array is too large");
}
GL.NamedBufferSubData(Handle, parOffset * _stride, parData.Length * _stride, parData);
}
public override void Bind()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
}

View File

@@ -13,14 +13,15 @@ internal static class Debug
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
}
private static void DebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length,
IntPtr message, IntPtr userParam)
private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity,
int parLength,
IntPtr parMessage, IntPtr parUserParam)
{
var logger = Log.ForContext<Engine>();
var messageText = Marshal.PtrToStringAnsi(message, length) ?? "Unknown OpenGL Error";
var messageText = Marshal.PtrToStringAnsi(parMessage, parLength) ?? "Unknown OpenGL Error";
var typePrefix = type switch
var typePrefix = parType switch
{
DebugType.DebugTypeError => "Error",
DebugType.DebugTypeDeprecatedBehavior => "Deprecated",
@@ -34,7 +35,7 @@ internal static class Debug
_ => "Unknown"
};
var sourcePrefix = source switch
var sourcePrefix = parSource switch
{
DebugSource.DebugSourceApi => "API",
DebugSource.DebugSourceWindowSystem => "Window",
@@ -45,18 +46,18 @@ internal static class Debug
_ => "Unknown"
};
logger.Write(GetLogLevel(severity),
logger.Write(GetLogLevel(parSeverity),
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
typePrefix,
sourcePrefix,
messageText,
id
parId
);
}
private static LogEventLevel GetLogLevel(DebugSeverity severity)
private static LogEventLevel GetLogLevel(DebugSeverity parSeverity)
{
return severity switch
return parSeverity switch
{
DebugSeverity.DebugSeverityNotification => LogEventLevel.Information,
DebugSeverity.DebugSeverityHigh => LogEventLevel.Error,

View File

@@ -1,4 +1,5 @@
using Engine.Renderer.Pixel;
using System.Collections.ObjectModel;
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using OpenTK.Graphics.OpenGL;
@@ -12,7 +13,9 @@ public class Framebuffer : OpenGlObject
protected set
{
if (value <= 0)
{
throw new ArgumentException("Width must be greater than 0");
}
_width = value;
}
@@ -24,70 +27,93 @@ public class Framebuffer : OpenGlObject
protected set
{
if (value <= 0)
{
throw new ArgumentException("Height must be greater than 0");
}
_height = value;
}
}
public IConstTexture Texture => _texture;
internal Texture.Texture TextureInternal => _texture;
internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0);
private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments;
private int _width;
private int _height;
private readonly DynamicTexture _texture;
private readonly Renderbuffer? _renderbuffer;
public Framebuffer(int width, int height, PixelFormat format, PixelType type, PixelInternalFormat internalFormat,
bool createRenderbuffer = true)
public Framebuffer(int parWidth, int parHeight,
IDictionary<FramebufferAttachment, IFramebufferAttachment> parAttachments)
{
Width = width;
Height = height;
Width = parWidth;
Height = parHeight;
_attachments = new ReadOnlyDictionary<FramebufferAttachment, IFramebufferAttachment>(parAttachments);
GL.CreateFramebuffers(1, out int handle);
Handle = handle;
_texture = new DynamicTexture(width, height, format, type, internalFormat);
GL.NamedFramebufferTexture(Handle, FramebufferAttachment.ColorAttachment0, _texture.Handle, 0);
if (createRenderbuffer)
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
{
_renderbuffer = new Renderbuffer(width, height, RenderbufferStorage.Depth24Stencil8);
GL.NamedFramebufferRenderbuffer(Handle, FramebufferAttachment.DepthStencilAttachment,
RenderbufferTarget.Renderbuffer, _renderbuffer.Handle);
switch (attachment.Value.Type)
{
case IFramebufferAttachment.AttachmentType._texture:
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
break;
case IFramebufferAttachment.AttachmentType._renderbuffer:
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
attachment.Value.Handle);
break;
}
}
var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer);
if (status != FramebufferStatus.FramebufferComplete)
{
throw new Exception($"Framebuffer is not complete: {status}");
}
public static Framebuffer Create<T>(int width, int height, bool createRenderbuffer = true)
where T : struct, IPixel
{
var pixel = default(T);
return new Framebuffer(width, height, pixel.Format, pixel.Type, pixel.InternalFormat, createRenderbuffer);
}
public void Resize(int width, int height)
public static FramebufferBuilder Builder(int parWidth, int parHeight)
{
return new FramebufferBuilder(parWidth, parHeight);
}
public T? GetAttachment<T>(FramebufferAttachment parAttachment) where T : IFramebufferAttachment
{
if (!_attachments.TryGetValue(parAttachment, out var attachmentValue))
{
return default;
}
if (attachmentValue is T t)
{
return t;
}
return default;
}
public void Resize(int parWidth, int parHeight)
{
if (Width == parWidth && Height == parHeight)
{
if (Width == width && Height == height)
return;
Width = width;
Height = height;
_texture.Resize(width, height);
_renderbuffer?.Resize(width, height);
}
internal override void Bind()
Width = parWidth;
Height = parHeight;
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
{
attachment.Value.Resize(parWidth, parHeight);
}
}
public override void Bind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
}

View File

@@ -0,0 +1,95 @@
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Framebuffer;
public class FramebufferBuilder(int parWidth, int parHeight)
{
private readonly List<AttachmentSpecification> _attachments = [];
private readonly HashSet<FramebufferAttachment> _usedAttachments = [];
private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0;
public FramebufferBuilder AddColorAttachment<T>()
where T : struct, IPixel
{
if (_currentColorAttachment > FramebufferAttachment.ColorAttachment15)
{
throw new ArgumentException("Framebuffer already has 16 color attachments");
}
var pixel = default(T);
return AddAttachment(new TextureAttachmentSpecification
{
FramebufferAttachment = _currentColorAttachment++,
PixelFormat = pixel.Format,
PixelType = pixel.Type,
PixelInternalFormat = pixel.InternalFormat
});
}
public FramebufferBuilder AddDepthAttachment()
{
return AddAttachment(new RenderbufferAttachmentSpecification
{
FramebufferAttachment = FramebufferAttachment.DepthAttachment,
RenderbufferStorage = RenderbufferStorage.DepthComponent
});
}
private FramebufferBuilder AddAttachment(AttachmentSpecification parAttachment)
{
if (_usedAttachments.Contains(parAttachment.FramebufferAttachment))
{
throw new ArgumentException("Framebuffer already has attachment of type " +
parAttachment.FramebufferAttachment);
}
_attachments.Add(parAttachment);
_usedAttachments.Add(parAttachment.FramebufferAttachment);
return this;
}
public Framebuffer Build()
{
Dictionary<FramebufferAttachment, IFramebufferAttachment>? attachments = _attachments
.ToDictionary(
parAttachment => parAttachment.FramebufferAttachment,
parAttachment => parAttachment.Create(parWidth, parHeight)
);
return new Framebuffer(parWidth, parHeight, attachments);
}
private class TextureAttachmentSpecification : AttachmentSpecification
{
public PixelFormat PixelFormat { get; init; }
public PixelType PixelType { get; init; }
public PixelInternalFormat PixelInternalFormat { get; init; }
public override DynamicTexture Create(int parWidth, int parHeight)
{
return new DynamicTexture(parWidth, parHeight, PixelFormat, PixelType, PixelInternalFormat);
}
}
private class RenderbufferAttachmentSpecification : AttachmentSpecification
{
public RenderbufferStorage RenderbufferStorage { get; init; }
public override Renderbuffer Create(int parWidth, int parHeight)
{
return new Renderbuffer(parWidth, parHeight, RenderbufferStorage);
}
}
private abstract class AttachmentSpecification
{
public FramebufferAttachment FramebufferAttachment { get; init; }
public abstract IFramebufferAttachment Create(int parWidth, int parHeight);
}
}

View File

@@ -0,0 +1,15 @@
namespace Engine.Renderer.Framebuffer;
public interface IFramebufferAttachment
{
public AttachmentType Type { get; }
internal int Handle { get; }
public void Resize(int parWidth, int parHeight);
public enum AttachmentType
{
_texture,
_renderbuffer
}
}

View File

@@ -2,18 +2,20 @@
namespace Engine.Renderer.Framebuffer;
public class Renderbuffer : OpenGlObject
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
{
public int Width { get; private set; }
public int Height { get; private set; }
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._renderbuffer;
private readonly RenderbufferStorage _format;
public Renderbuffer(int width, int height, RenderbufferStorage format)
public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat)
{
Width = width;
Height = height;
_format = format;
Width = parWidth;
Height = parHeight;
_format = parFormat;
GL.CreateRenderbuffers(1, out int handle);
Handle = handle;
@@ -21,23 +23,25 @@ public class Renderbuffer : OpenGlObject
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
}
public void Resize(int width, int height)
public void Resize(int parWidth, int parHeight)
{
if (Width == parWidth && Height == parHeight)
{
if (Width == width && Height == height)
return;
}
Width = width;
Height = height;
Width = parWidth;
Height = parHeight;
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
}
internal override void Bind()
public override void Bind()
{
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
}

View File

@@ -1,8 +1,16 @@
using Engine.Renderer.Texture;
using Engine.Scene;
using OpenTK.Windowing.Common;
namespace Engine.Renderer;
public interface IPresenter
public interface IPresenter : IUpdate
{
public void Present(IConstTexture texture);
public bool IsExiting { get; }
public int Width { get; }
public int Height { get; }
public event Action<ResizeEventArgs> Resize;
public void Present(IConstTexture parTexture);
}

View File

@@ -6,8 +6,8 @@ public abstract class OpenGlObject
{
public int Handle { get; protected set; } = -1;
internal abstract void Bind();
internal abstract void Unbind();
public abstract void Bind();
public abstract void Unbind();
protected abstract void Destroy();

View File

@@ -1,39 +0,0 @@
using Engine.Renderer.Framebuffer;
using Engine.Renderer.Pixel;
using Engine.Renderer.Shader;
namespace Engine.Renderer.Postprocess;
public class Pipeline
{
private readonly List<RenderPass> _renderPasses = [];
public void AddRenderPass(ShaderProgram shaderProgram, Framebuffer.Framebuffer frameBuffer)
{
_renderPasses.Add(new RenderPass(shaderProgram, frameBuffer));
}
public void Render()
{
foreach (var renderPass in _renderPasses)
{
var texture = renderPass.FrameBuffer.TextureInternal;
texture.BindUnit();
renderPass.ShaderProgram.Bind();
renderPass.FrameBuffer.Bind();
// TODO: Render
}
}
private class RenderPass
{
internal ShaderProgram ShaderProgram { get; }
internal Framebuffer.Framebuffer FrameBuffer { get; }
internal RenderPass(ShaderProgram shaderProgram, Framebuffer.Framebuffer frameBuffer)
{
ShaderProgram = shaderProgram;
FrameBuffer = frameBuffer;
}
}
}

View File

@@ -9,16 +9,19 @@ namespace Engine.Renderer;
public class Renderer
{
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal;
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
private readonly Framebuffer.Framebuffer _framebuffer;
private readonly Queue<Action<Renderer>> _renderActions = new();
public Renderer(int width, int height)
public Renderer(int parWidth, int parHeight)
{
InitializeOpenGl();
_framebuffer = Framebuffer.Framebuffer.Create<Rgb8>(width, height);
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
.AddColorAttachment<Rgb8>()
.AddDepthAttachment()
.Build();
}
private void InitializeOpenGl()
@@ -35,21 +38,24 @@ public class Renderer
GL.Enable(EnableCap.Blend);
}
internal void Commit(Action<Renderer> renderAction)
internal void Commit(Action<Renderer> parRenderAction)
{
_renderActions.Enqueue(renderAction);
_renderActions.Enqueue(parRenderAction);
}
internal void Render()
{
_framebuffer.Bind();
while (_renderActions.TryDequeue(out var renderAction))
while (_renderActions.TryDequeue(out Action<Renderer>? renderAction))
{
renderAction(this);
}
_framebuffer.Unbind();
}
internal void Resize(int width, int height)
internal void Resize(int parWidth, int parHeight)
{
_framebuffer.Resize(width, height);
_framebuffer.Resize(parWidth, parHeight);
}
}

View File

@@ -10,14 +10,14 @@ public class ShaderProgram : OpenGlObject
{
private readonly Dictionary<string, int> _uniforms = new();
public static ShaderProgram CreateFromSource(string source)
public static ShaderProgram CreateFromSource(string parSource)
{
var vertexSource = new StringBuilder();
var fragmentSource = new StringBuilder();
var inFragment = false;
var inVertex = false;
foreach (var line in source.Split('\n'))
foreach (var line in parSource.Split('\n'))
{
if (line.StartsWith("#shader vertex"))
{
@@ -42,21 +42,21 @@ public class ShaderProgram : OpenGlObject
return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
}
public ShaderProgram(string vertexSource, string fragmentSource)
public ShaderProgram(string parVertexSource, string parFragmentSource)
{
var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader);
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
Handle = LinkProgram(vertexShader, fragmentShader);
}
public void SetUniform<T>(string name, T value, [CallerMemberName] string caller = "")
public void SetUniform<T>(string parName, T parValue)
{
try
{
var location = GetUniformLocation(name);
var location = GetUniformLocation(parName);
switch (value)
switch (parValue)
{
case bool boolValue:
GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0);
@@ -85,17 +85,17 @@ public class ShaderProgram : OpenGlObject
}
catch (Exception ex)
{
Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller);
Log.Error(ex, "Failed to set uniform {UniformName}", parName);
throw;
}
}
internal override void Bind()
public override void Bind()
{
GL.UseProgram(Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.UseProgram(0);
}
@@ -105,23 +105,27 @@ public class ShaderProgram : OpenGlObject
GL.DeleteProgram(Handle);
}
private int GetUniformLocation(string name)
private int GetUniformLocation(string parName)
{
if (_uniforms.TryGetValue(parName, out var location))
{
if (_uniforms.TryGetValue(name, out var location))
return location;
location = GL.GetUniformLocation(Handle, name);
if (location < 0)
throw new ArgumentException($"Uniform '{name}' not found in shader program");
_uniforms.Add(name, location);
return location;
}
private static int CompileSource(string source, ShaderType type)
location = GL.GetUniformLocation(Handle, parName);
if (location < 0)
{
var shaderId = GL.CreateShader(type);
GL.ShaderSource(shaderId, source);
throw new ArgumentException($"Uniform '{parName}' not found in shader program");
}
_uniforms.Add(parName, location);
return location;
}
private static int CompileSource(string parSource, ShaderType parType)
{
var shaderId = GL.CreateShader(parType);
GL.ShaderSource(shaderId, parSource);
GL.CompileShader(shaderId);
GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status);
@@ -130,19 +134,19 @@ public class ShaderProgram : OpenGlObject
var log = GL.GetShaderInfoLog(shaderId);
GL.DeleteShader(shaderId);
throw new ShaderCompilationException(type, log);
throw new ShaderCompilationException(parType, log);
}
return shaderId;
}
private static int LinkProgram(int vertexShader, int fragmentShader)
private static int LinkProgram(int parVertexShader, int parFragmentShader)
{
var programId = GL.CreateProgram();
try
{
GL.AttachShader(programId, vertexShader);
GL.AttachShader(programId, fragmentShader);
GL.AttachShader(programId, parVertexShader);
GL.AttachShader(programId, parFragmentShader);
GL.LinkProgram(programId);
GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus);
@@ -162,20 +166,21 @@ public class ShaderProgram : OpenGlObject
}
finally
{
GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);
GL.DeleteShader(parVertexShader);
GL.DeleteShader(parFragmentShader);
}
return programId;
}
}
public class ShaderCompilationException(ShaderType type, string message)
: Exception($"Failed to compile {type} shader: {message}")
public class ShaderCompilationException(ShaderType parType, string parMessage)
: Exception($"Failed to compile {parType} shader: {parMessage}")
{
public ShaderType ShaderType { get; } = type;
public ShaderType ShaderType { get; } = parType;
}
public class ShaderLinkException(string message) : Exception($"Failed to link shader program: {message}");
public class ShaderLinkException(string parMessage) : Exception($"Failed to link shader program: {parMessage}");
public class ShaderValidationException(string message) : Exception($"Failed to validate shader program: {message}");
public class ShaderValidationException(string parMessage)
: Exception($"Failed to validate shader program: {parMessage}");

View File

@@ -1,40 +1,46 @@
using Engine.Renderer.Pixel;
using Engine.Renderer.Framebuffer;
using Engine.Renderer.Pixel;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Texture;
public class DynamicTexture : Texture
public class DynamicTexture : Texture, IFramebufferAttachment
{
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._texture;
private readonly PixelFormat _format;
private readonly PixelType _type;
private readonly PixelInternalFormat _internalFormat;
internal DynamicTexture(int width, int height, PixelFormat format, PixelType type, PixelInternalFormat internalFormat)
: base(width, height)
internal DynamicTexture(int parWidth, int parHeight, PixelFormat parFormat, PixelType parType,
PixelInternalFormat parInternalFormat)
: base(parWidth, parHeight)
{
_format = format;
_type = type;
_internalFormat = internalFormat;
_format = parFormat;
_type = parType;
_internalFormat = parInternalFormat;
GL.BindTexture(TextureTarget.Texture2D, Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
IntPtr.Zero);
}
public static DynamicTexture Create<T>(int width, int height)
public static DynamicTexture Create<T>(int parWidth, int parHeight)
where T : struct, IPixel
{
var pixel = default(T);
return new DynamicTexture(width, height, pixel.Format, pixel.Type, pixel.InternalFormat);
return new DynamicTexture(parWidth, parHeight, pixel.Format, pixel.Type, pixel.InternalFormat);
}
public void Resize(int width, int height)
public void Resize(int parWidth, int parHeight)
{
if (Width == parWidth && Height == parHeight)
{
if (Width == width && Height == height)
return;
}
Width = width;
Height = height;
Width = parWidth;
Height = parHeight;
Bind();
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,

View File

@@ -8,31 +8,39 @@ public interface IConstTexture
public int Width { get; }
public int Height { get; }
public void ReadPixels<T>(int x, int y, int width, int height, T[,] pixels)
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel;
}
public static class ConstTextureExtensions
{
public static T[,] ReadPixels<T>(this IConstTexture texture) where T : struct, IPixel
=> texture.ReadPixels<T>(0, 0, texture.Width, texture.Height);
public static T[,] ReadPixels<T>(this IConstTexture parTexture) where T : struct, IPixel
{
return parTexture.ReadPixels<T>(0, 0, parTexture.Width, parTexture.Height);
}
public static T[,] ReadPixels<T>(this IConstTexture texture, int x, int y, int width, int height)
public static T[,] ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight)
where T : struct, IPixel
{
var pixels = new T[width, height];
texture.ReadPixels(x, y, width, height, pixels);
var pixels = new T[parWidth, parHeight];
parTexture.ReadPixels(parX, parY, parWidth, parHeight, pixels);
return pixels;
}
public static void ReadPixels<T>(this IConstTexture texture, Image<T> image) where T : struct, IPixel
=> texture.ReadPixels(0, 0, image);
public static void ReadPixels<T>(this IConstTexture texture, int x, int y, Image<T> image)
where T : struct, IPixel =>
texture.ReadPixels(x, y, image.Width, image.Height, image.Pixels);
public static void ReadPixels<T>(this IConstTexture texture, T[,] pixels) where T : struct, IPixel
=> texture.ReadPixels(0, 0, texture.Width, texture.Height, pixels);
public static void ReadPixels<T>(this IConstTexture parTexture, Image<T> parImage) where T : struct, IPixel
{
parTexture.ReadPixels(0, 0, parImage);
}
public static void ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, Image<T> parImage)
where T : struct, IPixel
{
parTexture.ReadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
}
public static void ReadPixels<T>(this IConstTexture parTexture, T[,] parPixels) where T : struct, IPixel
{
parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);
}
}

View File

@@ -5,19 +5,25 @@ namespace Engine.Renderer.Texture;
public interface ITexture : IConstTexture
{
public void UploadPixels<T>(int x, int y, int width, int height, T[,] pixels)
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel;
}
public static class TextureExtensions
{
public static void UploadPixels<T>(this ITexture texture, Image<T> image) where T : struct, IPixel
=> texture.UploadPixels(0, 0, image);
public static void UploadPixels<T>(this ITexture texture, int x, int y, Image<T> image)
where T : struct, IPixel =>
texture.UploadPixels(x, y, image.Width, image.Height, image.Pixels);
public static void UploadPixels<T>(this ITexture texture, T[,] pixels) where T : struct, IPixel
=> texture.UploadPixels(0, 0, texture.Width, texture.Height, pixels);
public static void UploadPixels<T>(this ITexture parTexture, Image<T> parImage) where T : struct, IPixel
{
parTexture.UploadPixels(0, 0, parImage);
}
public static void UploadPixels<T>(this ITexture parTexture, int parX, int parY, Image<T> parImage)
where T : struct, IPixel
{
parTexture.UploadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
}
public static void UploadPixels<T>(this ITexture parTexture, T[,] parPixels) where T : struct, IPixel
{
parTexture.UploadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);
}
}

View File

@@ -5,15 +5,15 @@ namespace Engine.Renderer.Texture;
public class StaticTexture : Texture
{
private StaticTexture(int width, int height, SizedInternalFormat format) : base(width, height)
private StaticTexture(int parWidth, int parHeight, SizedInternalFormat parFormat) : base(parWidth, parHeight)
{
GL.TextureStorage2D(Handle, 1, format, Width, Height);
GL.TextureStorage2D(Handle, 1, parFormat, Width, Height);
}
public static StaticTexture Create<T>(int width, int height)
public static StaticTexture Create<T>(int parWidth, int parHeight)
where T : struct, IPixel
{
var format = default(T).SizedInternalFormat;
return new StaticTexture(width, height, format);
return new StaticTexture(parWidth, parHeight, format);
}
}

View File

@@ -12,7 +12,9 @@ public abstract class Texture : OpenGlObject, ITexture
protected set
{
if (value <= 0)
{
throw new ArgumentException("Width must be greater than 0");
}
_width = value;
}
@@ -24,7 +26,9 @@ public abstract class Texture : OpenGlObject, ITexture
protected set
{
if (value <= 0)
{
throw new ArgumentException("Height must be greater than 0");
}
_height = value;
}
@@ -33,69 +37,86 @@ public abstract class Texture : OpenGlObject, ITexture
private int _width;
private int _height;
protected Texture(int width, int height)
protected Texture(int parWidth, int parHeight)
{
Width = width;
Height = height;
Width = parWidth;
Height = parHeight;
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
Handle = handle;
}
public void UploadPixels<T>(int x, int y, int width, int height, T[,] pixels)
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel
{
if (x < 0 || y < 0)
if (parX < 0 || parY < 0)
{
throw new ArgumentException("x and y must be greater than 0");
if (width <= 0 || height <= 0)
throw new ArgumentException("Width and height must be greater than 0");
if (x + width > Width || y + height > Height)
throw new ArgumentException("x + width and y + height must be less than width and height");
if (pixels.Length != width * height)
throw new ArgumentException("Pixels array must be of size width * height");
var format = pixels[0, 0].Format;
var type = pixels[0, 0].Type;
GL.TextureSubImage2D(Handle, 0, x, y, width, height, format, type, pixels);
}
public void ReadPixels<T>(int x, int y, int width, int height, T[,] pixels)
if (parWidth <= 0 || parHeight <= 0)
{
throw new ArgumentException("Width and height must be greater than 0");
}
if (parX + parWidth > Width || parY + parHeight > Height)
{
throw new ArgumentException("x + width and y + height must be less than width and height");
}
if (parPixels.Length != parWidth * parHeight)
{
throw new ArgumentException("Pixels array must be of size width * height");
}
var format = parPixels[0, 0].Format;
var type = parPixels[0, 0].Type;
GL.TextureSubImage2D(Handle, 0, parX, parY, parWidth, parHeight, format, type, parPixels);
}
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
where T : struct, IPixel
{
if (x < 0 || y < 0)
if (parX < 0 || parY < 0)
{
throw new ArgumentException("x and y must be greater than 0");
}
if (width <= 0 || height <= 0)
if (parWidth <= 0 || parHeight <= 0)
{
throw new ArgumentException("Width and height must be greater than 0");
}
if (x + width > Width || y + height > Height)
if (parX + parWidth > Width || parY + parHeight > Height)
{
throw new ArgumentException("x + width and y + height must be less than width and height");
}
if (pixels.Length != width * height)
if (parPixels.Length != parWidth * parHeight)
{
throw new ArgumentException("Pixels array must be of size width * height");
}
var format = default(T).Format;
var type = default(T).Type;
GL.GetTextureSubImage(Handle, 0, x, y, 0, width, height, 1, format, type, pixels.Length * Marshal.SizeOf<T>(),
pixels);
GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type,
parPixels.Length * Marshal.SizeOf<T>(),
parPixels);
}
internal void BindUnit(int unit = 0)
public void BindUnit(int parUnit = 0)
{
GL.BindTextureUnit(unit, Handle);
GL.BindTextureUnit(parUnit, Handle);
}
internal override void Bind()
public override void Bind()
{
GL.BindTexture(TextureTarget.Texture2D, Handle);
}
internal override void Unbind()
public override void Unbind()
{
GL.BindTexture(TextureTarget.Texture2D, 0);
}

View File

@@ -4,13 +4,13 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public abstract class Camera(
float nearPlane,
float farPlane
float parNearPlane,
float parFarPlane
) : Component, ICamera
{
public float AspectRatio { get; private set; } = 1;
public float NearPlane { get; set; } = nearPlane;
public float FarPlane { get; set; } = farPlane;
public float NearPlane { get; set; } = parNearPlane;
public float FarPlane { get; set; } = parFarPlane;
private Vector2i _screenSize = new(1, 1);
@@ -27,6 +27,6 @@ public abstract class Camera(
}
}
public abstract Vector3 ScreenToWorld(Vector2 screenPosition);
public abstract Vector2 WorldToScreen(Vector3 worldPosition);
public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition);
public abstract Vector2 WorldToScreen(Vector3 parWorldPosition);
}

View File

@@ -4,15 +4,15 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public class OrthographicCamera(
float nearPlane = 0.1f,
float farPlane = 1000f,
float size = 10f,
OrthographicCamera.Axis axis = OrthographicCamera.Axis.Y
float parNearPlane = 0.1f,
float parFarPlane = 1000f,
float parSize = 10f,
OrthographicCamera.Axis parAxis = OrthographicCamera.Axis.Y
)
: Camera(nearPlane, farPlane)
: Camera(parNearPlane, parFarPlane)
{
public Axis FixedAxis { get; set; } = axis;
public float Size { get; set; } = size;
public Axis FixedAxis { get; set; } = parAxis;
public float Size { get; set; } = parSize;
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted();
@@ -20,19 +20,19 @@ public class OrthographicCamera(
? Matrix4.CreateOrthographic(Size, Size / AspectRatio, NearPlane, FarPlane)
: Matrix4.CreateOrthographic(Size * AspectRatio, Size, NearPlane, FarPlane);
public override Vector3 ScreenToWorld(Vector2 screenPosition)
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{
var offset = FixedAxis == Axis.X
? new Vector2(Size, Size / AspectRatio)
: new Vector2(Size * AspectRatio, Size);
offset /= 2;
return new Vector4(screenPosition.X - offset.X, screenPosition.Y - offset.Y, 0, 1)
return new Vector4(parScreenPosition.X - offset.X, parScreenPosition.Y - offset.Y, 0, 1)
.MulProject(GameObject.Transform.TransformMatrix)
.Xyz;
}
public override Vector2 WorldToScreen(Vector3 worldPosition)
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
{
throw new NotImplementedException();
}
@@ -40,6 +40,6 @@ public class OrthographicCamera(
public enum Axis
{
X,
Y,
Y
}
}

View File

@@ -4,13 +4,13 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public class PerspectiveCamera(
float fieldOfView = 90.0f,
float nearPlane = 0.1f,
float farPlane = 1000f
float parFieldOfView = 90.0f,
float parNearPlane = 0.1f,
float parFarPlane = 1000f
)
: Camera(nearPlane, farPlane)
: Camera(parNearPlane, parFarPlane)
{
public float FieldOfView { get; set; } = fieldOfView;
public float FieldOfView { get; set; } = parFieldOfView;
public override Matrix4 View
{
@@ -28,12 +28,12 @@ public class PerspectiveCamera(
public override Matrix4 Projection =>
Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
public override Vector3 ScreenToWorld(Vector2 screenPosition)
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{
throw new NotImplementedException();
}
public override Vector2 WorldToScreen(Vector3 worldPosition)
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
{
throw new NotImplementedException();
}

View File

@@ -26,13 +26,8 @@ public class Transform : Component
public Transform Clone()
{
var clone = new Transform
{
Position = Position,
Rotation = Rotation,
Scale = Scale,
LocalScale = LocalScale
};
var clone =
new Transform { Position = Position, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale };
return clone;
}

View File

@@ -1,39 +1,41 @@
namespace Engine.Scene.Component;
public abstract class Component
public abstract class Component : IUpdate, IRender
{
public Guid Id { get; } = Guid.NewGuid();
public GameObject GameObject { get; internal set; }
internal virtual void Awake()
public virtual void Awake()
{
}
internal virtual void Start()
public virtual void Start()
{
}
internal virtual void Update()
public virtual void Update(double parDeltaTime)
{
}
internal virtual void Render()
public virtual void Render()
{
}
internal virtual void Destroy()
public virtual void Destroy()
{
}
}
public static class ComponentTypeExtensions
{
internal static Type GetComponentBaseType(this Type type)
internal static Type GetComponentBaseType(this Type parType)
{
var baseType = type.BaseType;
var baseType = parType.BaseType;
if (baseType == null || baseType == typeof(Component))
return type;
{
return parType;
}
while (baseType.BaseType != null && baseType.BaseType != typeof(Component))
{

View File

@@ -4,7 +4,7 @@ using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public sealed class GameObject
public sealed class GameObject : IUpdate, IRender
{
public Guid Id { get; } = Guid.NewGuid();
public Transform Transform { get; }
@@ -24,9 +24,9 @@ public sealed class GameObject
Transform = GetComponent<Transform>()!;
}
public GameObject(Transform transform)
public GameObject(Transform parTransform)
{
AddComponent(transform.Clone());
AddComponent(parTransform.Clone());
ProcessChanges();
Transform = GetComponent<Transform>()!;
@@ -35,32 +35,42 @@ public sealed class GameObject
public void Awake()
{
foreach (var component in _components)
{
component.Awake();
}
}
public void Start()
{
foreach (var component in _components)
{
component.Start();
}
}
public void Update()
public void Update(double parDeltaTime)
{
foreach (var component in _components)
component.Update();
{
component.Update(parDeltaTime);
}
}
public void Render()
{
foreach (var component in _components)
{
component.Render();
}
}
public void Destroy()
{
foreach (var component in _components)
{
component.Destroy();
}
}
public T? GetComponent<T>() where T : Component.Component
{
@@ -73,32 +83,36 @@ public sealed class GameObject
AddComponent(component);
}
public void AddComponent<T>(params object?[] args) where T : Component.Component
public void AddComponent<T>(params object?[] parArgs) where T : Component.Component
{
var component = (T?)Activator.CreateInstance(
typeof(T),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance |
BindingFlags.OptionalParamBinding,
null,
args,
parArgs,
null
);
if (component == null)
{
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
}
AddComponent(component);
}
public void AddComponent<T>(T component) where T : Component.Component
public void AddComponent<T>(T parComponent) where T : Component.Component
{
_componentActions.Enqueue(() =>
{
if (HasComponent<T>())
{
throw new ArgumentException($"GameObject already has component of type {typeof(T)}");
}
component.GameObject = this;
_components.Add(component);
parComponent.GameObject = this;
_components.Add(parComponent);
_addedComponentTypes.Add(typeof(T).GetComponentBaseType());
});
}
@@ -106,16 +120,22 @@ public sealed class GameObject
public void RemoveComponent<T>() where T : Component.Component
{
if (typeof(T) == typeof(Transform))
{
throw new ArgumentException("GameObject cannot remove Transform component");
}
_componentActions.Enqueue(() =>
{
if (!HasComponent<T>())
{
throw new ArgumentException($"GameObject does not have component of type {typeof(T)}");
}
var component = GetComponent<T>();
if (component == null)
{
return;
}
_components.Remove(component);
_addedComponentTypes.Remove(typeof(T));
@@ -131,6 +151,8 @@ public sealed class GameObject
internal void ProcessChanges()
{
while (_componentActions.TryDequeue(out var action))
{
action();
}
}
}

View File

@@ -20,102 +20,122 @@ public class Hierarchy<T> : IEnumerable<T>
internal void ProcessChanges()
{
while (_hierarchyActions.TryDequeue(out var action))
{
action();
}
}
public void Add(T obj)
public void Add(T parObj)
{
_hierarchyActions.Enqueue(() =>
{
if (_parentLookup.ContainsKey(obj))
if (_parentLookup.ContainsKey(parObj))
{
throw new ArgumentException("Object is already added to hierarchy");
}
_childrenLookup.Add(obj, new List<T>());
_parentLookup.Add(obj, null);
_childrenLookup[null].Add(obj);
_childrenLookup.Add(parObj, new List<T>());
_parentLookup.Add(parObj, null);
_childrenLookup[null].Add(parObj);
});
}
public void Remove(T obj)
public void Remove(T parObj)
{
foreach (var child in GetChildren(parObj))
{
foreach (var child in GetChildren(obj))
Remove(child);
}
_hierarchyActions.Enqueue(() =>
{
var parent = GetParent(obj);
_childrenLookup[parent].Remove(obj);
var parent = GetParent(parObj);
_childrenLookup[parent].Remove(parObj);
_parentLookup.Remove(obj);
_childrenLookup.Remove(obj);
_parentLookup.Remove(parObj);
_childrenLookup.Remove(parObj);
});
}
public void AddChild(T parent, T child)
public void AddChild(T parEnt, T parChild)
{
SetParent(child, parent);
SetParent(parChild, parEnt);
}
private void SetParent(T child, T? parent)
private void SetParent(T parChild, T? parEnt)
{
if (parChild.Equals(parEnt))
{
if (child.Equals(parent))
throw new InvalidOperationException("Child cannot be parent");
}
_hierarchyActions.Enqueue(() =>
{
if (IsInHierarchy(child, parent))
if (IsInHierarchy(parChild, parEnt))
{
throw new InvalidOperationException("Parent is a child of child");
}
var oldParent = GetParent(child);
_childrenLookup[oldParent].Remove(child);
var oldParent = GetParent(parChild);
_childrenLookup[oldParent].Remove(parChild);
_childrenLookup[parent].Add(child);
_parentLookup[child] = parent;
_childrenLookup[parEnt].Add(parChild);
_parentLookup[parChild] = parEnt;
});
}
public bool Contains(T obj)
public bool Contains(T parObj)
{
return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj);
return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj);
}
public T? GetParent(T child)
public T? GetParent(T parChild)
{
return _parentLookup.TryGetValue(child, out var parent)
return _parentLookup.TryGetValue(parChild, out var parent)
? parent
: throw new InvalidOperationException($"Child {child} is not in hierarchy");
: throw new InvalidOperationException($"Child {parChild} is not in hierarchy");
}
public IEnumerable<T> GetChildren(T? obj = null)
public IEnumerable<T> GetChildren(T? parObj = null)
{
return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty<T>();
return _childrenLookup.TryGetValue(parObj, out IList<T>? children) ? children : Enumerable.Empty<T>();
}
public bool IsInHierarchy(T? ancestor, T? child)
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 (child == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
return false;
}
if (ancestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
if (parAncestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
{
return false;
}
if (ancestor.Equals(child))
if (parAncestor.Equals(parChild))
{
return true;
}
var parent = GetParent(child);
var parent = GetParent(parChild);
if (parent == null)
{
return false;
if (ancestor.Equals(parent))
return true;
return IsInHierarchy(ancestor, parent);
}
public IEnumerable<T> GetAllChildren(T? obj = null)
if (parAncestor.Equals(parent))
{
var children = GetChildren(obj);
return true;
}
return IsInHierarchy(parAncestor, parent);
}
public IEnumerable<T> GetAllChildren(T? parObj = null)
{
IEnumerable<T>? children = GetChildren(parObj);
foreach (var child in children)
{

View File

@@ -0,0 +1,6 @@
namespace Engine.Scene;
public interface IRender
{
void Render();
}

View File

@@ -0,0 +1,6 @@
namespace Engine.Scene;
public interface IUpdate
{
void Update(double parDeltaTime);
}

View File

@@ -3,10 +3,10 @@ using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public class Scene
public class Scene : IUpdate, IRender
{
public bool IsPlaying { get; private set; }
public ICamera? Camera { get; private set; }
public ICamera? MainCamera { get; private set; }
internal Hierarchy<GameObject> Hierarchy { get; } = [];
@@ -15,35 +15,43 @@ public class Scene
internal void Enter()
{
if (IsPlaying)
{
throw new InvalidOperationException("Scene is already playing");
}
ProcessChanges();
Camera = FindFirstComponent<Camera>();
MainCamera = FindFirstComponent<Camera>();
IsPlaying = true;
}
public T? FindFirstComponent<T>() where T : Component.Component
{
return Hierarchy.Select(gameObject => gameObject.GetComponent<T>()).OfType<T>().FirstOrDefault();
return Hierarchy.Select(parGameObject => parGameObject.GetComponent<T>()).FirstOrDefault();
}
internal void Update()
public void Update(double parDeltaTime)
{
if (!IsPlaying)
{
throw new InvalidOperationException("Scene is not playing");
}
ProcessChanges();
foreach (var gameObject in Hierarchy)
gameObject.Update();
{
gameObject.Update(parDeltaTime);
}
}
internal void Render()
public void Render()
{
if (!IsPlaying)
{
throw new InvalidOperationException("Scene is not playing");
}
foreach (var gameObject in Hierarchy)
{
@@ -54,7 +62,9 @@ public class Scene
internal void Exit()
{
if (!IsPlaying)
{
throw new InvalidOperationException("Scene is not playing");
}
foreach (var gameObject in Hierarchy)
{
@@ -64,34 +74,34 @@ public class Scene
IsPlaying = false;
}
public void Add(GameObject gameObject)
public void Add(GameObject parGameObject)
{
Hierarchy.Add(gameObject);
Hierarchy.Add(parGameObject);
_sceneActions.Enqueue(() =>
{
gameObject.Scene = this;
parGameObject.Scene = this;
gameObject.Awake();
gameObject.Start();
parGameObject.Awake();
parGameObject.Start();
});
}
public void Remove(GameObject gameObject)
public void Remove(GameObject parGameObject)
{
Hierarchy.Remove(gameObject);
Hierarchy.Remove(parGameObject);
_sceneActions.Enqueue(() =>
{
foreach (var child in Hierarchy.GetAllChildren(gameObject))
foreach (var child in Hierarchy.GetAllChildren(parGameObject))
{
child.Destroy();
child.Scene = null;
}
gameObject.Destroy();
parGameObject.Destroy();
gameObject.Scene = null;
parGameObject.Scene = null;
});
}
@@ -100,9 +110,13 @@ public class Scene
Hierarchy.ProcessChanges();
while (_sceneActions.TryDequeue(out var action))
{
action();
}
foreach (var gameObject in Hierarchy)
{
gameObject.ProcessChanges();
}
}
}

View File

@@ -0,0 +1,34 @@
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public class SceneManager : IUpdate, IRender
{
public Scene? CurrentScene => _currentScene;
private Scene? _currentScene;
private Scene? _nextScene;
public void TransitionTo(Scene parScene)
{
_nextScene = parScene;
}
public void Update(double parDeltaTime)
{
if (_nextScene != null)
{
_currentScene?.Exit();
_currentScene = _nextScene;
_nextScene = null;
_currentScene.Enter();
}
_currentScene?.Update(parDeltaTime);
}
public void Render()
{
_currentScene?.Render();
}
}

View File

@@ -4,11 +4,13 @@ namespace Engine.Util;
public static class Math
{
public static Vector4 MulProject(this Vector4 a, in Matrix4 m)
public static Vector4 MulProject(this Vector4 parA, in Matrix4 parM)
{
var result = a * m;
var result = parA * parM;
if (result.W != 0.0)
{
result /= result.W;
}
return result;
}

View File

@@ -1,55 +1,64 @@
namespace Engine.Util;
public readonly struct NullableObject<T>
public readonly struct NullableObject<T>(T? parValue) : IEquatable<NullableObject<T>>
where T : class
{
public bool IsNull => _value == null;
public T? Value => _value;
public bool IsNull => Value == null;
public T? Value { get; } = parValue;
private readonly T? _value;
public NullableObject()
public NullableObject() : this(null)
{
_value = null;
}
public NullableObject(T? value)
public static implicit operator T?(NullableObject<T> parNullableObject)
{
_value = value;
return parNullableObject.Value;
}
public static implicit operator T?(NullableObject<T> nullableObject) => nullableObject.Value;
public static implicit operator NullableObject<T>(T? value) => new(value);
public static implicit operator NullableObject<T>(T? parValue)
{
return new NullableObject<T>(parValue);
}
public override string ToString()
{
return _value?.ToString() ?? "null";
return Value?.ToString() ?? "null";
}
public override bool Equals(object? obj)
public override bool Equals(object? parObj)
{
if (obj == null)
return IsNull;
if (obj is not NullableObject<T> other)
return false;
if (IsNull && other.IsNull)
return true;
return _value!.Equals(other._value);
return parObj is NullableObject<T> other && Equals(other);
}
public override int GetHashCode()
{
if (IsNull)
{
return 0;
}
var hashCode = _value!.GetHashCode();
var hashCode = Value!.GetHashCode();
if (hashCode >= 0)
{
hashCode += 1;
}
return hashCode;
}
public bool Equals(NullableObject<T> parOther)
{
return EqualityComparer<T?>.Default.Equals(Value, parOther.Value);
}
public static bool operator ==(NullableObject<T> parLeft, NullableObject<T> parRight)
{
return parLeft.Equals(parRight);
}
public static bool operator !=(NullableObject<T> parLeft, NullableObject<T> parRight)
{
return !(parLeft == parRight);
}
}

View File

@@ -1,13 +1,9 @@
using Engine.Renderer;
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using OpenTK.Graphics.OpenGL;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine;
public class Window : IPresenter
public class Window
{
public bool IsExiting => _window.IsExiting;
public int Width { get; private set; }
@@ -17,19 +13,19 @@ public class Window : IPresenter
private readonly NativeWindow _window;
private readonly bool _headless;
public Window(Engine engine, NativeWindow window, bool headless)
public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless)
{
_engine = engine;
_window = window;
_headless = headless;
_engine = parEngine;
_window = parWindow;
_headless = parHeadless;
(Width, Height) = _window.ClientSize;
_window.MakeCurrent();
_window.Resize += args =>
_window.Resize += parArgs =>
{
Width = args.Width;
Height = args.Height;
Width = parArgs.Width;
Height = parArgs.Height;
};
}
@@ -41,45 +37,12 @@ public class Window : IPresenter
_window.SwapBuffers();
}
}
public void Present(IConstTexture texture)
{
if (_headless)
return;
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_engine.Renderer.TextureInternal.Bind();
GL.Enable(EnableCap.Texture2D);
GL.Begin(PrimitiveType.Quads);
GL.Color3(1, 1, 1);
GL.TexCoord2(0, 0);
GL.Vertex2(0, 0);
GL.TexCoord2(1, 0);
GL.Vertex2(1, 0);
GL.TexCoord2(1, 1);
GL.Vertex2(1, 1);
GL.TexCoord2(0, 1);
GL.Vertex2(0, 1);
GL.End();
GL.Disable(EnableCap.Texture2D);
GL.Flush();
_engine.Renderer.TextureInternal.Unbind();
}
}
public static class NativeWindowExtensions
{
public static unsafe void SwapBuffers(this NativeWindow window)
public static unsafe void SwapBuffers(this NativeWindow parWindow)
{
GLFW.SwapBuffers(window.WindowPtr);
GLFW.SwapBuffers(parWindow.WindowPtr);
}
}

View File

@@ -0,0 +1,129 @@
using Engine.Input;
namespace PresenterConsole;
public class ConsoleInputHandler : IInputHandler
{
private readonly HashSet<ConsoleKey> _currentKeys = [];
private readonly HashSet<ConsoleKey> _previousKeys = [];
private ConsoleModifiers _currentModifiers;
private ConsoleModifiers _previousModifiers;
public void Update(double deltaTime)
{
// Save previous state
_previousKeys.Clear();
foreach (var key in _currentKeys)
{
_previousKeys.Add(key);
}
_previousModifiers = _currentModifiers;
// Clear current state
_currentKeys.Clear();
_currentModifiers = 0;
// Read keys
while (Console.KeyAvailable)
{
var keyInfo = Console.ReadKey(intercept: true);
_currentKeys.Add(keyInfo.Key);
_currentModifiers |= keyInfo.Modifiers;
}
}
public bool IsKeyPressed(KeyCode keyCode)
{
if (IsModifierKey(keyCode))
return IsModifierActive(keyCode);
return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
}
public bool IsKeyJustPressed(KeyCode keyCode)
{
if (IsModifierKey(keyCode))
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
var consoleKey = ConvertToConsoleKey(keyCode);
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
}
public bool IsKeyRepeat(KeyCode keyCode)
{
if (IsModifierKey(keyCode))
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
var consoleKey = ConvertToConsoleKey(keyCode);
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
}
public bool IsMouseButtonPressed(MouseButton button)
{
return false;
}
public bool IsMouseButtonJustPressed(MouseButton button)
{
return false;
}
private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
private bool IsModifierActive(KeyCode keyCode) => keyCode switch
{
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
_ => false
};
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
{
KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
_ => false
};
private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
{
KeyCode.A => ConsoleKey.A,
KeyCode.B => ConsoleKey.B,
KeyCode.C => ConsoleKey.C,
KeyCode.D => ConsoleKey.D,
KeyCode.E => ConsoleKey.E,
KeyCode.F => ConsoleKey.F,
KeyCode.G => ConsoleKey.G,
KeyCode.H => ConsoleKey.H,
KeyCode.I => ConsoleKey.I,
KeyCode.J => ConsoleKey.J,
KeyCode.K => ConsoleKey.K,
KeyCode.L => ConsoleKey.L,
KeyCode.M => ConsoleKey.M,
KeyCode.N => ConsoleKey.N,
KeyCode.O => ConsoleKey.O,
KeyCode.P => ConsoleKey.P,
KeyCode.Q => ConsoleKey.Q,
KeyCode.R => ConsoleKey.R,
KeyCode.S => ConsoleKey.S,
KeyCode.T => ConsoleKey.T,
KeyCode.U => ConsoleKey.U,
KeyCode.V => ConsoleKey.V,
KeyCode.W => ConsoleKey.W,
KeyCode.X => ConsoleKey.X,
KeyCode.Y => ConsoleKey.Y,
KeyCode.Z => ConsoleKey.Z,
KeyCode.Enter => ConsoleKey.Enter,
KeyCode.Escape => ConsoleKey.Escape,
KeyCode.Space => ConsoleKey.Spacebar,
KeyCode.Tab => ConsoleKey.Tab,
KeyCode.Backspace => ConsoleKey.Backspace,
KeyCode.Up => ConsoleKey.UpArrow,
KeyCode.Down => ConsoleKey.DownArrow,
KeyCode.Left => ConsoleKey.LeftArrow,
KeyCode.Right => ConsoleKey.RightArrow,
_ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
};
}

View File

@@ -0,0 +1,51 @@
using Engine.Asset;
using Engine.Renderer;
using Engine.Renderer.Framebuffer;
using Engine.Renderer.Texture;
using OpenTK.Windowing.Common;
namespace PresenterConsole;
public class ConsolePresenter : IPresenter
{
public bool IsExiting => false;
public int Width => Console.WindowWidth;
public int Height => Console.WindowHeight;
public event Action<ResizeEventArgs>? Resize;
private readonly Framebuffer _framebuffer;
private Image<AsciiPixel> _asciiImage;
public ConsolePresenter(int width, int height)
{
_framebuffer = Framebuffer.Builder(width, height)
.AddColorAttachment<AsciiPixel>()
.Build();
}
public void Present(IConstTexture texture)
{
var openglTexture = (Texture)texture;
_framebuffer.Bind();
openglTexture.BindUnit();
// TODO: render with ascii shader
_framebuffer.Unbind();
var asciiTexture = _framebuffer.TextureInternal;
if (asciiTexture == null)
throw new InvalidOperationException("Framebuffer texture is null");
if (asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
_asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
asciiTexture.ReadPixels(_asciiImage);
}
public void Update(double deltaTime)
{
throw new NotImplementedException();
}
}