apply formatting
This commit is contained in:
282
.editorconfig
Normal file
282
.editorconfig
Normal file
@@ -0,0 +1,282 @@
|
||||
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||
root = true
|
||||
|
||||
# C# files
|
||||
[*.cs]
|
||||
|
||||
#### Core EditorConfig Options ####
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
tab_width = 2
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf
|
||||
insert_final_newline = false
|
||||
|
||||
#### .NET Coding Conventions ####
|
||||
|
||||
# Organize usings
|
||||
dotnet_separate_import_directive_groups = false
|
||||
dotnet_sort_system_directives_first = false
|
||||
file_header_template = unset
|
||||
|
||||
# this. and Me. preferences
|
||||
dotnet_style_qualification_for_event = false
|
||||
dotnet_style_qualification_for_field = false
|
||||
dotnet_style_qualification_for_method = false
|
||||
dotnet_style_qualification_for_property = false
|
||||
|
||||
# Language keywords vs BCL types preferences
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||
dotnet_style_predefined_type_for_member_access = true
|
||||
|
||||
# Parentheses preferences
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||
|
||||
# Modifier preferences
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||
|
||||
# Expression-level preferences
|
||||
dotnet_style_coalesce_expression = true
|
||||
dotnet_style_collection_initializer = true
|
||||
dotnet_style_explicit_tuple_names = true
|
||||
dotnet_style_namespace_match_folder = true
|
||||
dotnet_style_null_propagation = true
|
||||
dotnet_style_object_initializer = true
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
dotnet_style_prefer_auto_properties = true
|
||||
dotnet_style_prefer_compound_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||
dotnet_style_prefer_conditional_expression_over_return = true
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||
dotnet_style_prefer_inferred_tuple_names = true
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||
dotnet_style_prefer_simplified_interpolation = true
|
||||
|
||||
# Field preferences
|
||||
dotnet_style_readonly_field = true
|
||||
|
||||
# Parameter preferences
|
||||
dotnet_code_quality_unused_parameters = all
|
||||
|
||||
# Suppression preferences
|
||||
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||
|
||||
# New line preferences
|
||||
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||
|
||||
#### C# Coding Conventions ####
|
||||
|
||||
# var preferences
|
||||
csharp_style_var_elsewhere = true
|
||||
csharp_style_var_for_built_in_types = true
|
||||
csharp_style_var_when_type_is_apparent = true
|
||||
|
||||
# Expression-bodied members
|
||||
csharp_style_expression_bodied_accessors = true
|
||||
csharp_style_expression_bodied_constructors = false
|
||||
csharp_style_expression_bodied_indexers = true
|
||||
csharp_style_expression_bodied_lambdas = true
|
||||
csharp_style_expression_bodied_local_functions = false
|
||||
csharp_style_expression_bodied_methods = false
|
||||
csharp_style_expression_bodied_operators = false
|
||||
csharp_style_expression_bodied_properties = true
|
||||
|
||||
# Pattern matching preferences
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||
csharp_style_prefer_not_pattern = true
|
||||
csharp_style_prefer_pattern_matching = true
|
||||
csharp_style_prefer_switch_expression = true
|
||||
|
||||
# Null-checking preferences
|
||||
csharp_style_conditional_delegate_call = true
|
||||
|
||||
# Modifier preferences
|
||||
csharp_prefer_static_local_function = true
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
|
||||
|
||||
# Code-block preferences
|
||||
csharp_prefer_braces = true
|
||||
csharp_prefer_simple_using_statement = true
|
||||
|
||||
# Expression-level preferences
|
||||
csharp_prefer_simple_default_expression = true
|
||||
csharp_style_deconstructed_variable_declaration = true
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||
csharp_style_inlined_variable_declaration = true
|
||||
csharp_style_pattern_local_over_anonymous_function = true
|
||||
csharp_style_prefer_index_operator = true
|
||||
csharp_style_prefer_range_operator = true
|
||||
csharp_style_throw_expression = true
|
||||
csharp_style_unused_value_assignment_preference = discard_variable
|
||||
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace
|
||||
|
||||
# New line preferences
|
||||
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
# New line preferences
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = false
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.delegate_should_be_begins_with_d.severity = suggestion
|
||||
dotnet_naming_rule.delegate_should_be_begins_with_d.symbols = delegate
|
||||
dotnet_naming_rule.delegate_should_be_begins_with_d.style = begins_with_d
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.constant_should_be_constant_style.severity = suggestion
|
||||
dotnet_naming_rule.constant_should_be_constant_style.symbols = constant
|
||||
dotnet_naming_rule.constant_should_be_constant_style.style = constant_style
|
||||
|
||||
dotnet_naming_rule.all_fields_should_be_fields_style.severity = suggestion
|
||||
dotnet_naming_rule.all_fields_should_be_fields_style.symbols = all_fields
|
||||
dotnet_naming_rule.all_fields_should_be_fields_style.style = fields_style
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.parameter_should_be_begins_with_par.severity = suggestion
|
||||
dotnet_naming_rule.parameter_should_be_begins_with_par.symbols = parameter
|
||||
dotnet_naming_rule.parameter_should_be_begins_with_par.style = begins_with_par
|
||||
|
||||
dotnet_naming_rule.local_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.local_should_be_camel_case.symbols = local
|
||||
dotnet_naming_rule.local_should_be_camel_case.style = camel_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.delegate.applicable_kinds = delegate
|
||||
dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.delegate.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.all_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.all_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.all_fields.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.parameter.applicable_kinds = parameter
|
||||
dotnet_naming_symbols.parameter.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.parameter.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.constant.applicable_kinds = field
|
||||
dotnet_naming_symbols.constant.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.constant.required_modifiers = const
|
||||
|
||||
dotnet_naming_symbols.local.applicable_kinds = local
|
||||
dotnet_naming_symbols.local.applicable_accessibilities = *
|
||||
dotnet_naming_symbols.local.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.camel_case.required_prefix =
|
||||
dotnet_naming_style.camel_case.required_suffix =
|
||||
dotnet_naming_style.camel_case.word_separator =
|
||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.fields_style.required_prefix = _
|
||||
dotnet_naming_style.fields_style.required_suffix =
|
||||
dotnet_naming_style.fields_style.word_separator =
|
||||
dotnet_naming_style.fields_style.capitalization = camel_case
|
||||
|
||||
dotnet_naming_style.constant_style.required_prefix =
|
||||
dotnet_naming_style.constant_style.required_suffix =
|
||||
dotnet_naming_style.constant_style.word_separator = _
|
||||
dotnet_naming_style.constant_style.capitalization = all_upper
|
||||
|
||||
dotnet_naming_style.begins_with_d.required_prefix = d
|
||||
dotnet_naming_style.begins_with_d.required_suffix =
|
||||
dotnet_naming_style.begins_with_d.word_separator =
|
||||
dotnet_naming_style.begins_with_d.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.begins_with_par.required_prefix = par
|
||||
dotnet_naming_style.begins_with_par.required_suffix =
|
||||
dotnet_naming_style.begins_with_par.word_separator =
|
||||
dotnet_naming_style.begins_with_par.capitalization = pascal_case
|
||||
@@ -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
|
||||
|
||||
@@ -3,16 +3,16 @@ using Engine.Renderer.Texture;
|
||||
|
||||
namespace Engine.Asset;
|
||||
|
||||
public class Image<T>(T[,] pixels)
|
||||
public class Image<T>(T[,] parPixels)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
public int Width { get; } = pixels.GetLength(0);
|
||||
public int Height { get; } = pixels.GetLength(1);
|
||||
public T[,] Pixels { get; } = pixels;
|
||||
public int Width { get; } = parPixels.GetLength(0);
|
||||
public int Height { get; } = parPixels.GetLength(1);
|
||||
public T[,] Pixels { get; } = parPixels;
|
||||
|
||||
public T this[int x, int y] => Pixels[x, y];
|
||||
public T this[int parX, int parY] => Pixels[parX, parY];
|
||||
|
||||
public Image(int width, int height) : this(new T[width, height])
|
||||
public Image(int parWidth, int parHeight) : this(new T[parWidth, parHeight])
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
public interface IMeshLoader
|
||||
{
|
||||
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default);
|
||||
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default);
|
||||
|
||||
public static Mesh Optimize(Mesh mesh)
|
||||
public static Mesh Optimize(Mesh parMesh)
|
||||
{
|
||||
var optimizedMesh = new Mesh();
|
||||
var vertexMap = new Dictionary<Mesh.Vertex, uint>();
|
||||
uint index = 0;
|
||||
|
||||
foreach (var vertex in mesh.Vertices)
|
||||
foreach (var vertex in parMesh.Vertices)
|
||||
{
|
||||
if (vertexMap.TryGetValue(vertex, out var existingIndex))
|
||||
{
|
||||
optimizedMesh.IndicesInternal.Add(existingIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
vertexMap.Add(vertex, index);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -5,12 +5,14 @@ namespace Engine.Asset.Mesh.Loader;
|
||||
|
||||
public class ObjMeshLoader : IMeshLoader
|
||||
{
|
||||
private static readonly ObjMeshLoader Instance = new();
|
||||
private static readonly ObjMeshLoader _instance = new();
|
||||
|
||||
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||
=> Instance.LoadMesh(path, parameters);
|
||||
public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
|
||||
{
|
||||
return _instance.LoadMesh(parPath, parAmeters);
|
||||
}
|
||||
|
||||
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
|
||||
@@ -19,16 +21,18 @@ public class ObjMeshLoader : IMeshLoader
|
||||
var tempUVs = new List<Vector2>();
|
||||
var index = 0u;
|
||||
|
||||
var loadNormals = parameters.HasFlag(MeshLoaderParameters.LoadNormals);
|
||||
var loadUVs = parameters.HasFlag(MeshLoaderParameters.LoadUVs);
|
||||
var loadNormals = parAmeters.HasFlag(MeshLoaderParameters._loadNormals);
|
||||
var loadUVs = parAmeters.HasFlag(MeshLoaderParameters._loadUVs);
|
||||
|
||||
using var reader = new StreamReader(path);
|
||||
using var reader = new StreamReader(parPath);
|
||||
while (reader.ReadLine() is { } line)
|
||||
{
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 0 || parts[0].StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (parts[0])
|
||||
{
|
||||
@@ -58,20 +62,17 @@ public class ObjMeshLoader : IMeshLoader
|
||||
case "f":
|
||||
for (var i = 1; i <= 3; i++)
|
||||
{
|
||||
var faceComponents = parts[i].Split('/');
|
||||
var meshVertex = new Mesh.Vertex
|
||||
{
|
||||
Position = tempVertices[int.Parse(faceComponents[0]) - 1]
|
||||
};
|
||||
string[]? faceComponents = parts[i].Split('/');
|
||||
var meshVertex = new Mesh.Vertex { _position = tempVertices[int.Parse(faceComponents[0]) - 1] };
|
||||
|
||||
if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "")
|
||||
{
|
||||
meshVertex.Uv = tempUVs[int.Parse(faceComponents[1]) - 1];
|
||||
meshVertex._uv = tempUVs[int.Parse(faceComponents[1]) - 1];
|
||||
}
|
||||
|
||||
if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "")
|
||||
{
|
||||
meshVertex.Normal = tempNormals[int.Parse(faceComponents[2]) - 1];
|
||||
meshVertex._normal = tempNormals[int.Parse(faceComponents[2]) - 1];
|
||||
}
|
||||
|
||||
mesh.VerticesInternal.Add(meshVertex);
|
||||
@@ -82,8 +83,10 @@ public class ObjMeshLoader : IMeshLoader
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
|
||||
if (parAmeters.HasFlag(MeshLoaderParameters._optimize))
|
||||
{
|
||||
mesh = IMeshLoader.Optimize(mesh);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@@ -5,34 +5,41 @@ namespace Engine.Asset.Mesh.Loader;
|
||||
|
||||
public class StlMeshLoader : IMeshLoader
|
||||
{
|
||||
private static readonly StlMeshLoader Instance = new();
|
||||
private static readonly StlMeshLoader _instance = new();
|
||||
|
||||
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||
=> Instance.LoadMesh(path, parameters);
|
||||
public static Mesh Load(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
|
||||
{
|
||||
return _instance.LoadMesh(parPath, parAmeters);
|
||||
}
|
||||
|
||||
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||
public Mesh LoadMesh(string parPath, MeshLoaderParameters parAmeters = MeshLoaderParameters._default)
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
|
||||
var currentNormal = new Vector3();
|
||||
var index = 0u;
|
||||
|
||||
using var reader = new StreamReader(path);
|
||||
using var reader = new StreamReader(parPath);
|
||||
while (reader.ReadLine() is { } line)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("solid") || line.StartsWith("outer loop") ||
|
||||
line.StartsWith("endloop"))
|
||||
{
|
||||
continue;
|
||||
if (line.StartsWith("endsolid"))
|
||||
break;
|
||||
}
|
||||
|
||||
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (line.StartsWith("endsolid"))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string[]? parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
switch (parts[0])
|
||||
{
|
||||
case "facet" when parts[1] == "normal" && parameters.HasFlag(MeshLoaderParameters.LoadNormals):
|
||||
case "facet" when parts[1] == "normal" && parAmeters.HasFlag(MeshLoaderParameters._loadNormals):
|
||||
currentNormal = new Vector3(
|
||||
float.Parse(parts[2], CultureInfo.InvariantCulture),
|
||||
float.Parse(parts[3], CultureInfo.InvariantCulture),
|
||||
@@ -47,19 +54,17 @@ public class StlMeshLoader : IMeshLoader
|
||||
float.Parse(parts[3], CultureInfo.InvariantCulture)
|
||||
);
|
||||
|
||||
mesh.VerticesInternal.Add(new Mesh.Vertex
|
||||
{
|
||||
Position = vertex,
|
||||
Normal = currentNormal
|
||||
});
|
||||
mesh.VerticesInternal.Add(new Mesh.Vertex { _position = vertex, _normal = currentNormal });
|
||||
mesh.IndicesInternal.Add(index++);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
|
||||
if (parAmeters.HasFlag(MeshLoaderParameters._optimize))
|
||||
{
|
||||
mesh = IMeshLoader.Optimize(mesh);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
@@ -17,13 +17,10 @@ public class Mesh
|
||||
|
||||
public record struct Vertex : IVertex
|
||||
{
|
||||
[Vertex(VertexAttribType.Float, 3)]
|
||||
public Vector3 Position;
|
||||
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
|
||||
|
||||
[Vertex(VertexAttribType.Float, 3)]
|
||||
public Vector3 Normal;
|
||||
[Vertex(VertexAttribType.Float, 3)] public Vector3 _normal;
|
||||
|
||||
[Vertex(VertexAttribType.Float, 2)]
|
||||
public Vector2 Uv;
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using System.Diagnostics;
|
||||
using Engine.Input;
|
||||
using Engine.Scene;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
@@ -10,24 +13,29 @@ namespace Engine;
|
||||
public sealed class Engine
|
||||
{
|
||||
public Renderer.Renderer Renderer => _renderer;
|
||||
public SceneManager SceneManager => _sceneManager;
|
||||
|
||||
private readonly Window _window;
|
||||
private readonly Renderer.Renderer _renderer;
|
||||
private readonly SceneManager _sceneManager = new();
|
||||
private readonly ILogger _logger;
|
||||
private readonly IInputHandler _inputHandler;
|
||||
|
||||
public Engine(int width, int height, bool headless = false, string title = "")
|
||||
private Thread? _updateThread;
|
||||
|
||||
public Engine(int parWidth, int parHeight, bool parHeadless = false, string parTitle = "")
|
||||
{
|
||||
var settings = new NativeWindowSettings
|
||||
{
|
||||
ClientSize = headless ? new Vector2i(1, 1) : new Vector2i(width, height),
|
||||
Title = title,
|
||||
StartVisible = !headless,
|
||||
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
|
||||
Title = parTitle,
|
||||
StartVisible = !parHeadless,
|
||||
APIVersion = new Version(4, 5),
|
||||
Profile = ContextProfile.Compatability
|
||||
};
|
||||
|
||||
_window = new Window(this, new NativeWindow(settings), headless);
|
||||
_renderer = new Renderer.Renderer(width, height);
|
||||
_window = new Window(this, new NativeWindow(settings), parHeadless);
|
||||
_renderer = new Renderer.Renderer(parWidth, parHeight);
|
||||
|
||||
Thread.CurrentThread.Name = "RendererThread";
|
||||
|
||||
@@ -47,6 +55,9 @@ public sealed class Engine
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_updateThread = new Thread(RunUpdate);
|
||||
_updateThread.Start();
|
||||
|
||||
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
|
||||
|
||||
GL.Viewport(0, 0, _window.Width, _window.Height);
|
||||
@@ -55,5 +66,18 @@ public sealed class Engine
|
||||
{
|
||||
_window.Update();
|
||||
}
|
||||
|
||||
_updateThread.Join();
|
||||
}
|
||||
|
||||
private void RunUpdate()
|
||||
{
|
||||
var timer = Stopwatch.StartNew();
|
||||
while (!_window.IsExiting)
|
||||
{
|
||||
_window.Update();
|
||||
_sceneManager.Update(timer.Elapsed.TotalSeconds);
|
||||
timer.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
52
Engine/src/Input/KeyCode.cs
Normal file
52
Engine/src/Input/KeyCode.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace Engine.Input;
|
||||
|
||||
public enum KeyCode
|
||||
{
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
|
||||
Ctrl,
|
||||
Alt,
|
||||
Shift,
|
||||
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
Escape,
|
||||
Enter,
|
||||
Space,
|
||||
Tab,
|
||||
Backspace,
|
||||
Delete,
|
||||
Insert,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
public enum MouseButton
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
_left,
|
||||
_right,
|
||||
_middle,
|
||||
|
||||
Button4,
|
||||
Button5,
|
||||
Button6,
|
||||
Button7,
|
||||
_button4,
|
||||
_button5,
|
||||
_button6,
|
||||
_button7
|
||||
}
|
||||
@@ -7,40 +7,44 @@ public class IndexBuffer : OpenGlObject
|
||||
{
|
||||
internal int Count { get; }
|
||||
|
||||
public IndexBuffer(int count, BufferStorageFlags flags)
|
||||
public IndexBuffer(int parCount, BufferStorageFlags parFlags)
|
||||
{
|
||||
Count = count;
|
||||
Count = parCount;
|
||||
|
||||
GL.CreateBuffers(1, out int handle);
|
||||
Handle = handle;
|
||||
|
||||
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, flags);
|
||||
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, parFlags);
|
||||
|
||||
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
|
||||
}
|
||||
|
||||
public void UploadData(uint[] data)
|
||||
public void UploadData(uint[] parData)
|
||||
{
|
||||
UploadData(0, data);
|
||||
UploadData(0, parData);
|
||||
}
|
||||
|
||||
public void UploadData(int offset, uint[] data)
|
||||
public void UploadData(int parOffset, uint[] parData)
|
||||
{
|
||||
if (parOffset < 0)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentException("Offset must be greater than 0");
|
||||
|
||||
if (data.Length + offset > Count)
|
||||
throw new ArgumentException("Data array is too large");
|
||||
|
||||
GL.NamedBufferSubData(Handle, offset, data.Length * sizeof(uint), data);
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
if (parData.Length + parOffset > Count)
|
||||
{
|
||||
throw new ArgumentException("Data array is too large");
|
||||
}
|
||||
|
||||
GL.NamedBufferSubData(Handle, parOffset, parData.Length * sizeof(uint), parData);
|
||||
}
|
||||
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
|
||||
}
|
||||
|
||||
@@ -6,38 +6,53 @@ namespace Engine.Renderer.Buffer.Vertex;
|
||||
|
||||
public interface IVertex
|
||||
{
|
||||
public static IOrderedEnumerable<FieldInfo> GetFields<T>() => GetFields(typeof(T));
|
||||
|
||||
public static IOrderedEnumerable<FieldInfo> GetFields(Type type) =>
|
||||
type.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(f => f.MetadataToken);
|
||||
|
||||
public static bool IsValid(Type type)
|
||||
public static IOrderedEnumerable<FieldInfo> GetFields<T>()
|
||||
{
|
||||
if (!type.IsValueType || !type.IsAssignableTo(typeof(IVertex)))
|
||||
return false;
|
||||
return GetFields(typeof(T));
|
||||
}
|
||||
|
||||
var fields = GetFields(type);
|
||||
public static IOrderedEnumerable<FieldInfo> GetFields(Type parType)
|
||||
{
|
||||
return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken);
|
||||
}
|
||||
|
||||
public static bool IsValid(Type parType)
|
||||
{
|
||||
if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IOrderedEnumerable<FieldInfo>? fields = GetFields(parType);
|
||||
var totalSize = 0;
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (!field.FieldType.IsValueType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var attribute = field.GetCustomAttribute<VertexAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var size = AttributeSize(attribute.Type) * attribute.ComponentCount * attribute.RepeatCount;
|
||||
if (size != Marshal.SizeOf(field.FieldType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
totalSize += size;
|
||||
}
|
||||
|
||||
return totalSize == Marshal.SizeOf(type);
|
||||
return totalSize == Marshal.SizeOf(parType);
|
||||
}
|
||||
|
||||
public static int AttributeSize(VertexAttribType type) => type switch
|
||||
public static int AttributeSize(VertexAttribType parType)
|
||||
{
|
||||
return parType switch
|
||||
{
|
||||
VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte),
|
||||
VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short),
|
||||
@@ -47,4 +62,5 @@ public interface IVertex
|
||||
VertexAttribType.Double => sizeof(double),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,22 @@ public class VertexAttribute : Attribute
|
||||
public bool Normalized { get; }
|
||||
public int RepeatCount { get; }
|
||||
|
||||
public VertexAttribute(VertexAttribType type, int componentCount = 1, bool normalized = false, int repeatCount = 1)
|
||||
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
|
||||
int parRepeatCount = 1)
|
||||
{
|
||||
if (parComponentCount <= 0)
|
||||
{
|
||||
if (componentCount <= 0)
|
||||
throw new ArgumentException("Count must be greater than 0");
|
||||
}
|
||||
|
||||
if (repeatCount <= 0)
|
||||
if (parRepeatCount <= 0)
|
||||
{
|
||||
throw new ArgumentException("Repeat must be greater than 0");
|
||||
}
|
||||
|
||||
Type = type;
|
||||
ComponentCount = componentCount;
|
||||
Normalized = normalized;
|
||||
RepeatCount = repeatCount;
|
||||
Type = parType;
|
||||
ComponentCount = parComponentCount;
|
||||
Normalized = parNormalized;
|
||||
RepeatCount = parRepeatCount;
|
||||
}
|
||||
}
|
||||
@@ -17,26 +17,30 @@ public class VertexArray : OpenGlObject
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public void BindIndexBuffer(IndexBuffer buffer)
|
||||
public void BindIndexBuffer(IndexBuffer parBuffer)
|
||||
{
|
||||
GL.VertexArrayElementBuffer(Handle, buffer.Handle);
|
||||
GL.VertexArrayElementBuffer(Handle, parBuffer.Handle);
|
||||
|
||||
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, buffer.Handle);
|
||||
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, parBuffer.Handle);
|
||||
}
|
||||
|
||||
public void BindVertexBuffer<T>(VertexBuffer<T> buffer, int bindingIndex = 0, int divisor = 0)
|
||||
public void BindVertexBuffer<T>(VertexBuffer<T> parBuffer, int parBindingIndex = 0, int parDivisor = 0)
|
||||
where T : struct, IVertex
|
||||
{
|
||||
if (bindingIndex < 0)
|
||||
if (parBindingIndex < 0)
|
||||
{
|
||||
throw new ArgumentException("Binding index must be greater than 0");
|
||||
}
|
||||
|
||||
if (divisor < 0)
|
||||
if (parDivisor < 0)
|
||||
{
|
||||
throw new ArgumentException("Divisor must be greater than 0");
|
||||
}
|
||||
|
||||
var stride = Marshal.SizeOf<T>();
|
||||
var fields = IVertex.GetFields<T>();
|
||||
IOrderedEnumerable<FieldInfo>? fields = IVertex.GetFields<T>();
|
||||
|
||||
GL.VertexArrayVertexBuffer(Handle, bindingIndex, buffer.Handle, 0, stride);
|
||||
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
|
||||
|
||||
var location = 0;
|
||||
foreach (var field in fields)
|
||||
@@ -44,40 +48,41 @@ public class VertexArray : OpenGlObject
|
||||
var attribute = field.GetCustomAttribute<VertexAttribute>()!;
|
||||
|
||||
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
|
||||
SetupAttribute(attribute, location, offset, bindingIndex);
|
||||
SetupAttribute(attribute, location, offset, parBindingIndex);
|
||||
|
||||
location += attribute.RepeatCount;
|
||||
}
|
||||
|
||||
GL.VertexArrayBindingDivisor(Handle, bindingIndex, divisor);
|
||||
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);
|
||||
|
||||
Log.Debug(
|
||||
"Vertex array {Handle} bound to vertex buffer {Buffer} at {BindingIndex} binding with {Divisor} divisor",
|
||||
Handle, buffer.Handle, bindingIndex, divisor);
|
||||
Handle, parBuffer.Handle, parBindingIndex, parDivisor);
|
||||
}
|
||||
|
||||
private void SetupAttribute(VertexAttribute attribute, int baseLocation, int baseOffset, int bindingIndex)
|
||||
private void SetupAttribute(VertexAttribute parAttribute, int parBaseLocation, int parBaseOffset, int parBindingIndex)
|
||||
{
|
||||
var size = attribute.ComponentCount * IVertex.AttributeSize(attribute.Type);
|
||||
var size = parAttribute.ComponentCount * IVertex.AttributeSize(parAttribute.Type);
|
||||
|
||||
for (var i = 0; i < attribute.RepeatCount; i++)
|
||||
for (var i = 0; i < parAttribute.RepeatCount; i++)
|
||||
{
|
||||
var location = baseLocation + i;
|
||||
var offset = baseOffset + i * size;
|
||||
var location = parBaseLocation + i;
|
||||
var offset = parBaseOffset + (i * size);
|
||||
|
||||
GL.EnableVertexArrayAttrib(Handle, location);
|
||||
GL.VertexArrayAttribFormat(Handle, location, attribute.ComponentCount, attribute.Type, attribute.Normalized,
|
||||
GL.VertexArrayAttribFormat(Handle, location, parAttribute.ComponentCount, parAttribute.Type,
|
||||
parAttribute.Normalized,
|
||||
offset);
|
||||
GL.VertexArrayAttribBinding(Handle, location, bindingIndex);
|
||||
GL.VertexArrayAttribBinding(Handle, location, parBindingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindVertexArray(Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindVertexArray(0);
|
||||
}
|
||||
|
||||
@@ -12,46 +12,54 @@ public class VertexBuffer<T> : OpenGlObject
|
||||
|
||||
private readonly int _stride = Marshal.SizeOf<T>();
|
||||
|
||||
public VertexBuffer(int count, BufferStorageFlags flags)
|
||||
public VertexBuffer(int parCount, BufferStorageFlags parFlags)
|
||||
{
|
||||
if (!IVertex.IsValid(typeof(T)))
|
||||
{
|
||||
throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
|
||||
}
|
||||
|
||||
if (count <= 0)
|
||||
if (parCount <= 0)
|
||||
{
|
||||
throw new ArgumentException("Count must be greater than 0");
|
||||
}
|
||||
|
||||
Count = count;
|
||||
Count = parCount;
|
||||
|
||||
GL.CreateBuffers(1, out int handle);
|
||||
Handle = handle;
|
||||
|
||||
GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, flags);
|
||||
GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, parFlags);
|
||||
|
||||
Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
|
||||
}
|
||||
|
||||
public void UploadData(T[] data)
|
||||
public void UploadData(T[] parData)
|
||||
{
|
||||
UploadData(0, data);
|
||||
UploadData(0, parData);
|
||||
}
|
||||
|
||||
public void UploadData(int offset, T[] data)
|
||||
public void UploadData(int parOffset, T[] parData)
|
||||
{
|
||||
if (parOffset < 0)
|
||||
{
|
||||
if (offset < 0)
|
||||
throw new ArgumentException("Offset must be greater than 0");
|
||||
|
||||
if (data.Length + offset > Count)
|
||||
throw new ArgumentException("Data array is too large");
|
||||
|
||||
GL.NamedBufferSubData(Handle, offset * _stride, data.Length * _stride, data);
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
if (parData.Length + parOffset > Count)
|
||||
{
|
||||
throw new ArgumentException("Data array is too large");
|
||||
}
|
||||
|
||||
GL.NamedBufferSubData(Handle, parOffset * _stride, parData.Length * _stride, parData);
|
||||
}
|
||||
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||
}
|
||||
|
||||
@@ -13,14 +13,15 @@ internal static class Debug
|
||||
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private static void DebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length,
|
||||
IntPtr message, IntPtr userParam)
|
||||
private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity,
|
||||
int parLength,
|
||||
IntPtr parMessage, IntPtr parUserParam)
|
||||
{
|
||||
var logger = Log.ForContext<Engine>();
|
||||
|
||||
var messageText = Marshal.PtrToStringAnsi(message, length) ?? "Unknown OpenGL Error";
|
||||
var messageText = Marshal.PtrToStringAnsi(parMessage, parLength) ?? "Unknown OpenGL Error";
|
||||
|
||||
var typePrefix = type switch
|
||||
var typePrefix = parType switch
|
||||
{
|
||||
DebugType.DebugTypeError => "Error",
|
||||
DebugType.DebugTypeDeprecatedBehavior => "Deprecated",
|
||||
@@ -34,7 +35,7 @@ internal static class Debug
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
var sourcePrefix = source switch
|
||||
var sourcePrefix = parSource switch
|
||||
{
|
||||
DebugSource.DebugSourceApi => "API",
|
||||
DebugSource.DebugSourceWindowSystem => "Window",
|
||||
@@ -45,18 +46,18 @@ internal static class Debug
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
logger.Write(GetLogLevel(severity),
|
||||
logger.Write(GetLogLevel(parSeverity),
|
||||
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
|
||||
typePrefix,
|
||||
sourcePrefix,
|
||||
messageText,
|
||||
id
|
||||
parId
|
||||
);
|
||||
}
|
||||
|
||||
private static LogEventLevel GetLogLevel(DebugSeverity severity)
|
||||
private static LogEventLevel GetLogLevel(DebugSeverity parSeverity)
|
||||
{
|
||||
return severity switch
|
||||
return parSeverity switch
|
||||
{
|
||||
DebugSeverity.DebugSeverityNotification => LogEventLevel.Information,
|
||||
DebugSeverity.DebugSeverityHigh => LogEventLevel.Error,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Engine.Renderer.Pixel;
|
||||
using System.Collections.ObjectModel;
|
||||
using Engine.Renderer.Pixel;
|
||||
using Engine.Renderer.Texture;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
@@ -12,7 +13,9 @@ public class Framebuffer : OpenGlObject
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentException("Width must be greater than 0");
|
||||
}
|
||||
|
||||
_width = value;
|
||||
}
|
||||
@@ -24,70 +27,93 @@ public class Framebuffer : OpenGlObject
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentException("Height must be greater than 0");
|
||||
}
|
||||
|
||||
_height = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IConstTexture Texture => _texture;
|
||||
internal Texture.Texture TextureInternal => _texture;
|
||||
internal Texture.Texture? TextureInternal => GetAttachment<DynamicTexture>(FramebufferAttachment.ColorAttachment0);
|
||||
|
||||
private readonly IDictionary<FramebufferAttachment, IFramebufferAttachment> _attachments;
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
private readonly DynamicTexture _texture;
|
||||
private readonly Renderbuffer? _renderbuffer;
|
||||
|
||||
public Framebuffer(int width, int height, PixelFormat format, PixelType type, PixelInternalFormat internalFormat,
|
||||
bool createRenderbuffer = true)
|
||||
public Framebuffer(int parWidth, int parHeight,
|
||||
IDictionary<FramebufferAttachment, IFramebufferAttachment> parAttachments)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
_attachments = new ReadOnlyDictionary<FramebufferAttachment, IFramebufferAttachment>(parAttachments);
|
||||
|
||||
GL.CreateFramebuffers(1, out int handle);
|
||||
Handle = handle;
|
||||
|
||||
_texture = new DynamicTexture(width, height, format, type, internalFormat);
|
||||
GL.NamedFramebufferTexture(Handle, FramebufferAttachment.ColorAttachment0, _texture.Handle, 0);
|
||||
|
||||
if (createRenderbuffer)
|
||||
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
|
||||
{
|
||||
_renderbuffer = new Renderbuffer(width, height, RenderbufferStorage.Depth24Stencil8);
|
||||
GL.NamedFramebufferRenderbuffer(Handle, FramebufferAttachment.DepthStencilAttachment,
|
||||
RenderbufferTarget.Renderbuffer, _renderbuffer.Handle);
|
||||
switch (attachment.Value.Type)
|
||||
{
|
||||
case IFramebufferAttachment.AttachmentType._texture:
|
||||
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
|
||||
break;
|
||||
case IFramebufferAttachment.AttachmentType._renderbuffer:
|
||||
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
|
||||
attachment.Value.Handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer);
|
||||
if (status != FramebufferStatus.FramebufferComplete)
|
||||
{
|
||||
throw new Exception($"Framebuffer is not complete: {status}");
|
||||
}
|
||||
|
||||
public static Framebuffer Create<T>(int width, int height, bool createRenderbuffer = true)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
var pixel = default(T);
|
||||
return new Framebuffer(width, height, pixel.Format, pixel.Type, pixel.InternalFormat, createRenderbuffer);
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
public static FramebufferBuilder Builder(int parWidth, int parHeight)
|
||||
{
|
||||
return new FramebufferBuilder(parWidth, parHeight);
|
||||
}
|
||||
|
||||
public T? GetAttachment<T>(FramebufferAttachment parAttachment) where T : IFramebufferAttachment
|
||||
{
|
||||
if (!_attachments.TryGetValue(parAttachment, out var attachmentValue))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (attachmentValue is T t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Resize(int parWidth, int parHeight)
|
||||
{
|
||||
if (Width == parWidth && Height == parHeight)
|
||||
{
|
||||
if (Width == width && Height == height)
|
||||
return;
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
_texture.Resize(width, height);
|
||||
_renderbuffer?.Resize(width, height);
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
|
||||
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
|
||||
{
|
||||
attachment.Value.Resize(parWidth, parHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
}
|
||||
|
||||
95
Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs
Normal file
95
Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Engine.Renderer.Pixel;
|
||||
using Engine.Renderer.Texture;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Engine.Renderer.Framebuffer;
|
||||
|
||||
public class FramebufferBuilder(int parWidth, int parHeight)
|
||||
{
|
||||
private readonly List<AttachmentSpecification> _attachments = [];
|
||||
private readonly HashSet<FramebufferAttachment> _usedAttachments = [];
|
||||
|
||||
private FramebufferAttachment _currentColorAttachment = FramebufferAttachment.ColorAttachment0;
|
||||
|
||||
public FramebufferBuilder AddColorAttachment<T>()
|
||||
where T : struct, IPixel
|
||||
{
|
||||
if (_currentColorAttachment > FramebufferAttachment.ColorAttachment15)
|
||||
{
|
||||
throw new ArgumentException("Framebuffer already has 16 color attachments");
|
||||
}
|
||||
|
||||
var pixel = default(T);
|
||||
|
||||
return AddAttachment(new TextureAttachmentSpecification
|
||||
{
|
||||
FramebufferAttachment = _currentColorAttachment++,
|
||||
PixelFormat = pixel.Format,
|
||||
PixelType = pixel.Type,
|
||||
PixelInternalFormat = pixel.InternalFormat
|
||||
});
|
||||
}
|
||||
|
||||
public FramebufferBuilder AddDepthAttachment()
|
||||
{
|
||||
return AddAttachment(new RenderbufferAttachmentSpecification
|
||||
{
|
||||
FramebufferAttachment = FramebufferAttachment.DepthAttachment,
|
||||
RenderbufferStorage = RenderbufferStorage.DepthComponent
|
||||
});
|
||||
}
|
||||
|
||||
private FramebufferBuilder AddAttachment(AttachmentSpecification parAttachment)
|
||||
{
|
||||
if (_usedAttachments.Contains(parAttachment.FramebufferAttachment))
|
||||
{
|
||||
throw new ArgumentException("Framebuffer already has attachment of type " +
|
||||
parAttachment.FramebufferAttachment);
|
||||
}
|
||||
|
||||
_attachments.Add(parAttachment);
|
||||
_usedAttachments.Add(parAttachment.FramebufferAttachment);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Framebuffer Build()
|
||||
{
|
||||
Dictionary<FramebufferAttachment, IFramebufferAttachment>? attachments = _attachments
|
||||
.ToDictionary(
|
||||
parAttachment => parAttachment.FramebufferAttachment,
|
||||
parAttachment => parAttachment.Create(parWidth, parHeight)
|
||||
);
|
||||
|
||||
return new Framebuffer(parWidth, parHeight, attachments);
|
||||
}
|
||||
|
||||
private class TextureAttachmentSpecification : AttachmentSpecification
|
||||
{
|
||||
public PixelFormat PixelFormat { get; init; }
|
||||
public PixelType PixelType { get; init; }
|
||||
public PixelInternalFormat PixelInternalFormat { get; init; }
|
||||
|
||||
public override DynamicTexture Create(int parWidth, int parHeight)
|
||||
{
|
||||
return new DynamicTexture(parWidth, parHeight, PixelFormat, PixelType, PixelInternalFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private class RenderbufferAttachmentSpecification : AttachmentSpecification
|
||||
{
|
||||
public RenderbufferStorage RenderbufferStorage { get; init; }
|
||||
|
||||
public override Renderbuffer Create(int parWidth, int parHeight)
|
||||
{
|
||||
return new Renderbuffer(parWidth, parHeight, RenderbufferStorage);
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class AttachmentSpecification
|
||||
{
|
||||
public FramebufferAttachment FramebufferAttachment { get; init; }
|
||||
|
||||
public abstract IFramebufferAttachment Create(int parWidth, int parHeight);
|
||||
}
|
||||
}
|
||||
15
Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs
Normal file
15
Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Engine.Renderer.Framebuffer;
|
||||
|
||||
public interface IFramebufferAttachment
|
||||
{
|
||||
public AttachmentType Type { get; }
|
||||
internal int Handle { get; }
|
||||
|
||||
public void Resize(int parWidth, int parHeight);
|
||||
|
||||
public enum AttachmentType
|
||||
{
|
||||
_texture,
|
||||
_renderbuffer
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
namespace Engine.Renderer.Framebuffer;
|
||||
|
||||
public class Renderbuffer : OpenGlObject
|
||||
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
|
||||
{
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
|
||||
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._renderbuffer;
|
||||
|
||||
private readonly RenderbufferStorage _format;
|
||||
|
||||
public Renderbuffer(int width, int height, RenderbufferStorage format)
|
||||
public Renderbuffer(int parWidth, int parHeight, RenderbufferStorage parFormat)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
_format = format;
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
_format = parFormat;
|
||||
|
||||
GL.CreateRenderbuffers(1, out int handle);
|
||||
Handle = handle;
|
||||
@@ -21,23 +23,25 @@ public class Renderbuffer : OpenGlObject
|
||||
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
public void Resize(int parWidth, int parHeight)
|
||||
{
|
||||
if (Width == parWidth && Height == parHeight)
|
||||
{
|
||||
if (Width == width && Height == height)
|
||||
return;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
|
||||
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
using Engine.Renderer.Texture;
|
||||
using Engine.Scene;
|
||||
using OpenTK.Windowing.Common;
|
||||
|
||||
namespace Engine.Renderer;
|
||||
|
||||
public interface IPresenter
|
||||
public interface IPresenter : IUpdate
|
||||
{
|
||||
public void Present(IConstTexture texture);
|
||||
public bool IsExiting { get; }
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public event Action<ResizeEventArgs> Resize;
|
||||
|
||||
public void Present(IConstTexture parTexture);
|
||||
}
|
||||
@@ -6,8 +6,8 @@ public abstract class OpenGlObject
|
||||
{
|
||||
public int Handle { get; protected set; } = -1;
|
||||
|
||||
internal abstract void Bind();
|
||||
internal abstract void Unbind();
|
||||
public abstract void Bind();
|
||||
public abstract void Unbind();
|
||||
|
||||
protected abstract void Destroy();
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using Engine.Renderer.Framebuffer;
|
||||
using Engine.Renderer.Pixel;
|
||||
using Engine.Renderer.Shader;
|
||||
|
||||
namespace Engine.Renderer.Postprocess;
|
||||
|
||||
public class Pipeline
|
||||
{
|
||||
private readonly List<RenderPass> _renderPasses = [];
|
||||
|
||||
public void AddRenderPass(ShaderProgram shaderProgram, Framebuffer.Framebuffer frameBuffer)
|
||||
{
|
||||
_renderPasses.Add(new RenderPass(shaderProgram, frameBuffer));
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
foreach (var renderPass in _renderPasses)
|
||||
{
|
||||
var texture = renderPass.FrameBuffer.TextureInternal;
|
||||
texture.BindUnit();
|
||||
renderPass.ShaderProgram.Bind();
|
||||
renderPass.FrameBuffer.Bind();
|
||||
// TODO: Render
|
||||
}
|
||||
}
|
||||
|
||||
private class RenderPass
|
||||
{
|
||||
internal ShaderProgram ShaderProgram { get; }
|
||||
internal Framebuffer.Framebuffer FrameBuffer { get; }
|
||||
|
||||
internal RenderPass(ShaderProgram shaderProgram, Framebuffer.Framebuffer frameBuffer)
|
||||
{
|
||||
ShaderProgram = shaderProgram;
|
||||
FrameBuffer = frameBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,19 @@ namespace Engine.Renderer;
|
||||
|
||||
public class Renderer
|
||||
{
|
||||
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal;
|
||||
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
|
||||
|
||||
private readonly Framebuffer.Framebuffer _framebuffer;
|
||||
private readonly Queue<Action<Renderer>> _renderActions = new();
|
||||
|
||||
public Renderer(int width, int height)
|
||||
public Renderer(int parWidth, int parHeight)
|
||||
{
|
||||
InitializeOpenGl();
|
||||
|
||||
_framebuffer = Framebuffer.Framebuffer.Create<Rgb8>(width, height);
|
||||
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
|
||||
.AddColorAttachment<Rgb8>()
|
||||
.AddDepthAttachment()
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void InitializeOpenGl()
|
||||
@@ -35,21 +38,24 @@ public class Renderer
|
||||
GL.Enable(EnableCap.Blend);
|
||||
}
|
||||
|
||||
internal void Commit(Action<Renderer> renderAction)
|
||||
internal void Commit(Action<Renderer> parRenderAction)
|
||||
{
|
||||
_renderActions.Enqueue(renderAction);
|
||||
_renderActions.Enqueue(parRenderAction);
|
||||
}
|
||||
|
||||
internal void Render()
|
||||
{
|
||||
_framebuffer.Bind();
|
||||
while (_renderActions.TryDequeue(out var renderAction))
|
||||
while (_renderActions.TryDequeue(out Action<Renderer>? renderAction))
|
||||
{
|
||||
renderAction(this);
|
||||
}
|
||||
|
||||
_framebuffer.Unbind();
|
||||
}
|
||||
|
||||
internal void Resize(int width, int height)
|
||||
internal void Resize(int parWidth, int parHeight)
|
||||
{
|
||||
_framebuffer.Resize(width, height);
|
||||
_framebuffer.Resize(parWidth, parHeight);
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ public class ShaderProgram : OpenGlObject
|
||||
{
|
||||
private readonly Dictionary<string, int> _uniforms = new();
|
||||
|
||||
public static ShaderProgram CreateFromSource(string source)
|
||||
public static ShaderProgram CreateFromSource(string parSource)
|
||||
{
|
||||
var vertexSource = new StringBuilder();
|
||||
var fragmentSource = new StringBuilder();
|
||||
var inFragment = false;
|
||||
var inVertex = false;
|
||||
|
||||
foreach (var line in source.Split('\n'))
|
||||
foreach (var line in parSource.Split('\n'))
|
||||
{
|
||||
if (line.StartsWith("#shader vertex"))
|
||||
{
|
||||
@@ -42,21 +42,21 @@ public class ShaderProgram : OpenGlObject
|
||||
return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
|
||||
}
|
||||
|
||||
public ShaderProgram(string vertexSource, string fragmentSource)
|
||||
public ShaderProgram(string parVertexSource, string parFragmentSource)
|
||||
{
|
||||
var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader);
|
||||
var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader);
|
||||
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
|
||||
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
|
||||
|
||||
Handle = LinkProgram(vertexShader, fragmentShader);
|
||||
}
|
||||
|
||||
public void SetUniform<T>(string name, T value, [CallerMemberName] string caller = "")
|
||||
public void SetUniform<T>(string parName, T parValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = GetUniformLocation(name);
|
||||
var location = GetUniformLocation(parName);
|
||||
|
||||
switch (value)
|
||||
switch (parValue)
|
||||
{
|
||||
case bool boolValue:
|
||||
GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0);
|
||||
@@ -85,17 +85,17 @@ public class ShaderProgram : OpenGlObject
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller);
|
||||
Log.Error(ex, "Failed to set uniform {UniformName}", parName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
public override void Bind()
|
||||
{
|
||||
GL.UseProgram(Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.UseProgram(0);
|
||||
}
|
||||
@@ -105,23 +105,27 @@ public class ShaderProgram : OpenGlObject
|
||||
GL.DeleteProgram(Handle);
|
||||
}
|
||||
|
||||
private int GetUniformLocation(string name)
|
||||
private int GetUniformLocation(string parName)
|
||||
{
|
||||
if (_uniforms.TryGetValue(parName, out var location))
|
||||
{
|
||||
if (_uniforms.TryGetValue(name, out var location))
|
||||
return location;
|
||||
|
||||
location = GL.GetUniformLocation(Handle, name);
|
||||
if (location < 0)
|
||||
throw new ArgumentException($"Uniform '{name}' not found in shader program");
|
||||
|
||||
_uniforms.Add(name, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
private static int CompileSource(string source, ShaderType type)
|
||||
location = GL.GetUniformLocation(Handle, parName);
|
||||
if (location < 0)
|
||||
{
|
||||
var shaderId = GL.CreateShader(type);
|
||||
GL.ShaderSource(shaderId, source);
|
||||
throw new ArgumentException($"Uniform '{parName}' not found in shader program");
|
||||
}
|
||||
|
||||
_uniforms.Add(parName, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
private static int CompileSource(string parSource, ShaderType parType)
|
||||
{
|
||||
var shaderId = GL.CreateShader(parType);
|
||||
GL.ShaderSource(shaderId, parSource);
|
||||
GL.CompileShader(shaderId);
|
||||
|
||||
GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status);
|
||||
@@ -130,19 +134,19 @@ public class ShaderProgram : OpenGlObject
|
||||
var log = GL.GetShaderInfoLog(shaderId);
|
||||
GL.DeleteShader(shaderId);
|
||||
|
||||
throw new ShaderCompilationException(type, log);
|
||||
throw new ShaderCompilationException(parType, log);
|
||||
}
|
||||
|
||||
return shaderId;
|
||||
}
|
||||
|
||||
private static int LinkProgram(int vertexShader, int fragmentShader)
|
||||
private static int LinkProgram(int parVertexShader, int parFragmentShader)
|
||||
{
|
||||
var programId = GL.CreateProgram();
|
||||
try
|
||||
{
|
||||
GL.AttachShader(programId, vertexShader);
|
||||
GL.AttachShader(programId, fragmentShader);
|
||||
GL.AttachShader(programId, parVertexShader);
|
||||
GL.AttachShader(programId, parFragmentShader);
|
||||
GL.LinkProgram(programId);
|
||||
|
||||
GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus);
|
||||
@@ -162,20 +166,21 @@ public class ShaderProgram : OpenGlObject
|
||||
}
|
||||
finally
|
||||
{
|
||||
GL.DeleteShader(vertexShader);
|
||||
GL.DeleteShader(fragmentShader);
|
||||
GL.DeleteShader(parVertexShader);
|
||||
GL.DeleteShader(parFragmentShader);
|
||||
}
|
||||
|
||||
return programId;
|
||||
}
|
||||
}
|
||||
|
||||
public class ShaderCompilationException(ShaderType type, string message)
|
||||
: Exception($"Failed to compile {type} shader: {message}")
|
||||
public class ShaderCompilationException(ShaderType parType, string parMessage)
|
||||
: Exception($"Failed to compile {parType} shader: {parMessage}")
|
||||
{
|
||||
public ShaderType ShaderType { get; } = type;
|
||||
public ShaderType ShaderType { get; } = parType;
|
||||
}
|
||||
|
||||
public class ShaderLinkException(string message) : Exception($"Failed to link shader program: {message}");
|
||||
public class ShaderLinkException(string parMessage) : Exception($"Failed to link shader program: {parMessage}");
|
||||
|
||||
public class ShaderValidationException(string message) : Exception($"Failed to validate shader program: {message}");
|
||||
public class ShaderValidationException(string parMessage)
|
||||
: Exception($"Failed to validate shader program: {parMessage}");
|
||||
@@ -1,40 +1,46 @@
|
||||
using Engine.Renderer.Pixel;
|
||||
using Engine.Renderer.Framebuffer;
|
||||
using Engine.Renderer.Pixel;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Engine.Renderer.Texture;
|
||||
|
||||
public class DynamicTexture : Texture
|
||||
public class DynamicTexture : Texture, IFramebufferAttachment
|
||||
{
|
||||
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._texture;
|
||||
|
||||
private readonly PixelFormat _format;
|
||||
private readonly PixelType _type;
|
||||
private readonly PixelInternalFormat _internalFormat;
|
||||
|
||||
internal DynamicTexture(int width, int height, PixelFormat format, PixelType type, PixelInternalFormat internalFormat)
|
||||
: base(width, height)
|
||||
internal DynamicTexture(int parWidth, int parHeight, PixelFormat parFormat, PixelType parType,
|
||||
PixelInternalFormat parInternalFormat)
|
||||
: base(parWidth, parHeight)
|
||||
{
|
||||
_format = format;
|
||||
_type = type;
|
||||
_internalFormat = internalFormat;
|
||||
_format = parFormat;
|
||||
_type = parType;
|
||||
_internalFormat = parInternalFormat;
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static DynamicTexture Create<T>(int width, int height)
|
||||
public static DynamicTexture Create<T>(int parWidth, int parHeight)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
var pixel = default(T);
|
||||
return new DynamicTexture(width, height, pixel.Format, pixel.Type, pixel.InternalFormat);
|
||||
return new DynamicTexture(parWidth, parHeight, pixel.Format, pixel.Type, pixel.InternalFormat);
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
public void Resize(int parWidth, int parHeight)
|
||||
{
|
||||
if (Width == parWidth && Height == parHeight)
|
||||
{
|
||||
if (Width == width && Height == height)
|
||||
return;
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
|
||||
Bind();
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
||||
|
||||
@@ -8,31 +8,39 @@ public interface IConstTexture
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
|
||||
public void ReadPixels<T>(int x, int y, int width, int height, T[,] pixels)
|
||||
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
|
||||
where T : struct, IPixel;
|
||||
}
|
||||
|
||||
public static class ConstTextureExtensions
|
||||
{
|
||||
public static T[,] ReadPixels<T>(this IConstTexture texture) where T : struct, IPixel
|
||||
=> texture.ReadPixels<T>(0, 0, texture.Width, texture.Height);
|
||||
public static T[,] ReadPixels<T>(this IConstTexture parTexture) where T : struct, IPixel
|
||||
{
|
||||
return parTexture.ReadPixels<T>(0, 0, parTexture.Width, parTexture.Height);
|
||||
}
|
||||
|
||||
public static T[,] ReadPixels<T>(this IConstTexture texture, int x, int y, int width, int height)
|
||||
public static T[,] ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
var pixels = new T[width, height];
|
||||
texture.ReadPixels(x, y, width, height, pixels);
|
||||
var pixels = new T[parWidth, parHeight];
|
||||
parTexture.ReadPixels(parX, parY, parWidth, parHeight, pixels);
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
public static void ReadPixels<T>(this IConstTexture texture, Image<T> image) where T : struct, IPixel
|
||||
=> texture.ReadPixels(0, 0, image);
|
||||
public static void ReadPixels<T>(this IConstTexture parTexture, Image<T> parImage) where T : struct, IPixel
|
||||
{
|
||||
parTexture.ReadPixels(0, 0, parImage);
|
||||
}
|
||||
|
||||
public static void ReadPixels<T>(this IConstTexture texture, int x, int y, Image<T> image)
|
||||
where T : struct, IPixel =>
|
||||
texture.ReadPixels(x, y, image.Width, image.Height, image.Pixels);
|
||||
public static void ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, Image<T> parImage)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
parTexture.ReadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
|
||||
}
|
||||
|
||||
public static void ReadPixels<T>(this IConstTexture texture, T[,] pixels) where T : struct, IPixel
|
||||
=> texture.ReadPixels(0, 0, texture.Width, texture.Height, pixels);
|
||||
public static void ReadPixels<T>(this IConstTexture parTexture, T[,] parPixels) where T : struct, IPixel
|
||||
{
|
||||
parTexture.ReadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,25 @@ namespace Engine.Renderer.Texture;
|
||||
|
||||
public interface ITexture : IConstTexture
|
||||
{
|
||||
public void UploadPixels<T>(int x, int y, int width, int height, T[,] pixels)
|
||||
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
|
||||
where T : struct, IPixel;
|
||||
}
|
||||
|
||||
public static class TextureExtensions
|
||||
{
|
||||
public static void UploadPixels<T>(this ITexture texture, Image<T> image) where T : struct, IPixel
|
||||
=> texture.UploadPixels(0, 0, image);
|
||||
public static void UploadPixels<T>(this ITexture parTexture, Image<T> parImage) where T : struct, IPixel
|
||||
{
|
||||
parTexture.UploadPixels(0, 0, parImage);
|
||||
}
|
||||
|
||||
public static void UploadPixels<T>(this ITexture texture, int x, int y, Image<T> image)
|
||||
where T : struct, IPixel =>
|
||||
texture.UploadPixels(x, y, image.Width, image.Height, image.Pixels);
|
||||
public static void UploadPixels<T>(this ITexture parTexture, int parX, int parY, Image<T> parImage)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
parTexture.UploadPixels(parX, parY, parImage.Width, parImage.Height, parImage.Pixels);
|
||||
}
|
||||
|
||||
public static void UploadPixels<T>(this ITexture texture, T[,] pixels) where T : struct, IPixel
|
||||
=> texture.UploadPixels(0, 0, texture.Width, texture.Height, pixels);
|
||||
public static void UploadPixels<T>(this ITexture parTexture, T[,] parPixels) where T : struct, IPixel
|
||||
{
|
||||
parTexture.UploadPixels(0, 0, parTexture.Width, parTexture.Height, parPixels);
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@ namespace Engine.Renderer.Texture;
|
||||
|
||||
public class StaticTexture : Texture
|
||||
{
|
||||
private StaticTexture(int width, int height, SizedInternalFormat format) : base(width, height)
|
||||
private StaticTexture(int parWidth, int parHeight, SizedInternalFormat parFormat) : base(parWidth, parHeight)
|
||||
{
|
||||
GL.TextureStorage2D(Handle, 1, format, Width, Height);
|
||||
GL.TextureStorage2D(Handle, 1, parFormat, Width, Height);
|
||||
}
|
||||
|
||||
public static StaticTexture Create<T>(int width, int height)
|
||||
public static StaticTexture Create<T>(int parWidth, int parHeight)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
var format = default(T).SizedInternalFormat;
|
||||
return new StaticTexture(width, height, format);
|
||||
return new StaticTexture(parWidth, parHeight, format);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ public abstract class Texture : OpenGlObject, ITexture
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentException("Width must be greater than 0");
|
||||
}
|
||||
|
||||
_width = value;
|
||||
}
|
||||
@@ -24,7 +26,9 @@ public abstract class Texture : OpenGlObject, ITexture
|
||||
protected set
|
||||
{
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentException("Height must be greater than 0");
|
||||
}
|
||||
|
||||
_height = value;
|
||||
}
|
||||
@@ -33,69 +37,86 @@ public abstract class Texture : OpenGlObject, ITexture
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
protected Texture(int width, int height)
|
||||
protected Texture(int parWidth, int parHeight)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
|
||||
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public void UploadPixels<T>(int x, int y, int width, int height, T[,] pixels)
|
||||
public void UploadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
if (x < 0 || y < 0)
|
||||
if (parX < 0 || parY < 0)
|
||||
{
|
||||
throw new ArgumentException("x and y must be greater than 0");
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
throw new ArgumentException("Width and height must be greater than 0");
|
||||
|
||||
if (x + width > Width || y + height > Height)
|
||||
throw new ArgumentException("x + width and y + height must be less than width and height");
|
||||
|
||||
if (pixels.Length != width * height)
|
||||
throw new ArgumentException("Pixels array must be of size width * height");
|
||||
|
||||
var format = pixels[0, 0].Format;
|
||||
var type = pixels[0, 0].Type;
|
||||
|
||||
GL.TextureSubImage2D(Handle, 0, x, y, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public void ReadPixels<T>(int x, int y, int width, int height, T[,] pixels)
|
||||
if (parWidth <= 0 || parHeight <= 0)
|
||||
{
|
||||
throw new ArgumentException("Width and height must be greater than 0");
|
||||
}
|
||||
|
||||
if (parX + parWidth > Width || parY + parHeight > Height)
|
||||
{
|
||||
throw new ArgumentException("x + width and y + height must be less than width and height");
|
||||
}
|
||||
|
||||
if (parPixels.Length != parWidth * parHeight)
|
||||
{
|
||||
throw new ArgumentException("Pixels array must be of size width * height");
|
||||
}
|
||||
|
||||
var format = parPixels[0, 0].Format;
|
||||
var type = parPixels[0, 0].Type;
|
||||
|
||||
GL.TextureSubImage2D(Handle, 0, parX, parY, parWidth, parHeight, format, type, parPixels);
|
||||
}
|
||||
|
||||
public void ReadPixels<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
|
||||
where T : struct, IPixel
|
||||
{
|
||||
if (x < 0 || y < 0)
|
||||
if (parX < 0 || parY < 0)
|
||||
{
|
||||
throw new ArgumentException("x and y must be greater than 0");
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
if (parWidth <= 0 || parHeight <= 0)
|
||||
{
|
||||
throw new ArgumentException("Width and height must be greater than 0");
|
||||
}
|
||||
|
||||
if (x + width > Width || y + height > Height)
|
||||
if (parX + parWidth > Width || parY + parHeight > Height)
|
||||
{
|
||||
throw new ArgumentException("x + width and y + height must be less than width and height");
|
||||
}
|
||||
|
||||
if (pixels.Length != width * height)
|
||||
if (parPixels.Length != parWidth * parHeight)
|
||||
{
|
||||
throw new ArgumentException("Pixels array must be of size width * height");
|
||||
}
|
||||
|
||||
var format = default(T).Format;
|
||||
var type = default(T).Type;
|
||||
|
||||
GL.GetTextureSubImage(Handle, 0, x, y, 0, width, height, 1, format, type, pixels.Length * Marshal.SizeOf<T>(),
|
||||
pixels);
|
||||
GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type,
|
||||
parPixels.Length * Marshal.SizeOf<T>(),
|
||||
parPixels);
|
||||
}
|
||||
|
||||
internal void BindUnit(int unit = 0)
|
||||
public void BindUnit(int parUnit = 0)
|
||||
{
|
||||
GL.BindTextureUnit(unit, Handle);
|
||||
GL.BindTextureUnit(parUnit, Handle);
|
||||
}
|
||||
|
||||
internal override void Bind()
|
||||
public override void Bind()
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||
}
|
||||
|
||||
internal override void Unbind()
|
||||
public override void Unbind()
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ using OpenTK.Mathematics;
|
||||
namespace Engine.Scene.Component.BuiltIn;
|
||||
|
||||
public abstract class Camera(
|
||||
float nearPlane,
|
||||
float farPlane
|
||||
float parNearPlane,
|
||||
float parFarPlane
|
||||
) : Component, ICamera
|
||||
{
|
||||
public float AspectRatio { get; private set; } = 1;
|
||||
public float NearPlane { get; set; } = nearPlane;
|
||||
public float FarPlane { get; set; } = farPlane;
|
||||
public float NearPlane { get; set; } = parNearPlane;
|
||||
public float FarPlane { get; set; } = parFarPlane;
|
||||
|
||||
private Vector2i _screenSize = new(1, 1);
|
||||
|
||||
@@ -27,6 +27,6 @@ public abstract class Camera(
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Vector3 ScreenToWorld(Vector2 screenPosition);
|
||||
public abstract Vector2 WorldToScreen(Vector3 worldPosition);
|
||||
public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition);
|
||||
public abstract Vector2 WorldToScreen(Vector3 parWorldPosition);
|
||||
}
|
||||
@@ -4,15 +4,15 @@ using OpenTK.Mathematics;
|
||||
namespace Engine.Scene.Component.BuiltIn;
|
||||
|
||||
public class OrthographicCamera(
|
||||
float nearPlane = 0.1f,
|
||||
float farPlane = 1000f,
|
||||
float size = 10f,
|
||||
OrthographicCamera.Axis axis = OrthographicCamera.Axis.Y
|
||||
float parNearPlane = 0.1f,
|
||||
float parFarPlane = 1000f,
|
||||
float parSize = 10f,
|
||||
OrthographicCamera.Axis parAxis = OrthographicCamera.Axis.Y
|
||||
)
|
||||
: Camera(nearPlane, farPlane)
|
||||
: Camera(parNearPlane, parFarPlane)
|
||||
{
|
||||
public Axis FixedAxis { get; set; } = axis;
|
||||
public float Size { get; set; } = size;
|
||||
public Axis FixedAxis { get; set; } = parAxis;
|
||||
public float Size { get; set; } = parSize;
|
||||
|
||||
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted();
|
||||
|
||||
@@ -20,19 +20,19 @@ public class OrthographicCamera(
|
||||
? Matrix4.CreateOrthographic(Size, Size / AspectRatio, NearPlane, FarPlane)
|
||||
: Matrix4.CreateOrthographic(Size * AspectRatio, Size, NearPlane, FarPlane);
|
||||
|
||||
public override Vector3 ScreenToWorld(Vector2 screenPosition)
|
||||
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
|
||||
{
|
||||
var offset = FixedAxis == Axis.X
|
||||
? new Vector2(Size, Size / AspectRatio)
|
||||
: new Vector2(Size * AspectRatio, Size);
|
||||
offset /= 2;
|
||||
|
||||
return new Vector4(screenPosition.X - offset.X, screenPosition.Y - offset.Y, 0, 1)
|
||||
return new Vector4(parScreenPosition.X - offset.X, parScreenPosition.Y - offset.Y, 0, 1)
|
||||
.MulProject(GameObject.Transform.TransformMatrix)
|
||||
.Xyz;
|
||||
}
|
||||
|
||||
public override Vector2 WorldToScreen(Vector3 worldPosition)
|
||||
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -40,6 +40,6 @@ public class OrthographicCamera(
|
||||
public enum Axis
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Y
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@ using OpenTK.Mathematics;
|
||||
namespace Engine.Scene.Component.BuiltIn;
|
||||
|
||||
public class PerspectiveCamera(
|
||||
float fieldOfView = 90.0f,
|
||||
float nearPlane = 0.1f,
|
||||
float farPlane = 1000f
|
||||
float parFieldOfView = 90.0f,
|
||||
float parNearPlane = 0.1f,
|
||||
float parFarPlane = 1000f
|
||||
)
|
||||
: Camera(nearPlane, farPlane)
|
||||
: Camera(parNearPlane, parFarPlane)
|
||||
{
|
||||
public float FieldOfView { get; set; } = fieldOfView;
|
||||
public float FieldOfView { get; set; } = parFieldOfView;
|
||||
|
||||
public override Matrix4 View
|
||||
{
|
||||
@@ -28,12 +28,12 @@ public class PerspectiveCamera(
|
||||
public override Matrix4 Projection =>
|
||||
Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
|
||||
|
||||
public override Vector3 ScreenToWorld(Vector2 screenPosition)
|
||||
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Vector2 WorldToScreen(Vector3 worldPosition)
|
||||
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -26,13 +26,8 @@ public class Transform : Component
|
||||
|
||||
public Transform Clone()
|
||||
{
|
||||
var clone = new Transform
|
||||
{
|
||||
Position = Position,
|
||||
Rotation = Rotation,
|
||||
Scale = Scale,
|
||||
LocalScale = LocalScale
|
||||
};
|
||||
var clone =
|
||||
new Transform { Position = Position, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale };
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
namespace Engine.Scene.Component;
|
||||
|
||||
public abstract class Component
|
||||
public abstract class Component : IUpdate, IRender
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
|
||||
public GameObject GameObject { get; internal set; }
|
||||
|
||||
internal virtual void Awake()
|
||||
public virtual void Awake()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void Start()
|
||||
public virtual void Start()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void Update()
|
||||
public virtual void Update(double parDeltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void Render()
|
||||
public virtual void Render()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void Destroy()
|
||||
public virtual void Destroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ComponentTypeExtensions
|
||||
{
|
||||
internal static Type GetComponentBaseType(this Type type)
|
||||
internal static Type GetComponentBaseType(this Type parType)
|
||||
{
|
||||
var baseType = type.BaseType;
|
||||
var baseType = parType.BaseType;
|
||||
if (baseType == null || baseType == typeof(Component))
|
||||
return type;
|
||||
{
|
||||
return parType;
|
||||
}
|
||||
|
||||
while (baseType.BaseType != null && baseType.BaseType != typeof(Component))
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Engine.Scene.Component.BuiltIn;
|
||||
|
||||
namespace Engine.Scene;
|
||||
|
||||
public sealed class GameObject
|
||||
public sealed class GameObject : IUpdate, IRender
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
public Transform Transform { get; }
|
||||
@@ -24,9 +24,9 @@ public sealed class GameObject
|
||||
Transform = GetComponent<Transform>()!;
|
||||
}
|
||||
|
||||
public GameObject(Transform transform)
|
||||
public GameObject(Transform parTransform)
|
||||
{
|
||||
AddComponent(transform.Clone());
|
||||
AddComponent(parTransform.Clone());
|
||||
ProcessChanges();
|
||||
|
||||
Transform = GetComponent<Transform>()!;
|
||||
@@ -35,32 +35,42 @@ public sealed class GameObject
|
||||
public void Awake()
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
component.Awake();
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
component.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
foreach (var component in _components)
|
||||
component.Update();
|
||||
{
|
||||
component.Update(parDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
component.Render();
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
foreach (var component in _components)
|
||||
{
|
||||
component.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public T? GetComponent<T>() where T : Component.Component
|
||||
{
|
||||
@@ -73,32 +83,36 @@ public sealed class GameObject
|
||||
AddComponent(component);
|
||||
}
|
||||
|
||||
public void AddComponent<T>(params object?[] args) where T : Component.Component
|
||||
public void AddComponent<T>(params object?[] parArgs) where T : Component.Component
|
||||
{
|
||||
var component = (T?)Activator.CreateInstance(
|
||||
typeof(T),
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance |
|
||||
BindingFlags.OptionalParamBinding,
|
||||
null,
|
||||
args,
|
||||
parArgs,
|
||||
null
|
||||
);
|
||||
|
||||
if (component == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
|
||||
}
|
||||
|
||||
AddComponent(component);
|
||||
}
|
||||
|
||||
public void AddComponent<T>(T component) where T : Component.Component
|
||||
public void AddComponent<T>(T parComponent) where T : Component.Component
|
||||
{
|
||||
_componentActions.Enqueue(() =>
|
||||
{
|
||||
if (HasComponent<T>())
|
||||
{
|
||||
throw new ArgumentException($"GameObject already has component of type {typeof(T)}");
|
||||
}
|
||||
|
||||
component.GameObject = this;
|
||||
_components.Add(component);
|
||||
parComponent.GameObject = this;
|
||||
_components.Add(parComponent);
|
||||
_addedComponentTypes.Add(typeof(T).GetComponentBaseType());
|
||||
});
|
||||
}
|
||||
@@ -106,16 +120,22 @@ public sealed class GameObject
|
||||
public void RemoveComponent<T>() where T : Component.Component
|
||||
{
|
||||
if (typeof(T) == typeof(Transform))
|
||||
{
|
||||
throw new ArgumentException("GameObject cannot remove Transform component");
|
||||
}
|
||||
|
||||
_componentActions.Enqueue(() =>
|
||||
{
|
||||
if (!HasComponent<T>())
|
||||
{
|
||||
throw new ArgumentException($"GameObject does not have component of type {typeof(T)}");
|
||||
}
|
||||
|
||||
var component = GetComponent<T>();
|
||||
if (component == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_components.Remove(component);
|
||||
_addedComponentTypes.Remove(typeof(T));
|
||||
@@ -131,6 +151,8 @@ public sealed class GameObject
|
||||
internal void ProcessChanges()
|
||||
{
|
||||
while (_componentActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,102 +20,122 @@ public class Hierarchy<T> : IEnumerable<T>
|
||||
internal void ProcessChanges()
|
||||
{
|
||||
while (_hierarchyActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(T obj)
|
||||
public void Add(T parObj)
|
||||
{
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
if (_parentLookup.ContainsKey(obj))
|
||||
if (_parentLookup.ContainsKey(parObj))
|
||||
{
|
||||
throw new ArgumentException("Object is already added to hierarchy");
|
||||
}
|
||||
|
||||
_childrenLookup.Add(obj, new List<T>());
|
||||
_parentLookup.Add(obj, null);
|
||||
_childrenLookup[null].Add(obj);
|
||||
_childrenLookup.Add(parObj, new List<T>());
|
||||
_parentLookup.Add(parObj, null);
|
||||
_childrenLookup[null].Add(parObj);
|
||||
});
|
||||
}
|
||||
|
||||
public void Remove(T obj)
|
||||
public void Remove(T parObj)
|
||||
{
|
||||
foreach (var child in GetChildren(parObj))
|
||||
{
|
||||
foreach (var child in GetChildren(obj))
|
||||
Remove(child);
|
||||
}
|
||||
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
var parent = GetParent(obj);
|
||||
_childrenLookup[parent].Remove(obj);
|
||||
var parent = GetParent(parObj);
|
||||
_childrenLookup[parent].Remove(parObj);
|
||||
|
||||
_parentLookup.Remove(obj);
|
||||
_childrenLookup.Remove(obj);
|
||||
_parentLookup.Remove(parObj);
|
||||
_childrenLookup.Remove(parObj);
|
||||
});
|
||||
}
|
||||
|
||||
public void AddChild(T parent, T child)
|
||||
public void AddChild(T parEnt, T parChild)
|
||||
{
|
||||
SetParent(child, parent);
|
||||
SetParent(parChild, parEnt);
|
||||
}
|
||||
|
||||
private void SetParent(T child, T? parent)
|
||||
private void SetParent(T parChild, T? parEnt)
|
||||
{
|
||||
if (parChild.Equals(parEnt))
|
||||
{
|
||||
if (child.Equals(parent))
|
||||
throw new InvalidOperationException("Child cannot be parent");
|
||||
}
|
||||
|
||||
_hierarchyActions.Enqueue(() =>
|
||||
{
|
||||
if (IsInHierarchy(child, parent))
|
||||
if (IsInHierarchy(parChild, parEnt))
|
||||
{
|
||||
throw new InvalidOperationException("Parent is a child of child");
|
||||
}
|
||||
|
||||
var oldParent = GetParent(child);
|
||||
_childrenLookup[oldParent].Remove(child);
|
||||
var oldParent = GetParent(parChild);
|
||||
_childrenLookup[oldParent].Remove(parChild);
|
||||
|
||||
_childrenLookup[parent].Add(child);
|
||||
_parentLookup[child] = parent;
|
||||
_childrenLookup[parEnt].Add(parChild);
|
||||
_parentLookup[parChild] = parEnt;
|
||||
});
|
||||
}
|
||||
|
||||
public bool Contains(T obj)
|
||||
public bool Contains(T parObj)
|
||||
{
|
||||
return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj);
|
||||
return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj);
|
||||
}
|
||||
|
||||
public T? GetParent(T child)
|
||||
public T? GetParent(T parChild)
|
||||
{
|
||||
return _parentLookup.TryGetValue(child, out var parent)
|
||||
return _parentLookup.TryGetValue(parChild, out var parent)
|
||||
? parent
|
||||
: throw new InvalidOperationException($"Child {child} is not in hierarchy");
|
||||
: throw new InvalidOperationException($"Child {parChild} is not in hierarchy");
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetChildren(T? obj = null)
|
||||
public IEnumerable<T> GetChildren(T? parObj = null)
|
||||
{
|
||||
return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty<T>();
|
||||
return _childrenLookup.TryGetValue(parObj, out IList<T>? children) ? children : Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
public bool IsInHierarchy(T? ancestor, T? child)
|
||||
public bool IsInHierarchy(T? parAncestor, T? parChild)
|
||||
{
|
||||
if (parChild == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
|
||||
{
|
||||
if (child == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ancestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
|
||||
if (parAncestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ancestor.Equals(child))
|
||||
if (parAncestor.Equals(parChild))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var parent = GetParent(child);
|
||||
var parent = GetParent(parChild);
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
return false;
|
||||
|
||||
if (ancestor.Equals(parent))
|
||||
return true;
|
||||
|
||||
return IsInHierarchy(ancestor, parent);
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAllChildren(T? obj = null)
|
||||
if (parAncestor.Equals(parent))
|
||||
{
|
||||
var children = GetChildren(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsInHierarchy(parAncestor, parent);
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAllChildren(T? parObj = null)
|
||||
{
|
||||
IEnumerable<T>? children = GetChildren(parObj);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
|
||||
6
Engine/src/Scene/IRender.cs
Normal file
6
Engine/src/Scene/IRender.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Engine.Scene;
|
||||
|
||||
public interface IRender
|
||||
{
|
||||
void Render();
|
||||
}
|
||||
6
Engine/src/Scene/IUpdate.cs
Normal file
6
Engine/src/Scene/IUpdate.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Engine.Scene;
|
||||
|
||||
public interface IUpdate
|
||||
{
|
||||
void Update(double parDeltaTime);
|
||||
}
|
||||
@@ -3,10 +3,10 @@ using Engine.Scene.Component.BuiltIn;
|
||||
|
||||
namespace Engine.Scene;
|
||||
|
||||
public class Scene
|
||||
public class Scene : IUpdate, IRender
|
||||
{
|
||||
public bool IsPlaying { get; private set; }
|
||||
public ICamera? Camera { get; private set; }
|
||||
public ICamera? MainCamera { get; private set; }
|
||||
|
||||
internal Hierarchy<GameObject> Hierarchy { get; } = [];
|
||||
|
||||
@@ -15,35 +15,43 @@ public class Scene
|
||||
internal void Enter()
|
||||
{
|
||||
if (IsPlaying)
|
||||
{
|
||||
throw new InvalidOperationException("Scene is already playing");
|
||||
}
|
||||
|
||||
ProcessChanges();
|
||||
|
||||
Camera = FindFirstComponent<Camera>();
|
||||
MainCamera = FindFirstComponent<Camera>();
|
||||
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
||||
public T? FindFirstComponent<T>() where T : Component.Component
|
||||
{
|
||||
return Hierarchy.Select(gameObject => gameObject.GetComponent<T>()).OfType<T>().FirstOrDefault();
|
||||
return Hierarchy.Select(parGameObject => parGameObject.GetComponent<T>()).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
if (!IsPlaying)
|
||||
{
|
||||
throw new InvalidOperationException("Scene is not playing");
|
||||
}
|
||||
|
||||
ProcessChanges();
|
||||
|
||||
foreach (var gameObject in Hierarchy)
|
||||
gameObject.Update();
|
||||
{
|
||||
gameObject.Update(parDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Render()
|
||||
public void Render()
|
||||
{
|
||||
if (!IsPlaying)
|
||||
{
|
||||
throw new InvalidOperationException("Scene is not playing");
|
||||
}
|
||||
|
||||
foreach (var gameObject in Hierarchy)
|
||||
{
|
||||
@@ -54,7 +62,9 @@ public class Scene
|
||||
internal void Exit()
|
||||
{
|
||||
if (!IsPlaying)
|
||||
{
|
||||
throw new InvalidOperationException("Scene is not playing");
|
||||
}
|
||||
|
||||
foreach (var gameObject in Hierarchy)
|
||||
{
|
||||
@@ -64,34 +74,34 @@ public class Scene
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
public void Add(GameObject gameObject)
|
||||
public void Add(GameObject parGameObject)
|
||||
{
|
||||
Hierarchy.Add(gameObject);
|
||||
Hierarchy.Add(parGameObject);
|
||||
|
||||
_sceneActions.Enqueue(() =>
|
||||
{
|
||||
gameObject.Scene = this;
|
||||
parGameObject.Scene = this;
|
||||
|
||||
gameObject.Awake();
|
||||
gameObject.Start();
|
||||
parGameObject.Awake();
|
||||
parGameObject.Start();
|
||||
});
|
||||
}
|
||||
|
||||
public void Remove(GameObject gameObject)
|
||||
public void Remove(GameObject parGameObject)
|
||||
{
|
||||
Hierarchy.Remove(gameObject);
|
||||
Hierarchy.Remove(parGameObject);
|
||||
|
||||
_sceneActions.Enqueue(() =>
|
||||
{
|
||||
foreach (var child in Hierarchy.GetAllChildren(gameObject))
|
||||
foreach (var child in Hierarchy.GetAllChildren(parGameObject))
|
||||
{
|
||||
child.Destroy();
|
||||
child.Scene = null;
|
||||
}
|
||||
|
||||
gameObject.Destroy();
|
||||
parGameObject.Destroy();
|
||||
|
||||
gameObject.Scene = null;
|
||||
parGameObject.Scene = null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,9 +110,13 @@ public class Scene
|
||||
Hierarchy.ProcessChanges();
|
||||
|
||||
while (_sceneActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
foreach (var gameObject in Hierarchy)
|
||||
{
|
||||
gameObject.ProcessChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Engine/src/Scene/SceneManager.cs
Normal file
34
Engine/src/Scene/SceneManager.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Engine.Scene.Component.BuiltIn;
|
||||
|
||||
namespace Engine.Scene;
|
||||
|
||||
public class SceneManager : IUpdate, IRender
|
||||
{
|
||||
public Scene? CurrentScene => _currentScene;
|
||||
|
||||
private Scene? _currentScene;
|
||||
private Scene? _nextScene;
|
||||
|
||||
public void TransitionTo(Scene parScene)
|
||||
{
|
||||
_nextScene = parScene;
|
||||
}
|
||||
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
if (_nextScene != null)
|
||||
{
|
||||
_currentScene?.Exit();
|
||||
_currentScene = _nextScene;
|
||||
_nextScene = null;
|
||||
_currentScene.Enter();
|
||||
}
|
||||
|
||||
_currentScene?.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
_currentScene?.Render();
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ namespace Engine.Util;
|
||||
|
||||
public static class Math
|
||||
{
|
||||
public static Vector4 MulProject(this Vector4 a, in Matrix4 m)
|
||||
public static Vector4 MulProject(this Vector4 parA, in Matrix4 parM)
|
||||
{
|
||||
var result = a * m;
|
||||
var result = parA * parM;
|
||||
if (result.W != 0.0)
|
||||
{
|
||||
result /= result.W;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,55 +1,64 @@
|
||||
namespace Engine.Util;
|
||||
|
||||
public readonly struct NullableObject<T>
|
||||
public readonly struct NullableObject<T>(T? parValue) : IEquatable<NullableObject<T>>
|
||||
where T : class
|
||||
{
|
||||
public bool IsNull => _value == null;
|
||||
public T? Value => _value;
|
||||
public bool IsNull => Value == null;
|
||||
public T? Value { get; } = parValue;
|
||||
|
||||
private readonly T? _value;
|
||||
|
||||
public NullableObject()
|
||||
public NullableObject() : this(null)
|
||||
{
|
||||
_value = null;
|
||||
}
|
||||
|
||||
public NullableObject(T? value)
|
||||
public static implicit operator T?(NullableObject<T> parNullableObject)
|
||||
{
|
||||
_value = value;
|
||||
return parNullableObject.Value;
|
||||
}
|
||||
|
||||
public static implicit operator T?(NullableObject<T> nullableObject) => nullableObject.Value;
|
||||
public static implicit operator NullableObject<T>(T? value) => new(value);
|
||||
public static implicit operator NullableObject<T>(T? parValue)
|
||||
{
|
||||
return new NullableObject<T>(parValue);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _value?.ToString() ?? "null";
|
||||
return Value?.ToString() ?? "null";
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
public override bool Equals(object? parObj)
|
||||
{
|
||||
if (obj == null)
|
||||
return IsNull;
|
||||
|
||||
if (obj is not NullableObject<T> other)
|
||||
return false;
|
||||
|
||||
if (IsNull && other.IsNull)
|
||||
return true;
|
||||
|
||||
return _value!.Equals(other._value);
|
||||
return parObj is NullableObject<T> other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (IsNull)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var hashCode = _value!.GetHashCode();
|
||||
var hashCode = Value!.GetHashCode();
|
||||
|
||||
if (hashCode >= 0)
|
||||
{
|
||||
hashCode += 1;
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public bool Equals(NullableObject<T> parOther)
|
||||
{
|
||||
return EqualityComparer<T?>.Default.Equals(Value, parOther.Value);
|
||||
}
|
||||
|
||||
public static bool operator ==(NullableObject<T> parLeft, NullableObject<T> parRight)
|
||||
{
|
||||
return parLeft.Equals(parRight);
|
||||
}
|
||||
|
||||
public static bool operator !=(NullableObject<T> parLeft, NullableObject<T> parRight)
|
||||
{
|
||||
return !(parLeft == parRight);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
using Engine.Renderer;
|
||||
using Engine.Renderer.Pixel;
|
||||
using Engine.Renderer.Texture;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace Engine;
|
||||
|
||||
public class Window : IPresenter
|
||||
public class Window
|
||||
{
|
||||
public bool IsExiting => _window.IsExiting;
|
||||
public int Width { get; private set; }
|
||||
@@ -17,19 +13,19 @@ public class Window : IPresenter
|
||||
private readonly NativeWindow _window;
|
||||
private readonly bool _headless;
|
||||
|
||||
public Window(Engine engine, NativeWindow window, bool headless)
|
||||
public Window(Engine parEngine, NativeWindow parWindow, bool parHeadless)
|
||||
{
|
||||
_engine = engine;
|
||||
_window = window;
|
||||
_headless = headless;
|
||||
_engine = parEngine;
|
||||
_window = parWindow;
|
||||
_headless = parHeadless;
|
||||
|
||||
(Width, Height) = _window.ClientSize;
|
||||
|
||||
_window.MakeCurrent();
|
||||
_window.Resize += args =>
|
||||
_window.Resize += parArgs =>
|
||||
{
|
||||
Width = args.Width;
|
||||
Height = args.Height;
|
||||
Width = parArgs.Width;
|
||||
Height = parArgs.Height;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,45 +37,12 @@ public class Window : IPresenter
|
||||
_window.SwapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
public void Present(IConstTexture texture)
|
||||
{
|
||||
if (_headless)
|
||||
return;
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
_engine.Renderer.TextureInternal.Bind();
|
||||
|
||||
GL.Enable(EnableCap.Texture2D);
|
||||
GL.Begin(PrimitiveType.Quads);
|
||||
GL.Color3(1, 1, 1);
|
||||
|
||||
GL.TexCoord2(0, 0);
|
||||
GL.Vertex2(0, 0);
|
||||
|
||||
GL.TexCoord2(1, 0);
|
||||
GL.Vertex2(1, 0);
|
||||
|
||||
GL.TexCoord2(1, 1);
|
||||
GL.Vertex2(1, 1);
|
||||
|
||||
GL.TexCoord2(0, 1);
|
||||
GL.Vertex2(0, 1);
|
||||
|
||||
GL.End();
|
||||
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.Flush();
|
||||
|
||||
_engine.Renderer.TextureInternal.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeWindowExtensions
|
||||
{
|
||||
public static unsafe void SwapBuffers(this NativeWindow window)
|
||||
public static unsafe void SwapBuffers(this NativeWindow parWindow)
|
||||
{
|
||||
GLFW.SwapBuffers(window.WindowPtr);
|
||||
GLFW.SwapBuffers(parWindow.WindowPtr);
|
||||
}
|
||||
}
|
||||
129
PresenterConsole/src/ConsoleInputHandler.cs
Normal file
129
PresenterConsole/src/ConsoleInputHandler.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Engine.Input;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public class ConsoleInputHandler : IInputHandler
|
||||
{
|
||||
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
||||
private readonly HashSet<ConsoleKey> _previousKeys = [];
|
||||
private ConsoleModifiers _currentModifiers;
|
||||
private ConsoleModifiers _previousModifiers;
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
// Save previous state
|
||||
_previousKeys.Clear();
|
||||
foreach (var key in _currentKeys)
|
||||
{
|
||||
_previousKeys.Add(key);
|
||||
}
|
||||
|
||||
_previousModifiers = _currentModifiers;
|
||||
|
||||
// Clear current state
|
||||
_currentKeys.Clear();
|
||||
_currentModifiers = 0;
|
||||
|
||||
// Read keys
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
var keyInfo = Console.ReadKey(intercept: true);
|
||||
_currentKeys.Add(keyInfo.Key);
|
||||
_currentModifiers |= keyInfo.Modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKeyPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode);
|
||||
|
||||
return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
|
||||
}
|
||||
|
||||
public bool IsKeyJustPressed(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
|
||||
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
|
||||
}
|
||||
|
||||
public bool IsKeyRepeat(KeyCode keyCode)
|
||||
{
|
||||
if (IsModifierKey(keyCode))
|
||||
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
|
||||
|
||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
|
||||
}
|
||||
|
||||
public bool IsMouseButtonPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsMouseButtonJustPressed(MouseButton button)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
|
||||
|
||||
private bool IsModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
|
||||
KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
|
||||
KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
|
||||
{
|
||||
KeyCode.A => ConsoleKey.A,
|
||||
KeyCode.B => ConsoleKey.B,
|
||||
KeyCode.C => ConsoleKey.C,
|
||||
KeyCode.D => ConsoleKey.D,
|
||||
KeyCode.E => ConsoleKey.E,
|
||||
KeyCode.F => ConsoleKey.F,
|
||||
KeyCode.G => ConsoleKey.G,
|
||||
KeyCode.H => ConsoleKey.H,
|
||||
KeyCode.I => ConsoleKey.I,
|
||||
KeyCode.J => ConsoleKey.J,
|
||||
KeyCode.K => ConsoleKey.K,
|
||||
KeyCode.L => ConsoleKey.L,
|
||||
KeyCode.M => ConsoleKey.M,
|
||||
KeyCode.N => ConsoleKey.N,
|
||||
KeyCode.O => ConsoleKey.O,
|
||||
KeyCode.P => ConsoleKey.P,
|
||||
KeyCode.Q => ConsoleKey.Q,
|
||||
KeyCode.R => ConsoleKey.R,
|
||||
KeyCode.S => ConsoleKey.S,
|
||||
KeyCode.T => ConsoleKey.T,
|
||||
KeyCode.U => ConsoleKey.U,
|
||||
KeyCode.V => ConsoleKey.V,
|
||||
KeyCode.W => ConsoleKey.W,
|
||||
KeyCode.X => ConsoleKey.X,
|
||||
KeyCode.Y => ConsoleKey.Y,
|
||||
KeyCode.Z => ConsoleKey.Z,
|
||||
KeyCode.Enter => ConsoleKey.Enter,
|
||||
KeyCode.Escape => ConsoleKey.Escape,
|
||||
KeyCode.Space => ConsoleKey.Spacebar,
|
||||
KeyCode.Tab => ConsoleKey.Tab,
|
||||
KeyCode.Backspace => ConsoleKey.Backspace,
|
||||
KeyCode.Up => ConsoleKey.UpArrow,
|
||||
KeyCode.Down => ConsoleKey.DownArrow,
|
||||
KeyCode.Left => ConsoleKey.LeftArrow,
|
||||
KeyCode.Right => ConsoleKey.RightArrow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
|
||||
};
|
||||
}
|
||||
51
PresenterConsole/src/ConsolePresenter.cs
Normal file
51
PresenterConsole/src/ConsolePresenter.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Engine.Asset;
|
||||
using Engine.Renderer;
|
||||
using Engine.Renderer.Framebuffer;
|
||||
using Engine.Renderer.Texture;
|
||||
using OpenTK.Windowing.Common;
|
||||
|
||||
namespace PresenterConsole;
|
||||
|
||||
public class ConsolePresenter : IPresenter
|
||||
{
|
||||
public bool IsExiting => false;
|
||||
public int Width => Console.WindowWidth;
|
||||
public int Height => Console.WindowHeight;
|
||||
public event Action<ResizeEventArgs>? Resize;
|
||||
|
||||
private readonly Framebuffer _framebuffer;
|
||||
private Image<AsciiPixel> _asciiImage;
|
||||
|
||||
public ConsolePresenter(int width, int height)
|
||||
{
|
||||
_framebuffer = Framebuffer.Builder(width, height)
|
||||
.AddColorAttachment<AsciiPixel>()
|
||||
.Build();
|
||||
}
|
||||
|
||||
public void Present(IConstTexture texture)
|
||||
{
|
||||
var openglTexture = (Texture)texture;
|
||||
|
||||
_framebuffer.Bind();
|
||||
openglTexture.BindUnit();
|
||||
|
||||
// TODO: render with ascii shader
|
||||
|
||||
_framebuffer.Unbind();
|
||||
|
||||
var asciiTexture = _framebuffer.TextureInternal;
|
||||
if (asciiTexture == null)
|
||||
throw new InvalidOperationException("Framebuffer texture is null");
|
||||
|
||||
if (asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
|
||||
_asciiImage = new Image<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
|
||||
|
||||
asciiTexture.ReadPixels(_asciiImage);
|
||||
}
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user