diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f8e79e5 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/DoomDeathmatch.sln b/DoomDeathmatch.sln index 00adcbd..d20fae8 100644 --- a/DoomDeathmatch.sln +++ b/DoomDeathmatch.sln @@ -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 diff --git a/Engine/src/Asset/Image.cs b/Engine/src/Asset/Image.cs index d8721f3..25bd691 100644 --- a/Engine/src/Asset/Image.cs +++ b/Engine/src/Asset/Image.cs @@ -3,30 +3,30 @@ using Engine.Renderer.Texture; namespace Engine.Asset; -public class Image(T[,] pixels) - where T : struct, IPixel +public class Image(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]) + { + } - public DynamicTexture ToDynamicTexture() - { - var texture = DynamicTexture.Create(Width, Height); - texture.UploadPixels(this); - return texture; - } + public DynamicTexture ToDynamicTexture() + { + var texture = DynamicTexture.Create(Width, Height); + texture.UploadPixels(this); + return texture; + } - public StaticTexture ToStaticTexture() - { - var texture = StaticTexture.Create(Width, Height); - texture.UploadPixels(this); - return texture; - } + public StaticTexture ToStaticTexture() + { + var texture = StaticTexture.Create(Width, Height); + texture.UploadPixels(this); + return texture; + } } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs index e6129c6..c5871ec 100644 --- a/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/IMeshLoader.cs @@ -2,28 +2,30 @@ 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(); + uint index = 0; + + foreach (var vertex in parMesh.Vertices) { - var optimizedMesh = new Mesh(); - var vertexMap = new Dictionary(); - uint index = 0; + if (vertexMap.TryGetValue(vertex, out var existingIndex)) + { + optimizedMesh.IndicesInternal.Add(existingIndex); + } + else + { + vertexMap.Add(vertex, index); + optimizedMesh.VerticesInternal.Add(vertex); + optimizedMesh.IndicesInternal.Add(index); - foreach (var vertex in mesh.Vertices) - { - if (vertexMap.TryGetValue(vertex, out var existingIndex)) - optimizedMesh.IndicesInternal.Add(existingIndex); - else - { - vertexMap.Add(vertex, index); - optimizedMesh.VerticesInternal.Add(vertex); - optimizedMesh.IndicesInternal.Add(index); - - index++; - } - } - - return optimizedMesh; + index++; + } } + + return optimizedMesh; + } } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs b/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs index a33df1b..09a8d79 100644 --- a/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs +++ b/Engine/src/Asset/Mesh/Loader/MeshLoaderParameters.cs @@ -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 } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs index b493583..ff3fc6b 100644 --- a/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/ObjMeshLoader.cs @@ -5,86 +5,89 @@ 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(); + + var tempVertices = new List(); + var tempNormals = new List(); + var tempUVs = new List(); + var index = 0u; + + var loadNormals = parAmeters.HasFlag(MeshLoaderParameters._loadNormals); + var loadUVs = parAmeters.HasFlag(MeshLoaderParameters._loadUVs); + + using var reader = new StreamReader(parPath); + while (reader.ReadLine() is { } line) { - var mesh = new Mesh(); + string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - var tempVertices = new List(); - var tempNormals = new List(); - var tempUVs = new List(); - var index = 0u; + if (parts.Length == 0 || parts[0].StartsWith('#')) + { + continue; + } - var loadNormals = parameters.HasFlag(MeshLoaderParameters.LoadNormals); - var loadUVs = parameters.HasFlag(MeshLoaderParameters.LoadUVs); + switch (parts[0]) + { + case "v": + tempVertices.Add(new Vector3( + float.Parse(parts[1], CultureInfo.InvariantCulture), + float.Parse(parts[2], CultureInfo.InvariantCulture), + float.Parse(parts[3], CultureInfo.InvariantCulture) + )); + break; - using var reader = new StreamReader(path); - while (reader.ReadLine() is { } line) - { - var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + case "vt" when loadUVs: + tempUVs.Add(new Vector2( + float.Parse(parts[1], CultureInfo.InvariantCulture), + float.Parse(parts[2], CultureInfo.InvariantCulture) + )); + break; - if (parts.Length == 0 || parts[0].StartsWith('#')) - continue; + case "vn" when loadNormals: + tempNormals.Add(new Vector3( + float.Parse(parts[1], CultureInfo.InvariantCulture), + float.Parse(parts[2], CultureInfo.InvariantCulture), + float.Parse(parts[3], CultureInfo.InvariantCulture) + )); + break; - switch (parts[0]) + case "f": + for (var i = 1; i <= 3; i++) + { + string[]? faceComponents = parts[i].Split('/'); + var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] }; + + if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "") { - case "v": - tempVertices.Add(new Vector3( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture), - float.Parse(parts[3], CultureInfo.InvariantCulture) - )); - break; - - case "vt" when loadUVs: - tempUVs.Add(new Vector2( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture) - )); - break; - - case "vn" when loadNormals: - tempNormals.Add(new Vector3( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture), - float.Parse(parts[3], CultureInfo.InvariantCulture) - )); - break; - - 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] - }; - - if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "") - { - meshVertex.Uv = tempUVs[int.Parse(faceComponents[1]) - 1]; - } - - if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "") - { - meshVertex.Normal = tempNormals[int.Parse(faceComponents[2]) - 1]; - } - - mesh.VerticesInternal.Add(meshVertex); - mesh.IndicesInternal.Add(index++); - } - - break; + meshVertex._uv = tempUVs[int.Parse(faceComponents[1]) - 1]; } - } - if (parameters.HasFlag(MeshLoaderParameters.Optimize)) - mesh = IMeshLoader.Optimize(mesh); + if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "") + { + meshVertex._normal = tempNormals[int.Parse(faceComponents[2]) - 1]; + } - return mesh; + mesh.VerticesInternal.Add(meshVertex); + mesh.IndicesInternal.Add(index++); + } + + break; + } } + + if (parAmeters.HasFlag(MeshLoaderParameters._optimize)) + { + mesh = IMeshLoader.Optimize(mesh); + } + + return mesh; + } } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs b/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs index baa1133..28078ae 100644 --- a/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs +++ b/Engine/src/Asset/Mesh/Loader/StlMeshLoader.cs @@ -5,62 +5,67 @@ 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(parPath); + while (reader.ReadLine() is { } line) { - var mesh = new Mesh(); + line = line.Trim(); - var currentNormal = new Vector3(); - var index = 0u; + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("solid") || line.StartsWith("outer loop") || + line.StartsWith("endloop")) + { + continue; + } - using var reader = new StreamReader(path); - while (reader.ReadLine() is { } line) - { - line = line.Trim(); + if (line.StartsWith("endsolid")) + { + break; + } - if (string.IsNullOrWhiteSpace(line) || line.StartsWith("solid") || line.StartsWith("outer loop") || - line.StartsWith("endloop")) - continue; - if (line.StartsWith("endsolid")) - break; + string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + switch (parts[0]) + { + 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), + float.Parse(parts[4], CultureInfo.InvariantCulture) + ); + break; + case "vertex": + { + var vertex = new Vector3( + float.Parse(parts[1], CultureInfo.InvariantCulture), + float.Parse(parts[2], CultureInfo.InvariantCulture), + float.Parse(parts[3], CultureInfo.InvariantCulture) + ); - switch (parts[0]) - { - 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), - float.Parse(parts[4], CultureInfo.InvariantCulture) - ); - break; - case "vertex": - { - var vertex = new Vector3( - float.Parse(parts[1], CultureInfo.InvariantCulture), - float.Parse(parts[2], CultureInfo.InvariantCulture), - float.Parse(parts[3], CultureInfo.InvariantCulture) - ); - - mesh.VerticesInternal.Add(new Mesh.Vertex - { - Position = vertex, - Normal = currentNormal - }); - mesh.IndicesInternal.Add(index++); - break; - } - } - } - - if (parameters.HasFlag(MeshLoaderParameters.Optimize)) - mesh = IMeshLoader.Optimize(mesh); - - return mesh; + mesh.VerticesInternal.Add(new Mesh.Vertex { _position = vertex, _normal = currentNormal }); + mesh.IndicesInternal.Add(index++); + break; + } + } } + + if (parAmeters.HasFlag(MeshLoaderParameters._optimize)) + { + mesh = IMeshLoader.Optimize(mesh); + } + + return mesh; + } } \ No newline at end of file diff --git a/Engine/src/Asset/Mesh/Mesh.cs b/Engine/src/Asset/Mesh/Mesh.cs index 36d59e7..683fd5a 100644 --- a/Engine/src/Asset/Mesh/Mesh.cs +++ b/Engine/src/Asset/Mesh/Mesh.cs @@ -6,24 +6,21 @@ namespace Engine.Asset.Mesh; public class Mesh { - public IReadOnlyList Vertices => _vertices; - public IReadOnlyList Indices => _indices; + public IReadOnlyList Vertices => _vertices; + public IReadOnlyList Indices => _indices; - internal IList VerticesInternal => _vertices; - internal IList IndicesInternal => _indices; + internal IList VerticesInternal => _vertices; + internal IList IndicesInternal => _indices; - private readonly List _vertices = []; - private readonly List _indices = []; + private readonly List _vertices = []; + private readonly List _indices = []; - public record struct Vertex : IVertex - { - [Vertex(VertexAttribType.Float, 3)] - public Vector3 Position; + public record struct Vertex : IVertex + { + [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; + } } \ No newline at end of file diff --git a/Engine/src/Engine.cs b/Engine/src/Engine.cs index 3d6329a..e6fee70 100644 --- a/Engine/src/Engine.cs +++ b/Engine/src/Engine.cs @@ -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; @@ -9,51 +12,72 @@ namespace Engine; public sealed class Engine { - public Renderer.Renderer Renderer => _renderer; + public Renderer.Renderer Renderer => _renderer; + public SceneManager SceneManager => _sceneManager; - private readonly Window _window; - private readonly Renderer.Renderer _renderer; - private readonly ILogger _logger; + 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 { - var settings = new NativeWindowSettings - { - ClientSize = headless ? new Vector2i(1, 1) : new Vector2i(width, height), - Title = title, - StartVisible = !headless, - APIVersion = new Version(4, 5), - Profile = ContextProfile.Compatability - }; + 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"; + Thread.CurrentThread.Name = "RendererThread"; - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.WithThreadName() - .Enrich.WithThreadId() - .Enrich.FromLogContext() - .WriteTo.Console( - outputTemplate: - "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}", - theme: AnsiConsoleTheme.Literate) - .CreateLogger(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithThreadName() + .Enrich.WithThreadId() + .Enrich.FromLogContext() + .WriteTo.Console( + outputTemplate: + "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}", + theme: AnsiConsoleTheme.Literate) + .CreateLogger(); - _logger = Log.ForContext(); + _logger = Log.ForContext(); + } + + 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); + + while (!_window.IsExiting) + { + _window.Update(); } - public void Run() + _updateThread.Join(); + } + + private void RunUpdate() + { + var timer = Stopwatch.StartNew(); + while (!_window.IsExiting) { - GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f); - - GL.Viewport(0, 0, _window.Width, _window.Height); - - while (!_window.IsExiting) - { - _window.Update(); - } + _window.Update(); + _sceneManager.Update(timer.Elapsed.TotalSeconds); + timer.Restart(); } + } } \ No newline at end of file diff --git a/Engine/src/Input/IInputHandler.cs b/Engine/src/Input/IInputHandler.cs index 4cdc45c..3002ec9 100644 --- a/Engine/src/Input/IInputHandler.cs +++ b/Engine/src/Input/IInputHandler.cs @@ -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); } \ No newline at end of file diff --git a/Engine/src/Input/Key.cs b/Engine/src/Input/Key.cs deleted file mode 100644 index cf838ae..0000000 --- a/Engine/src/Input/Key.cs +++ /dev/null @@ -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, -} \ No newline at end of file diff --git a/Engine/src/Input/KeyCode.cs b/Engine/src/Input/KeyCode.cs new file mode 100644 index 0000000..1b43403 --- /dev/null +++ b/Engine/src/Input/KeyCode.cs @@ -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 +} \ No newline at end of file diff --git a/Engine/src/Input/MouseButton.cs b/Engine/src/Input/MouseButton.cs index 79c4755..aedd735 100644 --- a/Engine/src/Input/MouseButton.cs +++ b/Engine/src/Input/MouseButton.cs @@ -2,12 +2,12 @@ public enum MouseButton { - Left, - Right, - Middle, + _left, + _right, + _middle, - Button4, - Button5, - Button6, - Button7, + _button4, + _button5, + _button6, + _button7 } \ No newline at end of file diff --git a/Engine/src/Renderer/Buffer/IndexBuffer.cs b/Engine/src/Renderer/Buffer/IndexBuffer.cs index 82576ee..81ef8c8 100644 --- a/Engine/src/Renderer/Buffer/IndexBuffer.cs +++ b/Engine/src/Renderer/Buffer/IndexBuffer.cs @@ -5,48 +5,52 @@ namespace Engine.Renderer.Buffer; public class IndexBuffer : OpenGlObject { - internal int Count { get; } + internal int Count { get; } - public IndexBuffer(int count, BufferStorageFlags flags) + public IndexBuffer(int parCount, BufferStorageFlags parFlags) + { + Count = parCount; + + GL.CreateBuffers(1, out int handle); + Handle = handle; + + GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, parFlags); + + Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count); + } + + public void UploadData(uint[] parData) + { + UploadData(0, parData); + } + + public void UploadData(int parOffset, uint[] parData) + { + if (parOffset < 0) { - Count = count; - - GL.CreateBuffers(1, out int handle); - Handle = handle; - - GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, flags); - - Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count); + throw new ArgumentException("Offset must be greater than 0"); } - public void UploadData(uint[] data) + if (parData.Length + parOffset > Count) { - UploadData(0, data); + throw new ArgumentException("Data array is too large"); } - public void UploadData(int offset, uint[] data) - { - if (offset < 0) - throw new ArgumentException("Offset must be greater than 0"); + GL.NamedBufferSubData(Handle, parOffset, parData.Length * sizeof(uint), parData); + } - if (data.Length + offset > Count) - throw new ArgumentException("Data array is too large"); + public override void Bind() + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); + } - GL.NamedBufferSubData(Handle, offset, data.Length * sizeof(uint), data); - } + public override void Unbind() + { + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + } - internal override void Bind() - { - GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); - } - - internal override void Unbind() - { - GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); - } - - protected override void Destroy() - { - GL.DeleteBuffer(Handle); - } + protected override void Destroy() + { + GL.DeleteBuffer(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Buffer/Vertex/IVertex.cs b/Engine/src/Renderer/Buffer/Vertex/IVertex.cs index 44c4317..9276fc8 100644 --- a/Engine/src/Renderer/Buffer/Vertex/IVertex.cs +++ b/Engine/src/Renderer/Buffer/Vertex/IVertex.cs @@ -6,45 +6,61 @@ namespace Engine.Renderer.Buffer.Vertex; public interface IVertex { - public static IOrderedEnumerable GetFields() => GetFields(typeof(T)); + public static IOrderedEnumerable GetFields() + { + return GetFields(typeof(T)); + } - public static IOrderedEnumerable GetFields(Type type) => - type.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(f => f.MetadataToken); + public static IOrderedEnumerable GetFields(Type parType) + { + return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken); + } - public static bool IsValid(Type type) + public static bool IsValid(Type parType) + { + if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex))) { - if (!type.IsValueType || !type.IsAssignableTo(typeof(IVertex))) - return false; - - var fields = GetFields(type); - var totalSize = 0; - foreach (var field in fields) - { - if (!field.FieldType.IsValueType) - return false; - - var attribute = field.GetCustomAttribute(); - 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 false; } - public static int AttributeSize(VertexAttribType type) => type switch + IOrderedEnumerable? fields = GetFields(parType); + var totalSize = 0; + foreach (var field in fields) { - VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte), - VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short), - VertexAttribType.Int or VertexAttribType.UnsignedInt => sizeof(int), - VertexAttribType.HalfFloat => Marshal.SizeOf(), - VertexAttribType.Float => sizeof(float), - VertexAttribType.Double => sizeof(double), - _ => 0 + if (!field.FieldType.IsValueType) + { + return false; + } + + var attribute = field.GetCustomAttribute(); + 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(parType); + } + + public static int AttributeSize(VertexAttribType parType) + { + return parType switch + { + VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte), + VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short), + VertexAttribType.Int or VertexAttribType.UnsignedInt => sizeof(int), + VertexAttribType.HalfFloat => Marshal.SizeOf(), + VertexAttribType.Float => sizeof(float), + VertexAttribType.Double => sizeof(double), + _ => 0 }; + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs b/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs index 7671b9d..2a79028 100644 --- a/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs +++ b/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs @@ -5,22 +5,27 @@ namespace Engine.Renderer.Buffer.Vertex; [AttributeUsage(AttributeTargets.Field)] public class VertexAttribute : Attribute { - public VertexAttribType Type { get; } - public int ComponentCount { get; } - public bool Normalized { get; } - public int RepeatCount { get; } + public VertexAttribType Type { get; } + public int ComponentCount { get; } + 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) - throw new ArgumentException("Repeat must be greater than 0"); - - Type = type; - ComponentCount = componentCount; - Normalized = normalized; - RepeatCount = repeatCount; + throw new ArgumentException("Count must be greater than 0"); } + + if (parRepeatCount <= 0) + { + throw new ArgumentException("Repeat must be greater than 0"); + } + + Type = parType; + ComponentCount = parComponentCount; + Normalized = parNormalized; + RepeatCount = parRepeatCount; + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Buffer/VertexArray.cs b/Engine/src/Renderer/Buffer/VertexArray.cs index 10c1454..d3d3f1b 100644 --- a/Engine/src/Renderer/Buffer/VertexArray.cs +++ b/Engine/src/Renderer/Buffer/VertexArray.cs @@ -8,82 +8,87 @@ namespace Engine.Renderer.Buffer; public class VertexArray : OpenGlObject { - // private IndexBuffer? _boundIndexBuffer; - // private readonly Dictionary> _boundVertexBuffers = new(); + // private IndexBuffer? _boundIndexBuffer; + // private readonly Dictionary> _boundVertexBuffers = new(); - public VertexArray() + public VertexArray() + { + GL.CreateVertexArrays(1, out int handle); + Handle = handle; + } + + public void BindIndexBuffer(IndexBuffer parBuffer) + { + GL.VertexArrayElementBuffer(Handle, parBuffer.Handle); + + Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, parBuffer.Handle); + } + + public void BindVertexBuffer(VertexBuffer parBuffer, int parBindingIndex = 0, int parDivisor = 0) + where T : struct, IVertex + { + if (parBindingIndex < 0) { - GL.CreateVertexArrays(1, out int handle); - Handle = handle; + throw new ArgumentException("Binding index must be greater than 0"); } - public void BindIndexBuffer(IndexBuffer buffer) + if (parDivisor < 0) { - GL.VertexArrayElementBuffer(Handle, buffer.Handle); - - Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, buffer.Handle); + throw new ArgumentException("Divisor must be greater than 0"); } - public void BindVertexBuffer(VertexBuffer buffer, int bindingIndex = 0, int divisor = 0) - where T : struct, IVertex + var stride = Marshal.SizeOf(); + IOrderedEnumerable? fields = IVertex.GetFields(); + + GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride); + + var location = 0; + foreach (var field in fields) { - if (bindingIndex < 0) - throw new ArgumentException("Binding index must be greater than 0"); + var attribute = field.GetCustomAttribute()!; - if (divisor < 0) - throw new ArgumentException("Divisor must be greater than 0"); + var offset = Marshal.OffsetOf(field.Name).ToInt32(); + SetupAttribute(attribute, location, offset, parBindingIndex); - var stride = Marshal.SizeOf(); - var fields = IVertex.GetFields(); - - GL.VertexArrayVertexBuffer(Handle, bindingIndex, buffer.Handle, 0, stride); - - var location = 0; - foreach (var field in fields) - { - var attribute = field.GetCustomAttribute()!; - - var offset = Marshal.OffsetOf(field.Name).ToInt32(); - SetupAttribute(attribute, location, offset, bindingIndex); - - location += attribute.RepeatCount; - } - - GL.VertexArrayBindingDivisor(Handle, bindingIndex, divisor); - - Log.Debug( - "Vertex array {Handle} bound to vertex buffer {Buffer} at {BindingIndex} binding with {Divisor} divisor", - Handle, buffer.Handle, bindingIndex, divisor); + location += attribute.RepeatCount; } - private void SetupAttribute(VertexAttribute attribute, int baseLocation, int baseOffset, int bindingIndex) + GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor); + + Log.Debug( + "Vertex array {Handle} bound to vertex buffer {Buffer} at {BindingIndex} binding with {Divisor} divisor", + Handle, parBuffer.Handle, parBindingIndex, parDivisor); + } + + private void SetupAttribute(VertexAttribute parAttribute, int parBaseLocation, int parBaseOffset, int parBindingIndex) + { + var size = parAttribute.ComponentCount * IVertex.AttributeSize(parAttribute.Type); + + for (var i = 0; i < parAttribute.RepeatCount; i++) { - var size = attribute.ComponentCount * IVertex.AttributeSize(attribute.Type); + var location = parBaseLocation + i; + var offset = parBaseOffset + (i * size); - for (var i = 0; i < attribute.RepeatCount; i++) - { - var location = baseLocation + i; - var offset = baseOffset + i * size; - - GL.EnableVertexArrayAttrib(Handle, location); - GL.VertexArrayAttribFormat(Handle, location, attribute.ComponentCount, attribute.Type, attribute.Normalized, - offset); - GL.VertexArrayAttribBinding(Handle, location, bindingIndex); - } + GL.EnableVertexArrayAttrib(Handle, location); + GL.VertexArrayAttribFormat(Handle, location, parAttribute.ComponentCount, parAttribute.Type, + parAttribute.Normalized, + offset); + GL.VertexArrayAttribBinding(Handle, location, parBindingIndex); } + } - internal override void Bind() - { - GL.BindVertexArray(Handle); - } + public override void Bind() + { + GL.BindVertexArray(Handle); + } - internal override void Unbind() - { - GL.BindVertexArray(0); - } + public override void Unbind() + { + GL.BindVertexArray(0); + } - protected override void Destroy() - { - GL.DeleteVertexArray(Handle); - } + protected override void Destroy() + { + GL.DeleteVertexArray(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Buffer/VertexBuffer.cs b/Engine/src/Renderer/Buffer/VertexBuffer.cs index 8fe1693..65100a5 100644 --- a/Engine/src/Renderer/Buffer/VertexBuffer.cs +++ b/Engine/src/Renderer/Buffer/VertexBuffer.cs @@ -6,58 +6,66 @@ using Serilog; namespace Engine.Renderer.Buffer; public class VertexBuffer : OpenGlObject - where T : struct, IVertex + where T : struct, IVertex { - internal int Count { get; } + internal int Count { get; } - private readonly int _stride = Marshal.SizeOf(); + private readonly int _stride = Marshal.SizeOf(); - public VertexBuffer(int count, BufferStorageFlags flags) + public VertexBuffer(int parCount, BufferStorageFlags parFlags) + { + if (!IVertex.IsValid(typeof(T))) { - if (!IVertex.IsValid(typeof(T))) - throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type"); - - if (count <= 0) - throw new ArgumentException("Count must be greater than 0"); - - Count = count; - - GL.CreateBuffers(1, out int handle); - Handle = handle; - - GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, flags); - - Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name); + throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type"); } - public void UploadData(T[] data) + if (parCount <= 0) { - UploadData(0, data); + throw new ArgumentException("Count must be greater than 0"); } - public void UploadData(int offset, T[] data) + Count = parCount; + + GL.CreateBuffers(1, out int handle); + Handle = handle; + + 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[] parData) + { + UploadData(0, parData); + } + + 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); + throw new ArgumentException("Offset must be greater than 0"); } - internal override void Bind() + if (parData.Length + parOffset > Count) { - GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); + throw new ArgumentException("Data array is too large"); } - internal override void Unbind() - { - GL.BindBuffer(BufferTarget.ArrayBuffer, 0); - } + GL.NamedBufferSubData(Handle, parOffset * _stride, parData.Length * _stride, parData); + } - protected override void Destroy() - { - GL.DeleteBuffer(Handle); - } + public override void Bind() + { + GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); + } + + public override void Unbind() + { + GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + } + + protected override void Destroy() + { + GL.DeleteBuffer(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Camera/ICamera.cs b/Engine/src/Renderer/Camera/ICamera.cs index 9c385bc..5a8d140 100644 --- a/Engine/src/Renderer/Camera/ICamera.cs +++ b/Engine/src/Renderer/Camera/ICamera.cs @@ -4,7 +4,7 @@ namespace Engine.Renderer.Camera; public interface ICamera { - public Matrix4 View { get; } - public Matrix4 Projection { get; } - public Vector2i ScreenSize { get; internal set; } + public Matrix4 View { get; } + public Matrix4 Projection { get; } + public Vector2i ScreenSize { get; internal set; } } \ No newline at end of file diff --git a/Engine/src/Renderer/Camera/ScreenspaceCamera.cs b/Engine/src/Renderer/Camera/ScreenspaceCamera.cs index 41d2dee..a29fa99 100644 --- a/Engine/src/Renderer/Camera/ScreenspaceCamera.cs +++ b/Engine/src/Renderer/Camera/ScreenspaceCamera.cs @@ -4,7 +4,7 @@ namespace Engine.Renderer.Camera; public class ScreenspaceCamera : ICamera { - public Matrix4 View => Matrix4.Identity; - public Matrix4 Projection => Matrix4.Identity; - public Vector2i ScreenSize { get; set; } + public Matrix4 View => Matrix4.Identity; + public Matrix4 Projection => Matrix4.Identity; + public Vector2i ScreenSize { get; set; } } \ No newline at end of file diff --git a/Engine/src/Renderer/Debug.cs b/Engine/src/Renderer/Debug.cs index 1eb2e65..cbaa41a 100644 --- a/Engine/src/Renderer/Debug.cs +++ b/Engine/src/Renderer/Debug.cs @@ -7,62 +7,63 @@ namespace Engine.Renderer; internal static class Debug { - public static void Setup() + public static void Setup() + { + GL.Enable(EnableCap.DebugOutput); + GL.DebugMessageCallback(DebugCallback, IntPtr.Zero); + } + + private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity, + int parLength, + IntPtr parMessage, IntPtr parUserParam) + { + var logger = Log.ForContext(); + + var messageText = Marshal.PtrToStringAnsi(parMessage, parLength) ?? "Unknown OpenGL Error"; + + var typePrefix = parType switch { - GL.Enable(EnableCap.DebugOutput); - GL.DebugMessageCallback(DebugCallback, IntPtr.Zero); - } + DebugType.DebugTypeError => "Error", + DebugType.DebugTypeDeprecatedBehavior => "Deprecated", + DebugType.DebugTypeUndefinedBehavior => "Undefined", + DebugType.DebugTypePortability => "Portability", + DebugType.DebugTypePerformance => "Performance", + DebugType.DebugTypeMarker => "Marker", + DebugType.DebugTypePushGroup => "PushGroup", + DebugType.DebugTypePopGroup => "PopGroup", + DebugType.DebugTypeOther => "Info", + _ => "Unknown" + }; - private static void DebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length, - IntPtr message, IntPtr userParam) + var sourcePrefix = parSource switch { - var logger = Log.ForContext(); + DebugSource.DebugSourceApi => "API", + DebugSource.DebugSourceWindowSystem => "Window", + DebugSource.DebugSourceShaderCompiler => "Shader", + DebugSource.DebugSourceThirdParty => "ThirdParty", + DebugSource.DebugSourceApplication => "Application", + DebugSource.DebugSourceOther => "Other", + _ => "Unknown" + }; - var messageText = Marshal.PtrToStringAnsi(message, length) ?? "Unknown OpenGL Error"; + logger.Write(GetLogLevel(parSeverity), + "[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})", + typePrefix, + sourcePrefix, + messageText, + parId + ); + } - var typePrefix = type switch - { - DebugType.DebugTypeError => "Error", - DebugType.DebugTypeDeprecatedBehavior => "Deprecated", - DebugType.DebugTypeUndefinedBehavior => "Undefined", - DebugType.DebugTypePortability => "Portability", - DebugType.DebugTypePerformance => "Performance", - DebugType.DebugTypeMarker => "Marker", - DebugType.DebugTypePushGroup => "PushGroup", - DebugType.DebugTypePopGroup => "PopGroup", - DebugType.DebugTypeOther => "Info", - _ => "Unknown" - }; - - var sourcePrefix = source switch - { - DebugSource.DebugSourceApi => "API", - DebugSource.DebugSourceWindowSystem => "Window", - DebugSource.DebugSourceShaderCompiler => "Shader", - DebugSource.DebugSourceThirdParty => "ThirdParty", - DebugSource.DebugSourceApplication => "Application", - DebugSource.DebugSourceOther => "Other", - _ => "Unknown" - }; - - logger.Write(GetLogLevel(severity), - "[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})", - typePrefix, - sourcePrefix, - messageText, - id - ); - } - - private static LogEventLevel GetLogLevel(DebugSeverity severity) + private static LogEventLevel GetLogLevel(DebugSeverity parSeverity) + { + return parSeverity switch { - return severity switch - { - DebugSeverity.DebugSeverityNotification => LogEventLevel.Information, - DebugSeverity.DebugSeverityHigh => LogEventLevel.Error, - DebugSeverity.DebugSeverityMedium => LogEventLevel.Warning, - DebugSeverity.DebugSeverityLow => LogEventLevel.Debug, - _ => LogEventLevel.Debug - }; - } + DebugSeverity.DebugSeverityNotification => LogEventLevel.Information, + DebugSeverity.DebugSeverityHigh => LogEventLevel.Error, + DebugSeverity.DebugSeverityMedium => LogEventLevel.Warning, + DebugSeverity.DebugSeverityLow => LogEventLevel.Debug, + _ => LogEventLevel.Debug + }; + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Framebuffer/Framebuffer.cs b/Engine/src/Renderer/Framebuffer/Framebuffer.cs index b30c9c8..9e8d1e2 100644 --- a/Engine/src/Renderer/Framebuffer/Framebuffer.cs +++ b/Engine/src/Renderer/Framebuffer/Framebuffer.cs @@ -1,4 +1,5 @@ -using Engine.Renderer.Pixel; +using System.Collections.ObjectModel; +using Engine.Renderer.Pixel; using Engine.Renderer.Texture; using OpenTK.Graphics.OpenGL; @@ -6,94 +7,119 @@ namespace Engine.Renderer.Framebuffer; public class Framebuffer : OpenGlObject { - public int Width + public int Width + { + get => _width; + protected set { - get => _width; - protected set - { - if (value <= 0) - throw new ArgumentException("Width must be greater than 0"); + if (value <= 0) + { + throw new ArgumentException("Width must be greater than 0"); + } - _width = value; - } + _width = value; + } + } + + public int Height + { + get => _height; + protected set + { + if (value <= 0) + { + throw new ArgumentException("Height must be greater than 0"); + } + + _height = value; + } + } + + internal Texture.Texture? TextureInternal => GetAttachment(FramebufferAttachment.ColorAttachment0); + + private readonly IDictionary _attachments; + private int _width; + private int _height; + + public Framebuffer(int parWidth, int parHeight, + IDictionary parAttachments) + { + Width = parWidth; + Height = parHeight; + _attachments = new ReadOnlyDictionary(parAttachments); + + GL.CreateFramebuffers(1, out int handle); + Handle = handle; + + foreach (KeyValuePair attachment in _attachments) + { + 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; + } } - public int Height + var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer); + if (status != FramebufferStatus.FramebufferComplete) { - get => _height; - protected set - { - if (value <= 0) - throw new ArgumentException("Height must be greater than 0"); + throw new Exception($"Framebuffer is not complete: {status}"); + } + } - _height = value; - } + public static FramebufferBuilder Builder(int parWidth, int parHeight) + { + return new FramebufferBuilder(parWidth, parHeight); + } + + public T? GetAttachment(FramebufferAttachment parAttachment) where T : IFramebufferAttachment + { + if (!_attachments.TryGetValue(parAttachment, out var attachmentValue)) + { + return default; } - public IConstTexture Texture => _texture; - internal Texture.Texture TextureInternal => _texture; - - 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) + if (attachmentValue is T t) { - Width = width; - Height = height; - - 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) - { - _renderbuffer = new Renderbuffer(width, height, RenderbufferStorage.Depth24Stencil8); - GL.NamedFramebufferRenderbuffer(Handle, FramebufferAttachment.DepthStencilAttachment, - RenderbufferTarget.Renderbuffer, _renderbuffer.Handle); - } - - var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer); - if (status != FramebufferStatus.FramebufferComplete) - throw new Exception($"Framebuffer is not complete: {status}"); + return t; } - public static Framebuffer Create(int width, int height, bool createRenderbuffer = true) - where T : struct, IPixel + return default; + } + + public void Resize(int parWidth, int parHeight) + { + if (Width == parWidth && Height == parHeight) { - var pixel = default(T); - return new Framebuffer(width, height, pixel.Format, pixel.Type, pixel.InternalFormat, createRenderbuffer); + return; } - public void Resize(int width, int height) + Width = parWidth; + Height = parHeight; + + foreach (KeyValuePair attachment in _attachments) { - if (Width == width && Height == height) - return; - - Width = width; - Height = height; - - _texture.Resize(width, height); - _renderbuffer?.Resize(width, height); + attachment.Value.Resize(parWidth, parHeight); } + } - internal override void Bind() - { - GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); - } + public override void Bind() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle); + } - internal override void Unbind() - { - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - } + public override void Unbind() + { + GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + } - protected override void Destroy() - { - GL.DeleteFramebuffer(Handle); - } + protected override void Destroy() + { + GL.DeleteFramebuffer(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs b/Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs new file mode 100644 index 0000000..e1cac61 --- /dev/null +++ b/Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs @@ -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 _attachments = []; + private readonly HashSet _usedAttachments = []; + + private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0; + + public FramebufferBuilder AddColorAttachment() + 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? 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); + } +} \ No newline at end of file diff --git a/Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs b/Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs new file mode 100644 index 0000000..e95069c --- /dev/null +++ b/Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs @@ -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 + } +} \ No newline at end of file diff --git a/Engine/src/Renderer/Framebuffer/Renderbuffer.cs b/Engine/src/Renderer/Framebuffer/Renderbuffer.cs index 02857a8..98a95d8 100644 --- a/Engine/src/Renderer/Framebuffer/Renderbuffer.cs +++ b/Engine/src/Renderer/Framebuffer/Renderbuffer.cs @@ -2,48 +2,52 @@ 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 int Width { get; private set; } + public int Height { get; private set; } - private readonly RenderbufferStorage _format; + public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._renderbuffer; - public Renderbuffer(int width, int height, RenderbufferStorage format) + private readonly RenderbufferStorage _format; + + public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat) + { + Width = parWidth; + Height = parHeight; + _format = parFormat; + + GL.CreateRenderbuffers(1, out int handle); + Handle = handle; + + GL.NamedRenderbufferStorage(Handle, _format, Width, Height); + } + + public void Resize(int parWidth, int parHeight) + { + if (Width == parWidth && Height == parHeight) { - Width = width; - Height = height; - _format = format; - - GL.CreateRenderbuffers(1, out int handle); - Handle = handle; - - GL.NamedRenderbufferStorage(Handle, _format, Width, Height); + return; } - public void Resize(int width, int height) - { - if (Width == width && Height == height) - return; + Width = parWidth; + Height = parHeight; - Width = width; - Height = height; + GL.NamedRenderbufferStorage(Handle, _format, Width, Height); + } - GL.NamedRenderbufferStorage(Handle, _format, Width, Height); - } + public override void Bind() + { + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle); + } - internal override void Bind() - { - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle); - } + public override void Unbind() + { + GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); + } - internal override void Unbind() - { - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); - } - - protected override void Destroy() - { - GL.DeleteRenderbuffer(Handle); - } + protected override void Destroy() + { + GL.DeleteRenderbuffer(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/IPresenter.cs b/Engine/src/Renderer/IPresenter.cs index 8c116c8..aa435fd 100644 --- a/Engine/src/Renderer/IPresenter.cs +++ b/Engine/src/Renderer/IPresenter.cs @@ -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 Resize; + + public void Present(IConstTexture parTexture); } \ No newline at end of file diff --git a/Engine/src/Renderer/OpenGLObject.cs b/Engine/src/Renderer/OpenGLObject.cs index 57a1e65..9593b22 100644 --- a/Engine/src/Renderer/OpenGLObject.cs +++ b/Engine/src/Renderer/OpenGLObject.cs @@ -4,18 +4,18 @@ namespace Engine.Renderer; public abstract class OpenGlObject { - public int Handle { get; protected set; } = -1; + 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(); + protected abstract void Destroy(); - ~OpenGlObject() - { - Destroy(); - Handle = -1; + ~OpenGlObject() + { + Destroy(); + Handle = -1; - Log.Debug("OpenGL object {Handle} destroyed", Handle); - } + Log.Debug("OpenGL object {Handle} destroyed", Handle); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Pixel/IPixel.cs b/Engine/src/Renderer/Pixel/IPixel.cs index ba3efdc..8ad0a8f 100644 --- a/Engine/src/Renderer/Pixel/IPixel.cs +++ b/Engine/src/Renderer/Pixel/IPixel.cs @@ -4,9 +4,9 @@ namespace Engine.Renderer.Pixel; public interface IPixel { - public PixelFormat Format { get; } - public PixelType Type { get; } + public PixelFormat Format { get; } + public PixelType Type { get; } - public PixelInternalFormat InternalFormat { get; } - public SizedInternalFormat SizedInternalFormat { get; } + public PixelInternalFormat InternalFormat { get; } + public SizedInternalFormat SizedInternalFormat { get; } } \ No newline at end of file diff --git a/Engine/src/Renderer/Pixel/R8.cs b/Engine/src/Renderer/Pixel/R8.cs index 5bf4684..95fc236 100644 --- a/Engine/src/Renderer/Pixel/R8.cs +++ b/Engine/src/Renderer/Pixel/R8.cs @@ -6,11 +6,11 @@ namespace Engine.Renderer.Pixel; [StructLayout(LayoutKind.Sequential)] public struct R8 : IPixel { - public PixelFormat Format => PixelFormat.Red; - public PixelType Type => PixelType.UnsignedByte; + public PixelFormat Format => PixelFormat.Red; + public PixelType Type => PixelType.UnsignedByte; - public PixelInternalFormat InternalFormat => PixelInternalFormat.R8; - public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8; + public PixelInternalFormat InternalFormat => PixelInternalFormat.R8; + public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8; - public byte R; + public byte R; } \ No newline at end of file diff --git a/Engine/src/Renderer/Pixel/Rgb8.cs b/Engine/src/Renderer/Pixel/Rgb8.cs index 0ef5a54..9440901 100644 --- a/Engine/src/Renderer/Pixel/Rgb8.cs +++ b/Engine/src/Renderer/Pixel/Rgb8.cs @@ -6,13 +6,13 @@ namespace Engine.Renderer.Pixel; [StructLayout(LayoutKind.Sequential)] public struct Rgb8 : IPixel { - public PixelFormat Format => PixelFormat.Rgb; - public PixelType Type => PixelType.UnsignedByte; + public PixelFormat Format => PixelFormat.Rgb; + public PixelType Type => PixelType.UnsignedByte; - public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8; - public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8; + public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8; + public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8; - public byte R; - public byte G; - public byte B; + public byte R; + public byte G; + public byte B; } \ No newline at end of file diff --git a/Engine/src/Renderer/Postprocess/Pipeline.cs b/Engine/src/Renderer/Postprocess/Pipeline.cs deleted file mode 100644 index 4be4b3d..0000000 --- a/Engine/src/Renderer/Postprocess/Pipeline.cs +++ /dev/null @@ -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 _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; - } - } -} \ No newline at end of file diff --git a/Engine/src/Renderer/Renderer.cs b/Engine/src/Renderer/Renderer.cs index adb0c9a..888c86b 100644 --- a/Engine/src/Renderer/Renderer.cs +++ b/Engine/src/Renderer/Renderer.cs @@ -9,47 +9,53 @@ 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> _renderActions = new(); + private readonly Framebuffer.Framebuffer _framebuffer; + private readonly Queue> _renderActions = new(); - public Renderer(int width, int height) - { - InitializeOpenGl(); + public Renderer(int parWidth, int parHeight) + { + InitializeOpenGl(); - _framebuffer = Framebuffer.Framebuffer.Create(width, height); - } + _framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight) + .AddColorAttachment() + .AddDepthAttachment() + .Build(); + } - private void InitializeOpenGl() - { + private void InitializeOpenGl() + { #if DEBUG - Debug.Setup(); + Debug.Setup(); #endif - GL.Enable(EnableCap.DepthTest); + GL.Enable(EnableCap.DepthTest); - GL.Enable(EnableCap.FramebufferSrgb); + GL.Enable(EnableCap.FramebufferSrgb); - GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); - GL.Enable(EnableCap.Blend); - } + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + GL.Enable(EnableCap.Blend); + } - internal void Commit(Action renderAction) + internal void Commit(Action parRenderAction) + { + _renderActions.Enqueue(parRenderAction); + } + + internal void Render() + { + _framebuffer.Bind(); + while (_renderActions.TryDequeue(out Action? renderAction)) { - _renderActions.Enqueue(renderAction); + renderAction(this); } - internal void Render() - { - _framebuffer.Bind(); - while (_renderActions.TryDequeue(out var renderAction)) - renderAction(this); - _framebuffer.Unbind(); - } + _framebuffer.Unbind(); + } - internal void Resize(int width, int height) - { - _framebuffer.Resize(width, height); - } + internal void Resize(int parWidth, int parHeight) + { + _framebuffer.Resize(parWidth, parHeight); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Shader/ShaderProgram.cs b/Engine/src/Renderer/Shader/ShaderProgram.cs index 1b22aef..1f428b7 100644 --- a/Engine/src/Renderer/Shader/ShaderProgram.cs +++ b/Engine/src/Renderer/Shader/ShaderProgram.cs @@ -8,174 +8,179 @@ namespace Engine.Renderer.Shader; public class ShaderProgram : OpenGlObject { - private readonly Dictionary _uniforms = new(); + private readonly Dictionary _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 parSource.Split('\n')) { - var vertexSource = new StringBuilder(); - var fragmentSource = new StringBuilder(); - var inFragment = false; - var inVertex = false; - - foreach (var line in source.Split('\n')) - { - if (line.StartsWith("#shader vertex")) - { - inVertex = true; - inFragment = false; - } - else if (line.StartsWith("#shader fragment")) - { - inVertex = false; - inFragment = true; - } - else if (inVertex) - { - vertexSource.AppendLine(line); - } - else if (inFragment) - { - fragmentSource.AppendLine(line); - } - } - - return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString()); + if (line.StartsWith("#shader vertex")) + { + inVertex = true; + inFragment = false; + } + else if (line.StartsWith("#shader fragment")) + { + inVertex = false; + inFragment = true; + } + else if (inVertex) + { + vertexSource.AppendLine(line); + } + else if (inFragment) + { + fragmentSource.AppendLine(line); + } } - public ShaderProgram(string vertexSource, string fragmentSource) - { - var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader); - var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader); + return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString()); + } - Handle = LinkProgram(vertexShader, fragmentShader); + public ShaderProgram(string parVertexSource, string parFragmentSource) + { + var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader); + var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader); + + Handle = LinkProgram(vertexShader, fragmentShader); + } + + public void SetUniform(string parName, T parValue) + { + try + { + var location = GetUniformLocation(parName); + + switch (parValue) + { + case bool boolValue: + GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0); + break; + case int intValue: + GL.ProgramUniform1(Handle, location, intValue); + break; + case float floatValue: + GL.ProgramUniform1(Handle, location, floatValue); + break; + case Vector2 vec2: + GL.ProgramUniform2(Handle, location, vec2); + break; + case Vector3 vec3: + GL.ProgramUniform3(Handle, location, vec3); + break; + case Vector4 vec4: + GL.ProgramUniform4(Handle, location, vec4); + break; + case Matrix4 matrix: + GL.ProgramUniformMatrix4(Handle, location, false, ref matrix); + break; + default: + throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to set uniform {UniformName}", parName); + throw; + } + } + + public override void Bind() + { + GL.UseProgram(Handle); + } + + public override void Unbind() + { + GL.UseProgram(0); + } + + protected override void Destroy() + { + GL.DeleteProgram(Handle); + } + + private int GetUniformLocation(string parName) + { + if (_uniforms.TryGetValue(parName, out var location)) + { + return location; } - public void SetUniform(string name, T value, [CallerMemberName] string caller = "") + location = GL.GetUniformLocation(Handle, parName); + if (location < 0) { - try - { - var location = GetUniformLocation(name); - - switch (value) - { - case bool boolValue: - GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0); - break; - case int intValue: - GL.ProgramUniform1(Handle, location, intValue); - break; - case float floatValue: - GL.ProgramUniform1(Handle, location, floatValue); - break; - case Vector2 vec2: - GL.ProgramUniform2(Handle, location, vec2); - break; - case Vector3 vec3: - GL.ProgramUniform3(Handle, location, vec3); - break; - case Vector4 vec4: - GL.ProgramUniform4(Handle, location, vec4); - break; - case Matrix4 matrix: - GL.ProgramUniformMatrix4(Handle, location, false, ref matrix); - break; - default: - throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller); - throw; - } + throw new ArgumentException($"Uniform '{parName}' not found in shader program"); } - internal override void Bind() + _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); + if (status == 0) { - GL.UseProgram(Handle); + var log = GL.GetShaderInfoLog(shaderId); + GL.DeleteShader(shaderId); + + throw new ShaderCompilationException(parType, log); } - internal override void Unbind() + return shaderId; + } + + private static int LinkProgram(int parVertexShader, int parFragmentShader) + { + var programId = GL.CreateProgram(); + try { - GL.UseProgram(0); + GL.AttachShader(programId, parVertexShader); + GL.AttachShader(programId, parFragmentShader); + GL.LinkProgram(programId); + + GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus); + if (linkStatus == 0) + { + var log = GL.GetProgramInfoLog(programId); + throw new ShaderLinkException(log); + } + + GL.ValidateProgram(programId); + GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus); + if (validateStatus == 0) + { + var log = GL.GetProgramInfoLog(programId); + throw new ShaderValidationException(log); + } + } + finally + { + GL.DeleteShader(parVertexShader); + GL.DeleteShader(parFragmentShader); } - protected override void Destroy() - { - GL.DeleteProgram(Handle); - } - - private int GetUniformLocation(string name) - { - 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) - { - var shaderId = GL.CreateShader(type); - GL.ShaderSource(shaderId, source); - GL.CompileShader(shaderId); - - GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status); - if (status == 0) - { - var log = GL.GetShaderInfoLog(shaderId); - GL.DeleteShader(shaderId); - - throw new ShaderCompilationException(type, log); - } - - return shaderId; - } - - private static int LinkProgram(int vertexShader, int fragmentShader) - { - var programId = GL.CreateProgram(); - try - { - GL.AttachShader(programId, vertexShader); - GL.AttachShader(programId, fragmentShader); - GL.LinkProgram(programId); - - GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus); - if (linkStatus == 0) - { - var log = GL.GetProgramInfoLog(programId); - throw new ShaderLinkException(log); - } - - GL.ValidateProgram(programId); - GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus); - if (validateStatus == 0) - { - var log = GL.GetProgramInfoLog(programId); - throw new ShaderValidationException(log); - } - } - finally - { - GL.DeleteShader(vertexShader); - GL.DeleteShader(fragmentShader); - } - - return programId; - } + 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}"); \ No newline at end of file +public class ShaderValidationException(string parMessage) + : Exception($"Failed to validate shader program: {parMessage}"); \ No newline at end of file diff --git a/Engine/src/Renderer/Texture/DynamicTexture.cs b/Engine/src/Renderer/Texture/DynamicTexture.cs index 78b9c14..666c51a 100644 --- a/Engine/src/Renderer/Texture/DynamicTexture.cs +++ b/Engine/src/Renderer/Texture/DynamicTexture.cs @@ -1,43 +1,49 @@ -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 { - private readonly PixelFormat _format; - private readonly PixelType _type; - private readonly PixelInternalFormat _internalFormat; + public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._texture; - internal DynamicTexture(int width, int height, PixelFormat format, PixelType type, PixelInternalFormat internalFormat) - : base(width, height) + private readonly PixelFormat _format; + private readonly PixelType _type; + private readonly PixelInternalFormat _internalFormat; + + internal DynamicTexture(int parWidth, int parHeight, PixelFormat parFormat, PixelType parType, + PixelInternalFormat parInternalFormat) + : base(parWidth, parHeight) + { + _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(int parWidth, int parHeight) + where T : struct, IPixel + { + var pixel = default(T); + return new DynamicTexture(parWidth, parHeight, pixel.Format, pixel.Type, pixel.InternalFormat); + } + + public void Resize(int parWidth, int parHeight) + { + if (Width == parWidth && Height == parHeight) { - _format = format; - _type = type; - _internalFormat = internalFormat; - - GL.BindTexture(TextureTarget.Texture2D, Handle); - GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type, - IntPtr.Zero); + return; } - public static DynamicTexture Create(int width, int height) - where T : struct, IPixel - { - var pixel = default(T); - return new DynamicTexture(width, height, pixel.Format, pixel.Type, pixel.InternalFormat); - } + Width = parWidth; + Height = parHeight; - public void Resize(int width, int height) - { - if (Width == width && Height == height) - return; - - Width = width; - Height = height; - - Bind(); - GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type, - IntPtr.Zero); - } + Bind(); + GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type, + IntPtr.Zero); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Texture/IConstTexture.cs b/Engine/src/Renderer/Texture/IConstTexture.cs index 7e759a7..87ec8b0 100644 --- a/Engine/src/Renderer/Texture/IConstTexture.cs +++ b/Engine/src/Renderer/Texture/IConstTexture.cs @@ -5,34 +5,42 @@ namespace Engine.Renderer.Texture; public interface IConstTexture { - public int Width { get; } - public int Height { get; } + public int Width { get; } + public int Height { get; } - public void ReadPixels(int x, int y, int width, int height, T[,] pixels) - where T : struct, IPixel; + public void ReadPixels(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) + where T : struct, IPixel; } public static class ConstTextureExtensions { - public static T[,] ReadPixels(this IConstTexture texture) where T : struct, IPixel - => texture.ReadPixels(0, 0, texture.Width, texture.Height); + public static T[,] ReadPixels(this IConstTexture parTexture) where T : struct, IPixel + { + return parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height); + } - public static T[,] ReadPixels(this IConstTexture texture, int x, int y, int width, int height) - where T : struct, IPixel - { - var pixels = new T[width, height]; - texture.ReadPixels(x, y, width, height, pixels); + public static T[,] ReadPixels(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight) + where T : struct, IPixel + { + var pixels = new T[parWidth, parHeight]; + parTexture.ReadPixels(parX, parY, parWidth, parHeight, pixels); - return pixels; - } + return pixels; + } - public static void ReadPixels(this IConstTexture texture, Image image) where T : struct, IPixel - => texture.ReadPixels(0, 0, image); + public static void ReadPixels(this IConstTexture parTexture, Image parImage) where T : struct, IPixel + { + parTexture.ReadPixels(0, 0, parImage); + } - public static void ReadPixels(this IConstTexture texture, int x, int y, Image image) - where T : struct, IPixel => - texture.ReadPixels(x, y, image.Width, image.Height, image.Pixels); + public static void ReadPixels(this IConstTexture parTexture, int parX, int parY, Image parImage) + where T : struct, IPixel + { + parTexture.ReadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels); + } - public static void ReadPixels(this IConstTexture texture, T[,] pixels) where T : struct, IPixel - => texture.ReadPixels(0, 0, texture.Width, texture.Height, pixels); + public static void ReadPixels(this IConstTexture parTexture, T[,] parPixels) where T : struct, IPixel + { + parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Texture/ITexture.cs b/Engine/src/Renderer/Texture/ITexture.cs index 197a29b..124e554 100644 --- a/Engine/src/Renderer/Texture/ITexture.cs +++ b/Engine/src/Renderer/Texture/ITexture.cs @@ -5,19 +5,25 @@ namespace Engine.Renderer.Texture; public interface ITexture : IConstTexture { - public void UploadPixels(int x, int y, int width, int height, T[,] pixels) - where T : struct, IPixel; + public void UploadPixels(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) + where T : struct, IPixel; } public static class TextureExtensions { - public static void UploadPixels(this ITexture texture, Image image) where T : struct, IPixel - => texture.UploadPixels(0, 0, image); + public static void UploadPixels(this ITexture parTexture, Image parImage) where T : struct, IPixel + { + parTexture.UploadPixels(0, 0, parImage); + } - public static void UploadPixels(this ITexture texture, int x, int y, Image image) - where T : struct, IPixel => - texture.UploadPixels(x, y, image.Width, image.Height, image.Pixels); + public static void UploadPixels(this ITexture parTexture, int parX, int parY, Image parImage) + where T : struct, IPixel + { + parTexture.UploadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels); + } - public static void UploadPixels(this ITexture texture, T[,] pixels) where T : struct, IPixel - => texture.UploadPixels(0, 0, texture.Width, texture.Height, pixels); + public static void UploadPixels(this ITexture parTexture, T[,] parPixels) where T : struct, IPixel + { + parTexture.UploadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Texture/StaticTexture.cs b/Engine/src/Renderer/Texture/StaticTexture.cs index ad2cf84..f180d44 100644 --- a/Engine/src/Renderer/Texture/StaticTexture.cs +++ b/Engine/src/Renderer/Texture/StaticTexture.cs @@ -5,15 +5,15 @@ namespace Engine.Renderer.Texture; public class StaticTexture : Texture { - private StaticTexture(int width, int height, SizedInternalFormat format) : base(width, height) - { - GL.TextureStorage2D(Handle, 1, format, Width, Height); - } + private StaticTexture(int parWidth, int parHeight, SizedInternalFormat parFormat) : base(parWidth, parHeight) + { + GL.TextureStorage2D(Handle, 1, parFormat, Width, Height); + } - public static StaticTexture Create(int width, int height) - where T : struct, IPixel - { - var format = default(T).SizedInternalFormat; - return new StaticTexture(width, height, format); - } + public static StaticTexture Create(int parWidth, int parHeight) + where T : struct, IPixel + { + var format = default(T).SizedInternalFormat; + return new StaticTexture(parWidth, parHeight, format); + } } \ No newline at end of file diff --git a/Engine/src/Renderer/Texture/Texture.cs b/Engine/src/Renderer/Texture/Texture.cs index fa105a5..7324103 100644 --- a/Engine/src/Renderer/Texture/Texture.cs +++ b/Engine/src/Renderer/Texture/Texture.cs @@ -6,102 +6,123 @@ namespace Engine.Renderer.Texture; public abstract class Texture : OpenGlObject, ITexture { - public int Width + public int Width + { + get => _width; + protected set { - get => _width; - protected set - { - if (value <= 0) - throw new ArgumentException("Width must be greater than 0"); + if (value <= 0) + { + throw new ArgumentException("Width must be greater than 0"); + } - _width = value; - } + _width = value; + } + } + + public int Height + { + get => _height; + protected set + { + if (value <= 0) + { + throw new ArgumentException("Height must be greater than 0"); + } + + _height = value; + } + } + + private int _width; + private int _height; + + protected Texture(int parWidth, int parHeight) + { + Width = parWidth; + Height = parHeight; + + GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle); + Handle = handle; + } + + public void UploadPixels(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) + where T : struct, IPixel + { + if (parX < 0 || parY < 0) + { + throw new ArgumentException("x and y must be greater than 0"); } - public int Height + if (parWidth <= 0 || parHeight <= 0) { - get => _height; - protected set - { - if (value <= 0) - throw new ArgumentException("Height must be greater than 0"); - - _height = value; - } + throw new ArgumentException("Width and height must be greater than 0"); } - private int _width; - private int _height; - - protected Texture(int width, int height) + if (parX + parWidth > Width || parY + parHeight > Height) { - Width = width; - Height = height; - - GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle); - Handle = handle; + throw new ArgumentException("x + width and y + height must be less than width and height"); } - public void UploadPixels(int x, int y, int width, int height, T[,] pixels) - where T : struct, IPixel + if (parPixels.Length != parWidth * parHeight) { - if (x < 0 || y < 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); + throw new ArgumentException("Pixels array must be of size width * height"); } - public void ReadPixels(int x, int y, int width, int height, T[,] pixels) - where T : struct, IPixel + 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(int parX, int parY, int parWidth, int parHeight, T[,] parPixels) + where T : struct, IPixel + { + if (parX < 0 || parY < 0) { - if (x < 0 || y < 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 = default(T).Format; - var type = default(T).Type; - - GL.GetTextureSubImage(Handle, 0, x, y, 0, width, height, 1, format, type, pixels.Length * Marshal.SizeOf(), - pixels); + throw new ArgumentException("x and y must be greater than 0"); } - internal void BindUnit(int unit = 0) + if (parWidth <= 0 || parHeight <= 0) { - GL.BindTextureUnit(unit, Handle); + throw new ArgumentException("Width and height must be greater than 0"); } - internal override void Bind() + if (parX + parWidth > Width || parY + parHeight > Height) { - GL.BindTexture(TextureTarget.Texture2D, Handle); + throw new ArgumentException("x + width and y + height must be less than width and height"); } - internal override void Unbind() + if (parPixels.Length != parWidth * parHeight) { - GL.BindTexture(TextureTarget.Texture2D, 0); + throw new ArgumentException("Pixels array must be of size width * height"); } - protected override void Destroy() - { - GL.DeleteTexture(Handle); - } + var format = default(T).Format; + var type = default(T).Type; + + GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type, + parPixels.Length * Marshal.SizeOf(), + parPixels); + } + + public void BindUnit(int parUnit = 0) + { + GL.BindTextureUnit(parUnit, Handle); + } + + public override void Bind() + { + GL.BindTexture(TextureTarget.Texture2D, Handle); + } + + public override void Unbind() + { + GL.BindTexture(TextureTarget.Texture2D, 0); + } + + protected override void Destroy() + { + GL.DeleteTexture(Handle); + } } \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/Camera.cs b/Engine/src/Scene/Component/BuiltIn/Camera.cs index 0a47d53..1d9fc9f 100644 --- a/Engine/src/Scene/Component/BuiltIn/Camera.cs +++ b/Engine/src/Scene/Component/BuiltIn/Camera.cs @@ -4,29 +4,29 @@ 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 AspectRatio { get; private set; } = 1; + public float NearPlane { get; set; } = parNearPlane; + public float FarPlane { get; set; } = parFarPlane; - private Vector2i _screenSize = new(1, 1); + private Vector2i _screenSize = new(1, 1); - public abstract Matrix4 View { get; } - public abstract Matrix4 Projection { get; } + public abstract Matrix4 View { get; } + public abstract Matrix4 Projection { get; } - public Vector2i ScreenSize + public Vector2i ScreenSize + { + get => _screenSize; + set { - get => _screenSize; - set - { - _screenSize = value; - AspectRatio = (float)value.X / value.Y; - } + _screenSize = value; + AspectRatio = (float)value.X / value.Y; } + } - public abstract Vector3 ScreenToWorld(Vector2 screenPosition); - public abstract Vector2 WorldToScreen(Vector3 worldPosition); + public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition); + public abstract Vector2 WorldToScreen(Vector3 parWorldPosition); } \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs b/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs index ced0fcd..232bb1a 100644 --- a/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs +++ b/Engine/src/Scene/Component/BuiltIn/OrthographicCamera.cs @@ -4,42 +4,42 @@ 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(); + public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted(); - public override Matrix4 Projection => FixedAxis == Axis.X - ? Matrix4.CreateOrthographic(Size, Size / AspectRatio, NearPlane, FarPlane) - : Matrix4.CreateOrthographic(Size * AspectRatio, Size, NearPlane, FarPlane); + public override Matrix4 Projection => FixedAxis == Axis.X + ? Matrix4.CreateOrthographic(Size, Size / AspectRatio, NearPlane, FarPlane) + : Matrix4.CreateOrthographic(Size * AspectRatio, Size, NearPlane, FarPlane); - public override Vector3 ScreenToWorld(Vector2 screenPosition) - { - var offset = FixedAxis == Axis.X - ? new Vector2(Size, Size / AspectRatio) - : new Vector2(Size * AspectRatio, Size); - offset /= 2; + 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) - .MulProject(GameObject.Transform.TransformMatrix) - .Xyz; - } + return new Vector4(parScreenPosition.X - offset.X, parScreenPosition.Y - offset.Y, 0, 1) + .MulProject(GameObject.Transform.TransformMatrix) + .Xyz; + } - public override Vector2 WorldToScreen(Vector3 worldPosition) - { - throw new NotImplementedException(); - } + public override Vector2 WorldToScreen(Vector3 parWorldPosition) + { + throw new NotImplementedException(); + } - public enum Axis - { - X, - Y, - } + public enum Axis + { + X, + Y + } } \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs b/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs index a55a4d2..e26e9b4 100644 --- a/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs +++ b/Engine/src/Scene/Component/BuiltIn/PerspectiveCamera.cs @@ -4,37 +4,37 @@ 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 + public override Matrix4 View + { + get { - get - { - var transformMatrix = GameObject.Transform.TransformMatrix; - var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix); - var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix); - var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized(); + var transformMatrix = GameObject.Transform.TransformMatrix; + var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix); + var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix); + var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized(); - return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz); - } + return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz); } + } - public override Matrix4 Projection => - Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); + public override Matrix4 Projection => + Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); - public override Vector3 ScreenToWorld(Vector2 screenPosition) - { - throw new NotImplementedException(); - } + public override Vector3 ScreenToWorld(Vector2 parScreenPosition) + { + throw new NotImplementedException(); + } - public override Vector2 WorldToScreen(Vector3 worldPosition) - { - throw new NotImplementedException(); - } + public override Vector2 WorldToScreen(Vector3 parWorldPosition) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/Engine/src/Scene/Component/BuiltIn/Transform.cs b/Engine/src/Scene/Component/BuiltIn/Transform.cs index 2e48368..d8f1925 100644 --- a/Engine/src/Scene/Component/BuiltIn/Transform.cs +++ b/Engine/src/Scene/Component/BuiltIn/Transform.cs @@ -4,36 +4,31 @@ namespace Engine.Scene.Component.BuiltIn; public class Transform : Component { - public Vector3 Position { get; set; } = Vector3.Zero; - public Quaternion Rotation { get; set; } = Quaternion.Identity; - public Vector3 Scale { get; set; } = Vector3.One; - public Vector3 LocalScale { get; set; } = Vector3.One; + public Vector3 Position { get; set; } = Vector3.Zero; + public Quaternion Rotation { get; set; } = Quaternion.Identity; + public Vector3 Scale { get; set; } = Vector3.One; + public Vector3 LocalScale { get; set; } = Vector3.One; - public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) * - Matrix4.CreateFromQuaternion(Rotation) * - Matrix4.CreateTranslation(Position); + public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) * + Matrix4.CreateFromQuaternion(Rotation) * + Matrix4.CreateTranslation(Position); - public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix; + public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix; - private Matrix4 ParentTransformMatrix + private Matrix4 ParentTransformMatrix + { + get { - get - { - var parent = GameObject.Scene?.Hierarchy.GetParent(GameObject); - return parent == null ? Matrix4.Identity : parent.Transform.TransformMatrix; - } + var parent = GameObject.Scene?.Hierarchy.GetParent(GameObject); + return parent == null ? Matrix4.Identity : parent.Transform.TransformMatrix; } + } - public Transform Clone() - { - var clone = new Transform - { - Position = Position, - Rotation = Rotation, - Scale = Scale, - LocalScale = LocalScale - }; + public Transform Clone() + { + var clone = + new Transform { Position = Position, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale }; - return clone; - } + return clone; + } } \ No newline at end of file diff --git a/Engine/src/Scene/Component/Component.cs b/Engine/src/Scene/Component/Component.cs index c6d3e32..f680bff 100644 --- a/Engine/src/Scene/Component/Component.cs +++ b/Engine/src/Scene/Component/Component.cs @@ -1,45 +1,47 @@ namespace Engine.Scene.Component; -public abstract class Component +public abstract class Component : IUpdate, IRender { - public Guid Id { get; } = Guid.NewGuid(); + public Guid Id { get; } = Guid.NewGuid(); - public GameObject GameObject { get; internal set; } + 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 = parType.BaseType; + if (baseType == null || baseType == typeof(Component)) { - var baseType = type.BaseType; - if (baseType == null || baseType == typeof(Component)) - return type; - - while (baseType.BaseType != null && baseType.BaseType != typeof(Component)) - { - baseType = baseType.BaseType; - } - - return baseType; + return parType; } + + while (baseType.BaseType != null && baseType.BaseType != typeof(Component)) + { + baseType = baseType.BaseType; + } + + return baseType; + } } \ No newline at end of file diff --git a/Engine/src/Scene/GameObject.cs b/Engine/src/Scene/GameObject.cs index 2c3540c..8801c1d 100644 --- a/Engine/src/Scene/GameObject.cs +++ b/Engine/src/Scene/GameObject.cs @@ -4,133 +4,155 @@ 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; } + public Guid Id { get; } = Guid.NewGuid(); + public Transform Transform { get; } - internal Scene? Scene { get; set; } + internal Scene? Scene { get; set; } - private readonly Queue _componentActions = new(); + private readonly Queue _componentActions = new(); - private readonly IList _components = new List(); - private readonly ISet _addedComponentTypes = new HashSet(); + private readonly IList _components = new List(); + private readonly ISet _addedComponentTypes = new HashSet(); - public GameObject() + public GameObject() + { + AddComponent(); + ProcessChanges(); + + Transform = GetComponent()!; + } + + public GameObject(Transform parTransform) + { + AddComponent(parTransform.Clone()); + ProcessChanges(); + + Transform = GetComponent()!; + } + + public void Awake() + { + foreach (var component in _components) { - AddComponent(); - ProcessChanges(); + component.Awake(); + } + } - Transform = GetComponent()!; + public void Start() + { + foreach (var component in _components) + { + component.Start(); + } + } + + public void Update(double parDeltaTime) + { + foreach (var component in _components) + { + 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() where T : Component.Component + { + return !HasComponent() ? null : _components.OfType().First(); + } + + public void AddComponent() where T : Component.Component, new() + { + var component = new T(); + AddComponent(component); + } + + public void AddComponent(params object?[] parArgs) where T : Component.Component + { + var component = (T?)Activator.CreateInstance( + typeof(T), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance | + BindingFlags.OptionalParamBinding, + null, + parArgs, + null + ); + + if (component == null) + { + throw new InvalidOperationException($"Failed to create component of type {typeof(T)}"); } - public GameObject(Transform transform) - { - AddComponent(transform.Clone()); - ProcessChanges(); + AddComponent(component); + } - Transform = GetComponent()!; + public void AddComponent(T parComponent) where T : Component.Component + { + _componentActions.Enqueue(() => + { + if (HasComponent()) + { + throw new ArgumentException($"GameObject already has component of type {typeof(T)}"); + } + + parComponent.GameObject = this; + _components.Add(parComponent); + _addedComponentTypes.Add(typeof(T).GetComponentBaseType()); + }); + } + + public void RemoveComponent() where T : Component.Component + { + if (typeof(T) == typeof(Transform)) + { + throw new ArgumentException("GameObject cannot remove Transform component"); } - public void Awake() + _componentActions.Enqueue(() => { - foreach (var component in _components) - component.Awake(); - } + if (!HasComponent()) + { + throw new ArgumentException($"GameObject does not have component of type {typeof(T)}"); + } - public void Start() + var component = GetComponent(); + if (component == null) + { + return; + } + + _components.Remove(component); + _addedComponentTypes.Remove(typeof(T)); + }); + } + + public bool HasComponent() where T : Component.Component + { + var baseType = typeof(T).GetComponentBaseType(); + return _addedComponentTypes.Contains(baseType); + } + + internal void ProcessChanges() + { + while (_componentActions.TryDequeue(out var action)) { - foreach (var component in _components) - component.Start(); - } - - public void Update() - { - foreach (var component in _components) - component.Update(); - } - - public void Render() - { - foreach (var component in _components) - component.Render(); - } - - public void Destroy() - { - foreach (var component in _components) - component.Destroy(); - } - - public T? GetComponent() where T : Component.Component - { - return !HasComponent() ? null : _components.OfType().First(); - } - - public void AddComponent() where T : Component.Component, new() - { - var component = new T(); - AddComponent(component); - } - - public void AddComponent(params object?[] args) where T : Component.Component - { - var component = (T?)Activator.CreateInstance( - typeof(T), - BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance | - BindingFlags.OptionalParamBinding, - null, - args, - null - ); - - if (component == null) - throw new InvalidOperationException($"Failed to create component of type {typeof(T)}"); - - AddComponent(component); - } - - public void AddComponent(T component) where T : Component.Component - { - _componentActions.Enqueue(() => - { - if (HasComponent()) - throw new ArgumentException($"GameObject already has component of type {typeof(T)}"); - - component.GameObject = this; - _components.Add(component); - _addedComponentTypes.Add(typeof(T).GetComponentBaseType()); - }); - } - - public void RemoveComponent() where T : Component.Component - { - if (typeof(T) == typeof(Transform)) - throw new ArgumentException("GameObject cannot remove Transform component"); - - _componentActions.Enqueue(() => - { - if (!HasComponent()) - throw new ArgumentException($"GameObject does not have component of type {typeof(T)}"); - - var component = GetComponent(); - if (component == null) - return; - - _components.Remove(component); - _addedComponentTypes.Remove(typeof(T)); - }); - } - - public bool HasComponent() where T : Component.Component - { - var baseType = typeof(T).GetComponentBaseType(); - return _addedComponentTypes.Contains(baseType); - } - - internal void ProcessChanges() - { - while (_componentActions.TryDequeue(out var action)) - action(); + action(); } + } } \ No newline at end of file diff --git a/Engine/src/Scene/Hierarchy.cs b/Engine/src/Scene/Hierarchy.cs index 643babb..063118c 100644 --- a/Engine/src/Scene/Hierarchy.cs +++ b/Engine/src/Scene/Hierarchy.cs @@ -5,136 +5,156 @@ using Engine.Util; namespace Engine.Scene; public class Hierarchy : IEnumerable - where T : class + where T : class { - private readonly Dictionary, IList> _childrenLookup = new(); - private readonly Dictionary _parentLookup = new(); + private readonly Dictionary, IList> _childrenLookup = new(); + private readonly Dictionary _parentLookup = new(); - private readonly ConcurrentQueue _hierarchyActions = new(); + private readonly ConcurrentQueue _hierarchyActions = new(); - public Hierarchy() + public Hierarchy() + { + _childrenLookup.Add(new NullableObject(), new List()); + } + + internal void ProcessChanges() + { + while (_hierarchyActions.TryDequeue(out var action)) { - _childrenLookup.Add(new NullableObject(), new List()); + action(); + } + } + + public void Add(T parObj) + { + _hierarchyActions.Enqueue(() => + { + if (_parentLookup.ContainsKey(parObj)) + { + throw new ArgumentException("Object is already added to hierarchy"); + } + + _childrenLookup.Add(parObj, new List()); + _parentLookup.Add(parObj, null); + _childrenLookup[null].Add(parObj); + }); + } + + public void Remove(T parObj) + { + foreach (var child in GetChildren(parObj)) + { + Remove(child); } - internal void ProcessChanges() + _hierarchyActions.Enqueue(() => { - while (_hierarchyActions.TryDequeue(out var action)) - action(); + var parent = GetParent(parObj); + _childrenLookup[parent].Remove(parObj); + + _parentLookup.Remove(parObj); + _childrenLookup.Remove(parObj); + }); + } + + public void AddChild(T parEnt, T parChild) + { + SetParent(parChild, parEnt); + } + + private void SetParent(T parChild, T? parEnt) + { + if (parChild.Equals(parEnt)) + { + throw new InvalidOperationException("Child cannot be parent"); } - public void Add(T obj) + _hierarchyActions.Enqueue(() => { - _hierarchyActions.Enqueue(() => - { - if (_parentLookup.ContainsKey(obj)) - throw new ArgumentException("Object is already added to hierarchy"); + if (IsInHierarchy(parChild, parEnt)) + { + throw new InvalidOperationException("Parent is a child of child"); + } - _childrenLookup.Add(obj, new List()); - _parentLookup.Add(obj, null); - _childrenLookup[null].Add(obj); - }); + var oldParent = GetParent(parChild); + _childrenLookup[oldParent].Remove(parChild); + + _childrenLookup[parEnt].Add(parChild); + _parentLookup[parChild] = parEnt; + }); + } + + public bool Contains(T parObj) + { + return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj); + } + + public T? GetParent(T parChild) + { + return _parentLookup.TryGetValue(parChild, out var parent) + ? parent + : throw new InvalidOperationException($"Child {parChild} is not in hierarchy"); + } + + public IEnumerable GetChildren(T? parObj = null) + { + return _childrenLookup.TryGetValue(parObj, out IList? children) ? children : Enumerable.Empty(); + } + + 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 + { + return false; } - public void Remove(T obj) + if (parAncestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent { - foreach (var child in GetChildren(obj)) - Remove(child); - - _hierarchyActions.Enqueue(() => - { - var parent = GetParent(obj); - _childrenLookup[parent].Remove(obj); - - _parentLookup.Remove(obj); - _childrenLookup.Remove(obj); - }); + return false; } - public void AddChild(T parent, T child) + if (parAncestor.Equals(parChild)) { - SetParent(child, parent); + return true; } - private void SetParent(T child, T? parent) + var parent = GetParent(parChild); + + if (parent == null) { - if (child.Equals(parent)) - throw new InvalidOperationException("Child cannot be parent"); - - _hierarchyActions.Enqueue(() => - { - if (IsInHierarchy(child, parent)) - throw new InvalidOperationException("Parent is a child of child"); - - var oldParent = GetParent(child); - _childrenLookup[oldParent].Remove(child); - - _childrenLookup[parent].Add(child); - _parentLookup[child] = parent; - }); + return false; } - public bool Contains(T obj) + if (parAncestor.Equals(parent)) { - return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj); + return true; } - public T? GetParent(T child) + return IsInHierarchy(parAncestor, parent); + } + + public IEnumerable GetAllChildren(T? parObj = null) + { + IEnumerable? children = GetChildren(parObj); + + foreach (var child in children) { - return _parentLookup.TryGetValue(child, out var parent) - ? parent - : throw new InvalidOperationException($"Child {child} is not in hierarchy"); + yield return child; + + foreach (var descendant in GetAllChildren(child)) + { + yield return descendant; + } } + } - public IEnumerable GetChildren(T? obj = null) - { - return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty(); - } + public IEnumerator GetEnumerator() + { + return _parentLookup.Keys.GetEnumerator(); + } - public bool IsInHierarchy(T? ancestor, T? child) - { - 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 - return false; - - if (ancestor.Equals(child)) - return true; - - var parent = GetParent(child); - - if (parent == null) - return false; - - if (ancestor.Equals(parent)) - return true; - - return IsInHierarchy(ancestor, parent); - } - - public IEnumerable GetAllChildren(T? obj = null) - { - var children = GetChildren(obj); - - foreach (var child in children) - { - yield return child; - - foreach (var descendant in GetAllChildren(child)) - { - yield return descendant; - } - } - } - - public IEnumerator GetEnumerator() - { - return _parentLookup.Keys.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } \ No newline at end of file diff --git a/Engine/src/Scene/IRender.cs b/Engine/src/Scene/IRender.cs new file mode 100644 index 0000000..5add892 --- /dev/null +++ b/Engine/src/Scene/IRender.cs @@ -0,0 +1,6 @@ +namespace Engine.Scene; + +public interface IRender +{ + void Render(); +} \ No newline at end of file diff --git a/Engine/src/Scene/IUpdate.cs b/Engine/src/Scene/IUpdate.cs new file mode 100644 index 0000000..2cd9d83 --- /dev/null +++ b/Engine/src/Scene/IUpdate.cs @@ -0,0 +1,6 @@ +namespace Engine.Scene; + +public interface IUpdate +{ + void Update(double parDeltaTime); +} \ No newline at end of file diff --git a/Engine/src/Scene/Scene.cs b/Engine/src/Scene/Scene.cs index 001e78b..d5c2119 100644 --- a/Engine/src/Scene/Scene.cs +++ b/Engine/src/Scene/Scene.cs @@ -3,106 +3,120 @@ 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 bool IsPlaying { get; private set; } + public ICamera? MainCamera { get; private set; } - internal Hierarchy Hierarchy { get; } = []; + internal Hierarchy Hierarchy { get; } = []; - private readonly Queue _sceneActions = []; + private readonly Queue _sceneActions = []; - internal void Enter() + internal void Enter() + { + if (IsPlaying) { - if (IsPlaying) - throw new InvalidOperationException("Scene is already playing"); - - ProcessChanges(); - - Camera = FindFirstComponent(); - - IsPlaying = true; + throw new InvalidOperationException("Scene is already playing"); } - public T? FindFirstComponent() where T : Component.Component + ProcessChanges(); + + MainCamera = FindFirstComponent(); + + IsPlaying = true; + } + + public T? FindFirstComponent() where T : Component.Component + { + return Hierarchy.Select(parGameObject => parGameObject.GetComponent()).FirstOrDefault(); + } + + public void Update(double parDeltaTime) + { + if (!IsPlaying) { - return Hierarchy.Select(gameObject => gameObject.GetComponent()).OfType().FirstOrDefault(); + throw new InvalidOperationException("Scene is not playing"); } - internal void Update() + ProcessChanges(); + + foreach (var gameObject in Hierarchy) { - if (!IsPlaying) - throw new InvalidOperationException("Scene is not playing"); + gameObject.Update(parDeltaTime); + } + } - ProcessChanges(); - - foreach (var gameObject in Hierarchy) - gameObject.Update(); + public void Render() + { + if (!IsPlaying) + { + throw new InvalidOperationException("Scene is not playing"); } - internal void Render() + foreach (var gameObject in Hierarchy) { - if (!IsPlaying) - throw new InvalidOperationException("Scene is not playing"); + gameObject.Render(); + } + } - foreach (var gameObject in Hierarchy) - { - gameObject.Render(); - } + internal void Exit() + { + if (!IsPlaying) + { + throw new InvalidOperationException("Scene is not playing"); } - internal void Exit() + foreach (var gameObject in Hierarchy) { - if (!IsPlaying) - throw new InvalidOperationException("Scene is not playing"); - - foreach (var gameObject in Hierarchy) - { - gameObject.Destroy(); - } - - IsPlaying = false; + gameObject.Destroy(); } - public void Add(GameObject gameObject) + IsPlaying = false; + } + + public void Add(GameObject parGameObject) + { + Hierarchy.Add(parGameObject); + + _sceneActions.Enqueue(() => { - Hierarchy.Add(gameObject); + parGameObject.Scene = this; - _sceneActions.Enqueue(() => - { - gameObject.Scene = this; + parGameObject.Awake(); + parGameObject.Start(); + }); + } - gameObject.Awake(); - gameObject.Start(); - }); + public void Remove(GameObject parGameObject) + { + Hierarchy.Remove(parGameObject); + + _sceneActions.Enqueue(() => + { + foreach (var child in Hierarchy.GetAllChildren(parGameObject)) + { + child.Destroy(); + child.Scene = null; + } + + parGameObject.Destroy(); + + parGameObject.Scene = null; + }); + } + + private void ProcessChanges() + { + Hierarchy.ProcessChanges(); + + while (_sceneActions.TryDequeue(out var action)) + { + action(); } - public void Remove(GameObject gameObject) + foreach (var gameObject in Hierarchy) { - Hierarchy.Remove(gameObject); - - _sceneActions.Enqueue(() => - { - foreach (var child in Hierarchy.GetAllChildren(gameObject)) - { - child.Destroy(); - child.Scene = null; - } - - gameObject.Destroy(); - - gameObject.Scene = null; - }); - } - - private void ProcessChanges() - { - Hierarchy.ProcessChanges(); - - while (_sceneActions.TryDequeue(out var action)) - action(); - - foreach (var gameObject in Hierarchy) - gameObject.ProcessChanges(); + gameObject.ProcessChanges(); } + } } \ No newline at end of file diff --git a/Engine/src/Scene/SceneManager.cs b/Engine/src/Scene/SceneManager.cs new file mode 100644 index 0000000..62ef2ad --- /dev/null +++ b/Engine/src/Scene/SceneManager.cs @@ -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(); + } +} \ No newline at end of file diff --git a/Engine/src/Util/Math.cs b/Engine/src/Util/Math.cs index 481755a..945d266 100644 --- a/Engine/src/Util/Math.cs +++ b/Engine/src/Util/Math.cs @@ -4,12 +4,14 @@ 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 = parA * parM; + if (result.W != 0.0) { - var result = a * m; - if (result.W != 0.0) - result /= result.W; - - return result; + result /= result.W; } + + return result; + } } \ No newline at end of file diff --git a/Engine/src/Util/NullableObject.cs b/Engine/src/Util/NullableObject.cs index 25c7e24..eb52b3b 100644 --- a/Engine/src/Util/NullableObject.cs +++ b/Engine/src/Util/NullableObject.cs @@ -1,55 +1,64 @@ namespace Engine.Util; -public readonly struct NullableObject - where T : class +public readonly struct NullableObject(T? parValue) : IEquatable> + 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() : this(null) + { + } - public NullableObject() + public static implicit operator T?(NullableObject parNullableObject) + { + return parNullableObject.Value; + } + + public static implicit operator NullableObject(T? parValue) + { + return new NullableObject(parValue); + } + + public override string ToString() + { + return Value?.ToString() ?? "null"; + } + + public override bool Equals(object? parObj) + { + return parObj is NullableObject other && Equals(other); + } + + public override int GetHashCode() + { + if (IsNull) { - _value = null; + return 0; } - public NullableObject(T? value) + var hashCode = Value!.GetHashCode(); + + if (hashCode >= 0) { - _value = value; + hashCode += 1; } - public static implicit operator T?(NullableObject nullableObject) => nullableObject.Value; - public static implicit operator NullableObject(T? value) => new(value); + return hashCode; + } - public override string ToString() - { - return _value?.ToString() ?? "null"; - } + public bool Equals(NullableObject parOther) + { + return EqualityComparer.Default.Equals(Value, parOther.Value); + } - public override bool Equals(object? obj) - { - if (obj == null) - return IsNull; + public static bool operator ==(NullableObject parLeft, NullableObject parRight) + { + return parLeft.Equals(parRight); + } - if (obj is not NullableObject other) - return false; - - if (IsNull && other.IsNull) - return true; - - return _value!.Equals(other._value); - } - - public override int GetHashCode() - { - if (IsNull) - return 0; - - var hashCode = _value!.GetHashCode(); - - if (hashCode >= 0) - hashCode += 1; - - return hashCode; - } + public static bool operator !=(NullableObject parLeft, NullableObject parRight) + { + return !(parLeft == parRight); + } } \ No newline at end of file diff --git a/Engine/src/Window.cs b/Engine/src/Window.cs index d90ec3b..947fe97 100644 --- a/Engine/src/Window.cs +++ b/Engine/src/Window.cs @@ -1,85 +1,48 @@ -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; } - public int Height { get; private set; } + public bool IsExiting => _window.IsExiting; + public int Width { get; private set; } + public int Height { get; private set; } - private readonly Engine _engine; - private readonly NativeWindow _window; - private readonly bool _headless; + private readonly Engine _engine; + 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 = parEngine; + _window = parWindow; + _headless = parHeadless; + + (Width, Height) = _window.ClientSize; + + _window.MakeCurrent(); + _window.Resize += parArgs => { - _engine = engine; - _window = window; - _headless = headless; + Width = parArgs.Width; + Height = parArgs.Height; + }; + } - (Width, Height) = _window.ClientSize; - - _window.MakeCurrent(); - _window.Resize += args => - { - Width = args.Width; - Height = args.Height; - }; - } - - public void Update() + public void Update() + { + if (!_headless) { - if (!_headless) - { - NativeWindow.ProcessWindowEvents(false); - _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(); + NativeWindow.ProcessWindowEvents(false); + _window.SwapBuffers(); } + } } public static class NativeWindowExtensions { - public static unsafe void SwapBuffers(this NativeWindow window) - { - GLFW.SwapBuffers(window.WindowPtr); - } + public static unsafe void SwapBuffers(this NativeWindow parWindow) + { + GLFW.SwapBuffers(parWindow.WindowPtr); + } } \ No newline at end of file diff --git a/PresenterConsole/src/ConsoleInputHandler.cs b/PresenterConsole/src/ConsoleInputHandler.cs new file mode 100644 index 0000000..111fd3e --- /dev/null +++ b/PresenterConsole/src/ConsoleInputHandler.cs @@ -0,0 +1,129 @@ +using Engine.Input; + +namespace PresenterConsole; + +public class ConsoleInputHandler : IInputHandler +{ + private readonly HashSet _currentKeys = []; + private readonly HashSet _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}") + }; +} \ No newline at end of file diff --git a/PresenterConsole/src/ConsolePresenter.cs b/PresenterConsole/src/ConsolePresenter.cs new file mode 100644 index 0000000..6a4a194 --- /dev/null +++ b/PresenterConsole/src/ConsolePresenter.cs @@ -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? Resize; + + private readonly Framebuffer _framebuffer; + private Image _asciiImage; + + public ConsolePresenter(int width, int height) + { + _framebuffer = Framebuffer.Builder(width, height) + .AddColorAttachment() + .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(asciiTexture.Width, asciiTexture.Height); + + asciiTexture.ReadPixels(_asciiImage); + } + + public void Update(double deltaTime) + { + throw new NotImplementedException(); + } +} \ No newline at end of file