apply formatting

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

282
.editorconfig Normal file
View File

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

View File

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

View File

@@ -3,30 +3,30 @@ using Engine.Renderer.Texture;
namespace Engine.Asset;
public class Image<T>(T[,] pixels)
where T : struct, IPixel
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])
{
}
public DynamicTexture ToDynamicTexture()
{
var texture = DynamicTexture.Create<T>(Width, Height);
texture.UploadPixels(this);
return texture;
}
public DynamicTexture ToDynamicTexture()
{
var texture = DynamicTexture.Create<T>(Width, Height);
texture.UploadPixels(this);
return texture;
}
public StaticTexture ToStaticTexture()
{
var texture = StaticTexture.Create<T>(Width, Height);
texture.UploadPixels(this);
return texture;
}
public StaticTexture ToStaticTexture()
{
var texture = StaticTexture.Create<T>(Width, Height);
texture.UploadPixels(this);
return texture;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,24 +6,21 @@ namespace Engine.Asset.Mesh;
public class Mesh
{
public IReadOnlyList<Vertex> Vertices => _vertices;
public IReadOnlyList<uint> Indices => _indices;
public IReadOnlyList<Vertex> Vertices => _vertices;
public IReadOnlyList<uint> Indices => _indices;
internal IList<Vertex> VerticesInternal => _vertices;
internal IList<uint> IndicesInternal => _indices;
internal IList<Vertex> VerticesInternal => _vertices;
internal IList<uint> IndicesInternal => _indices;
private readonly List<Vertex> _vertices = [];
private readonly List<uint> _indices = [];
private readonly List<Vertex> _vertices = [];
private readonly List<uint> _indices = [];
public record struct Vertex : IVertex
{
[Vertex(VertexAttribType.Float, 3)]
public Vector3 Position;
public record struct Vertex : IVertex
{
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
[Vertex(VertexAttribType.Float, 3)]
public Vector3 Normal;
[Vertex(VertexAttribType.Float, 3)] public Vector3 _normal;
[Vertex(VertexAttribType.Float, 2)]
public Vector2 Uv;
}
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
}
}

View File

@@ -1,4 +1,7 @@
using OpenTK.Graphics.OpenGL;
using System.Diagnostics;
using Engine.Input;
using Engine.Scene;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
@@ -9,51 +12,72 @@ namespace Engine;
public sealed class Engine
{
public Renderer.Renderer Renderer => _renderer;
public Renderer.Renderer Renderer => _renderer;
public SceneManager SceneManager => _sceneManager;
private readonly Window _window;
private readonly Renderer.Renderer _renderer;
private readonly ILogger _logger;
private readonly Window _window;
private readonly Renderer.Renderer _renderer;
private readonly SceneManager _sceneManager = new();
private readonly ILogger _logger;
private readonly IInputHandler _inputHandler;
public Engine(int width, int height, bool headless = false, string title = "")
private Thread? _updateThread;
public Engine(int parWidth, int parHeight, bool parHeadless = false, string parTitle = "")
{
var settings = new NativeWindowSettings
{
var settings = new NativeWindowSettings
{
ClientSize = headless ? new Vector2i(1, 1) : new Vector2i(width, height),
Title = title,
StartVisible = !headless,
APIVersion = new Version(4, 5),
Profile = ContextProfile.Compatability
};
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
Title = parTitle,
StartVisible = !parHeadless,
APIVersion = new Version(4, 5),
Profile = ContextProfile.Compatability
};
_window = new Window(this, new NativeWindow(settings), headless);
_renderer = new Renderer.Renderer(width, height);
_window = new Window(this, new NativeWindow(settings), parHeadless);
_renderer = new Renderer.Renderer(parWidth, parHeight);
Thread.CurrentThread.Name = "RendererThread";
Thread.CurrentThread.Name = "RendererThread";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithThreadName()
.Enrich.WithThreadId()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate:
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}",
theme: AnsiConsoleTheme.Literate)
.CreateLogger();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.WithThreadName()
.Enrich.WithThreadId()
.Enrich.FromLogContext()
.WriteTo.Console(
outputTemplate:
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}",
theme: AnsiConsoleTheme.Literate)
.CreateLogger();
_logger = Log.ForContext<Engine>();
_logger = Log.ForContext<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);
while (!_window.IsExiting)
{
_window.Update();
}
public void Run()
_updateThread.Join();
}
private void RunUpdate()
{
var timer = Stopwatch.StartNew();
while (!_window.IsExiting)
{
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Viewport(0, 0, _window.Width, _window.Height);
while (!_window.IsExiting)
{
_window.Update();
}
_window.Update();
_sceneManager.Update(timer.Elapsed.TotalSeconds);
timer.Restart();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,45 +6,61 @@ namespace Engine.Renderer.Buffer.Vertex;
public interface IVertex
{
public static IOrderedEnumerable<FieldInfo> GetFields<T>() => GetFields(typeof(T));
public static IOrderedEnumerable<FieldInfo> GetFields<T>()
{
return GetFields(typeof(T));
}
public static IOrderedEnumerable<FieldInfo> GetFields(Type type) =>
type.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(f => f.MetadataToken);
public static IOrderedEnumerable<FieldInfo> GetFields(Type parType)
{
return parType.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(parF => parF.MetadataToken);
}
public static bool IsValid(Type type)
public static bool IsValid(Type parType)
{
if (!parType.IsValueType || !parType.IsAssignableTo(typeof(IVertex)))
{
if (!type.IsValueType || !type.IsAssignableTo(typeof(IVertex)))
return false;
var fields = GetFields(type);
var totalSize = 0;
foreach (var field in fields)
{
if (!field.FieldType.IsValueType)
return false;
var attribute = field.GetCustomAttribute<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 false;
}
public static int AttributeSize(VertexAttribType type) => type switch
IOrderedEnumerable<FieldInfo>? fields = GetFields(parType);
var totalSize = 0;
foreach (var field in fields)
{
VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte),
VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short),
VertexAttribType.Int or VertexAttribType.UnsignedInt => sizeof(int),
VertexAttribType.HalfFloat => Marshal.SizeOf<Half>(),
VertexAttribType.Float => sizeof(float),
VertexAttribType.Double => sizeof(double),
_ => 0
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(parType);
}
public static int AttributeSize(VertexAttribType parType)
{
return parType switch
{
VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte),
VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short),
VertexAttribType.Int or VertexAttribType.UnsignedInt => sizeof(int),
VertexAttribType.HalfFloat => Marshal.SizeOf<Half>(),
VertexAttribType.Float => sizeof(float),
VertexAttribType.Double => sizeof(double),
_ => 0
};
}
}

View File

@@ -5,22 +5,27 @@ namespace Engine.Renderer.Buffer.Vertex;
[AttributeUsage(AttributeTargets.Field)]
public class VertexAttribute : Attribute
{
public VertexAttribType Type { get; }
public int ComponentCount { get; }
public bool Normalized { get; }
public int RepeatCount { get; }
public VertexAttribType Type { get; }
public int ComponentCount { get; }
public bool Normalized { get; }
public int RepeatCount { get; }
public VertexAttribute(VertexAttribType type, int componentCount = 1, bool normalized = false, int repeatCount = 1)
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
int parRepeatCount = 1)
{
if (parComponentCount <= 0)
{
if (componentCount <= 0)
throw new ArgumentException("Count must be greater than 0");
if (repeatCount <= 0)
throw new ArgumentException("Repeat must be greater than 0");
Type = type;
ComponentCount = componentCount;
Normalized = normalized;
RepeatCount = repeatCount;
throw new ArgumentException("Count must be greater than 0");
}
if (parRepeatCount <= 0)
{
throw new ArgumentException("Repeat must be greater than 0");
}
Type = parType;
ComponentCount = parComponentCount;
Normalized = parNormalized;
RepeatCount = parRepeatCount;
}
}

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ namespace Engine.Renderer.Camera;
public interface ICamera
{
public Matrix4 View { get; }
public Matrix4 Projection { get; }
public Vector2i ScreenSize { get; internal set; }
public Matrix4 View { get; }
public Matrix4 Projection { get; }
public Vector2i ScreenSize { get; internal set; }
}

View File

@@ -4,7 +4,7 @@ namespace Engine.Renderer.Camera;
public class ScreenspaceCamera : ICamera
{
public Matrix4 View => Matrix4.Identity;
public Matrix4 Projection => Matrix4.Identity;
public Vector2i ScreenSize { get; set; }
public Matrix4 View => Matrix4.Identity;
public Matrix4 Projection => Matrix4.Identity;
public Vector2i ScreenSize { get; set; }
}

View File

@@ -7,62 +7,63 @@ namespace Engine.Renderer;
internal static class Debug
{
public static void Setup()
public static void Setup()
{
GL.Enable(EnableCap.DebugOutput);
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
}
private static void DebugCallback(DebugSource parSource, DebugType parType, int parId, DebugSeverity parSeverity,
int parLength,
IntPtr parMessage, IntPtr parUserParam)
{
var logger = Log.ForContext<Engine>();
var messageText = Marshal.PtrToStringAnsi(parMessage, parLength) ?? "Unknown OpenGL Error";
var typePrefix = parType switch
{
GL.Enable(EnableCap.DebugOutput);
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
}
DebugType.DebugTypeError => "Error",
DebugType.DebugTypeDeprecatedBehavior => "Deprecated",
DebugType.DebugTypeUndefinedBehavior => "Undefined",
DebugType.DebugTypePortability => "Portability",
DebugType.DebugTypePerformance => "Performance",
DebugType.DebugTypeMarker => "Marker",
DebugType.DebugTypePushGroup => "PushGroup",
DebugType.DebugTypePopGroup => "PopGroup",
DebugType.DebugTypeOther => "Info",
_ => "Unknown"
};
private static void DebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length,
IntPtr message, IntPtr userParam)
var sourcePrefix = parSource switch
{
var logger = Log.ForContext<Engine>();
DebugSource.DebugSourceApi => "API",
DebugSource.DebugSourceWindowSystem => "Window",
DebugSource.DebugSourceShaderCompiler => "Shader",
DebugSource.DebugSourceThirdParty => "ThirdParty",
DebugSource.DebugSourceApplication => "Application",
DebugSource.DebugSourceOther => "Other",
_ => "Unknown"
};
var messageText = Marshal.PtrToStringAnsi(message, length) ?? "Unknown OpenGL Error";
logger.Write(GetLogLevel(parSeverity),
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
typePrefix,
sourcePrefix,
messageText,
parId
);
}
var typePrefix = type switch
{
DebugType.DebugTypeError => "Error",
DebugType.DebugTypeDeprecatedBehavior => "Deprecated",
DebugType.DebugTypeUndefinedBehavior => "Undefined",
DebugType.DebugTypePortability => "Portability",
DebugType.DebugTypePerformance => "Performance",
DebugType.DebugTypeMarker => "Marker",
DebugType.DebugTypePushGroup => "PushGroup",
DebugType.DebugTypePopGroup => "PopGroup",
DebugType.DebugTypeOther => "Info",
_ => "Unknown"
};
var sourcePrefix = source switch
{
DebugSource.DebugSourceApi => "API",
DebugSource.DebugSourceWindowSystem => "Window",
DebugSource.DebugSourceShaderCompiler => "Shader",
DebugSource.DebugSourceThirdParty => "ThirdParty",
DebugSource.DebugSourceApplication => "Application",
DebugSource.DebugSourceOther => "Other",
_ => "Unknown"
};
logger.Write(GetLogLevel(severity),
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
typePrefix,
sourcePrefix,
messageText,
id
);
}
private static LogEventLevel GetLogLevel(DebugSeverity severity)
private static LogEventLevel GetLogLevel(DebugSeverity parSeverity)
{
return parSeverity switch
{
return severity switch
{
DebugSeverity.DebugSeverityNotification => LogEventLevel.Information,
DebugSeverity.DebugSeverityHigh => LogEventLevel.Error,
DebugSeverity.DebugSeverityMedium => LogEventLevel.Warning,
DebugSeverity.DebugSeverityLow => LogEventLevel.Debug,
_ => LogEventLevel.Debug
};
}
DebugSeverity.DebugSeverityNotification => LogEventLevel.Information,
DebugSeverity.DebugSeverityHigh => LogEventLevel.Error,
DebugSeverity.DebugSeverityMedium => LogEventLevel.Warning,
DebugSeverity.DebugSeverityLow => LogEventLevel.Debug,
_ => LogEventLevel.Debug
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,18 +4,18 @@ namespace Engine.Renderer;
public abstract class OpenGlObject
{
public int Handle { get; protected set; } = -1;
public int Handle { get; protected set; } = -1;
internal abstract void Bind();
internal abstract void Unbind();
public abstract void Bind();
public abstract void Unbind();
protected abstract void Destroy();
protected abstract void Destroy();
~OpenGlObject()
{
Destroy();
Handle = -1;
~OpenGlObject()
{
Destroy();
Handle = -1;
Log.Debug("OpenGL object {Handle} destroyed", Handle);
}
Log.Debug("OpenGL object {Handle} destroyed", Handle);
}
}

View File

@@ -4,9 +4,9 @@ namespace Engine.Renderer.Pixel;
public interface IPixel
{
public PixelFormat Format { get; }
public PixelType Type { get; }
public PixelFormat Format { get; }
public PixelType Type { get; }
public PixelInternalFormat InternalFormat { get; }
public SizedInternalFormat SizedInternalFormat { get; }
public PixelInternalFormat InternalFormat { get; }
public SizedInternalFormat SizedInternalFormat { get; }
}

View File

@@ -6,11 +6,11 @@ namespace Engine.Renderer.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct R8 : IPixel
{
public PixelFormat Format => PixelFormat.Red;
public PixelType Type => PixelType.UnsignedByte;
public PixelFormat Format => PixelFormat.Red;
public PixelType Type => PixelType.UnsignedByte;
public PixelInternalFormat InternalFormat => PixelInternalFormat.R8;
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8;
public PixelInternalFormat InternalFormat => PixelInternalFormat.R8;
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.R8;
public byte R;
public byte R;
}

View File

@@ -6,13 +6,13 @@ namespace Engine.Renderer.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct Rgb8 : IPixel
{
public PixelFormat Format => PixelFormat.Rgb;
public PixelType Type => PixelType.UnsignedByte;
public PixelFormat Format => PixelFormat.Rgb;
public PixelType Type => PixelType.UnsignedByte;
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8;
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8;
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8;
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8;
public byte R;
public byte G;
public byte B;
public byte R;
public byte G;
public byte B;
}

View File

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

View File

@@ -9,47 +9,53 @@ namespace Engine.Renderer;
public class Renderer
{
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal;
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
private readonly Framebuffer.Framebuffer _framebuffer;
private readonly Queue<Action<Renderer>> _renderActions = new();
private readonly Framebuffer.Framebuffer _framebuffer;
private readonly Queue<Action<Renderer>> _renderActions = new();
public Renderer(int width, int height)
{
InitializeOpenGl();
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()
{
private void InitializeOpenGl()
{
#if DEBUG
Debug.Setup();
Debug.Setup();
#endif
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.FramebufferSrgb);
GL.Enable(EnableCap.FramebufferSrgb);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.Blend);
}
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.Blend);
}
internal void Commit(Action<Renderer> renderAction)
internal void Commit(Action<Renderer> parRenderAction)
{
_renderActions.Enqueue(parRenderAction);
}
internal void Render()
{
_framebuffer.Bind();
while (_renderActions.TryDequeue(out Action<Renderer>? renderAction))
{
_renderActions.Enqueue(renderAction);
renderAction(this);
}
internal void Render()
{
_framebuffer.Bind();
while (_renderActions.TryDequeue(out var renderAction))
renderAction(this);
_framebuffer.Unbind();
}
_framebuffer.Unbind();
}
internal void Resize(int width, int height)
{
_framebuffer.Resize(width, height);
}
internal void Resize(int parWidth, int parHeight)
{
_framebuffer.Resize(parWidth, parHeight);
}
}

View File

@@ -8,174 +8,179 @@ namespace Engine.Renderer.Shader;
public class ShaderProgram : OpenGlObject
{
private readonly Dictionary<string, int> _uniforms = new();
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 parSource.Split('\n'))
{
var vertexSource = new StringBuilder();
var fragmentSource = new StringBuilder();
var inFragment = false;
var inVertex = false;
foreach (var line in source.Split('\n'))
{
if (line.StartsWith("#shader vertex"))
{
inVertex = true;
inFragment = false;
}
else if (line.StartsWith("#shader fragment"))
{
inVertex = false;
inFragment = true;
}
else if (inVertex)
{
vertexSource.AppendLine(line);
}
else if (inFragment)
{
fragmentSource.AppendLine(line);
}
}
return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
if (line.StartsWith("#shader vertex"))
{
inVertex = true;
inFragment = false;
}
else if (line.StartsWith("#shader fragment"))
{
inVertex = false;
inFragment = true;
}
else if (inVertex)
{
vertexSource.AppendLine(line);
}
else if (inFragment)
{
fragmentSource.AppendLine(line);
}
}
public ShaderProgram(string vertexSource, string fragmentSource)
{
var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader);
return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
}
Handle = LinkProgram(vertexShader, fragmentShader);
public ShaderProgram(string parVertexSource, string parFragmentSource)
{
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
Handle = LinkProgram(vertexShader, fragmentShader);
}
public void SetUniform<T>(string parName, T parValue)
{
try
{
var location = GetUniformLocation(parName);
switch (parValue)
{
case bool boolValue:
GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0);
break;
case int intValue:
GL.ProgramUniform1(Handle, location, intValue);
break;
case float floatValue:
GL.ProgramUniform1(Handle, location, floatValue);
break;
case Vector2 vec2:
GL.ProgramUniform2(Handle, location, vec2);
break;
case Vector3 vec3:
GL.ProgramUniform3(Handle, location, vec3);
break;
case Vector4 vec4:
GL.ProgramUniform4(Handle, location, vec4);
break;
case Matrix4 matrix:
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
break;
default:
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to set uniform {UniformName}", parName);
throw;
}
}
public override void Bind()
{
GL.UseProgram(Handle);
}
public override void Unbind()
{
GL.UseProgram(0);
}
protected override void Destroy()
{
GL.DeleteProgram(Handle);
}
private int GetUniformLocation(string parName)
{
if (_uniforms.TryGetValue(parName, out var location))
{
return location;
}
public void SetUniform<T>(string name, T value, [CallerMemberName] string caller = "")
location = GL.GetUniformLocation(Handle, parName);
if (location < 0)
{
try
{
var location = GetUniformLocation(name);
switch (value)
{
case bool boolValue:
GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0);
break;
case int intValue:
GL.ProgramUniform1(Handle, location, intValue);
break;
case float floatValue:
GL.ProgramUniform1(Handle, location, floatValue);
break;
case Vector2 vec2:
GL.ProgramUniform2(Handle, location, vec2);
break;
case Vector3 vec3:
GL.ProgramUniform3(Handle, location, vec3);
break;
case Vector4 vec4:
GL.ProgramUniform4(Handle, location, vec4);
break;
case Matrix4 matrix:
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
break;
default:
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller);
throw;
}
throw new ArgumentException($"Uniform '{parName}' not found in shader program");
}
internal override void Bind()
_uniforms.Add(parName, location);
return location;
}
private static int CompileSource(string parSource, ShaderType parType)
{
var shaderId = GL.CreateShader(parType);
GL.ShaderSource(shaderId, parSource);
GL.CompileShader(shaderId);
GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status);
if (status == 0)
{
GL.UseProgram(Handle);
var log = GL.GetShaderInfoLog(shaderId);
GL.DeleteShader(shaderId);
throw new ShaderCompilationException(parType, log);
}
internal override void Unbind()
return shaderId;
}
private static int LinkProgram(int parVertexShader, int parFragmentShader)
{
var programId = GL.CreateProgram();
try
{
GL.UseProgram(0);
GL.AttachShader(programId, parVertexShader);
GL.AttachShader(programId, parFragmentShader);
GL.LinkProgram(programId);
GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus);
if (linkStatus == 0)
{
var log = GL.GetProgramInfoLog(programId);
throw new ShaderLinkException(log);
}
GL.ValidateProgram(programId);
GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus);
if (validateStatus == 0)
{
var log = GL.GetProgramInfoLog(programId);
throw new ShaderValidationException(log);
}
}
finally
{
GL.DeleteShader(parVertexShader);
GL.DeleteShader(parFragmentShader);
}
protected override void Destroy()
{
GL.DeleteProgram(Handle);
}
private int GetUniformLocation(string name)
{
if (_uniforms.TryGetValue(name, out var location))
return location;
location = GL.GetUniformLocation(Handle, name);
if (location < 0)
throw new ArgumentException($"Uniform '{name}' not found in shader program");
_uniforms.Add(name, location);
return location;
}
private static int CompileSource(string source, ShaderType type)
{
var shaderId = GL.CreateShader(type);
GL.ShaderSource(shaderId, source);
GL.CompileShader(shaderId);
GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status);
if (status == 0)
{
var log = GL.GetShaderInfoLog(shaderId);
GL.DeleteShader(shaderId);
throw new ShaderCompilationException(type, log);
}
return shaderId;
}
private static int LinkProgram(int vertexShader, int fragmentShader)
{
var programId = GL.CreateProgram();
try
{
GL.AttachShader(programId, vertexShader);
GL.AttachShader(programId, fragmentShader);
GL.LinkProgram(programId);
GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus);
if (linkStatus == 0)
{
var log = GL.GetProgramInfoLog(programId);
throw new ShaderLinkException(log);
}
GL.ValidateProgram(programId);
GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus);
if (validateStatus == 0)
{
var log = GL.GetProgramInfoLog(programId);
throw new ShaderValidationException(log);
}
}
finally
{
GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);
}
return programId;
}
return programId;
}
}
public class ShaderCompilationException(ShaderType type, string message)
: Exception($"Failed to compile {type} shader: {message}")
public class ShaderCompilationException(ShaderType parType, string parMessage)
: Exception($"Failed to compile {parType} shader: {parMessage}")
{
public ShaderType ShaderType { get; } = type;
public ShaderType ShaderType { get; } = parType;
}
public class ShaderLinkException(string message) : Exception($"Failed to link shader program: {message}");
public class ShaderLinkException(string parMessage) : Exception($"Failed to link shader program: {parMessage}");
public class ShaderValidationException(string message) : Exception($"Failed to validate shader program: {message}");
public class ShaderValidationException(string parMessage)
: Exception($"Failed to validate shader program: {parMessage}");

View File

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

View File

@@ -5,34 +5,42 @@ namespace Engine.Renderer.Texture;
public interface IConstTexture
{
public int Width { get; }
public int Height { get; }
public int Width { get; }
public int Height { get; }
public void ReadPixels<T>(int x, int y, int width, int height, T[,] pixels)
where T : struct, IPixel;
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)
where T : struct, IPixel
{
var pixels = new T[width, height];
texture.ReadPixels(x, y, width, height, pixels);
public static T[,] ReadPixels<T>(this IConstTexture parTexture, int parX, int parY, int parWidth, int parHeight)
where T : struct, IPixel
{
var pixels = new T[parWidth, parHeight];
parTexture.ReadPixels(parX, parY, parWidth, parHeight, pixels);
return pixels;
}
return pixels;
}
public static void ReadPixels<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);
}
}

View File

@@ -5,19 +5,25 @@ namespace Engine.Renderer.Texture;
public interface ITexture : IConstTexture
{
public void UploadPixels<T>(int x, int y, int width, int height, T[,] pixels)
where T : struct, IPixel;
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);
}
}

View File

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

View File

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

View File

@@ -4,29 +4,29 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public abstract class Camera(
float nearPlane,
float farPlane
float parNearPlane,
float parFarPlane
) : Component, ICamera
{
public float AspectRatio { get; private set; } = 1;
public float NearPlane { get; set; } = nearPlane;
public float FarPlane { get; set; } = farPlane;
public float AspectRatio { get; private set; } = 1;
public float NearPlane { get; set; } = parNearPlane;
public float FarPlane { get; set; } = parFarPlane;
private Vector2i _screenSize = new(1, 1);
private Vector2i _screenSize = new(1, 1);
public abstract Matrix4 View { get; }
public abstract Matrix4 Projection { get; }
public abstract Matrix4 View { get; }
public abstract Matrix4 Projection { get; }
public Vector2i ScreenSize
public Vector2i ScreenSize
{
get => _screenSize;
set
{
get => _screenSize;
set
{
_screenSize = value;
AspectRatio = (float)value.X / value.Y;
}
_screenSize = value;
AspectRatio = (float)value.X / value.Y;
}
}
public abstract Vector3 ScreenToWorld(Vector2 screenPosition);
public abstract Vector2 WorldToScreen(Vector3 worldPosition);
public abstract Vector3 ScreenToWorld(Vector2 parScreenPosition);
public abstract Vector2 WorldToScreen(Vector3 parWorldPosition);
}

View File

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

View File

@@ -4,37 +4,37 @@ using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public class PerspectiveCamera(
float fieldOfView = 90.0f,
float nearPlane = 0.1f,
float farPlane = 1000f
float parFieldOfView = 90.0f,
float parNearPlane = 0.1f,
float parFarPlane = 1000f
)
: Camera(nearPlane, farPlane)
: Camera(parNearPlane, parFarPlane)
{
public float FieldOfView { get; set; } = fieldOfView;
public float FieldOfView { get; set; } = parFieldOfView;
public override Matrix4 View
public override Matrix4 View
{
get
{
get
{
var transformMatrix = GameObject.Transform.TransformMatrix;
var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix);
var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix);
var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized();
var transformMatrix = GameObject.Transform.TransformMatrix;
var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix);
var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix);
var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized();
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
}
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
}
}
public override Matrix4 Projection =>
Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
public override Matrix4 Projection =>
Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
public override Vector3 ScreenToWorld(Vector2 screenPosition)
{
throw new NotImplementedException();
}
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
{
throw new NotImplementedException();
}
public override Vector2 WorldToScreen(Vector3 worldPosition)
{
throw new NotImplementedException();
}
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
{
throw new NotImplementedException();
}
}

View File

@@ -4,36 +4,31 @@ namespace Engine.Scene.Component.BuiltIn;
public class Transform : Component
{
public Vector3 Position { get; set; } = Vector3.Zero;
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public Vector3 Scale { get; set; } = Vector3.One;
public Vector3 LocalScale { get; set; } = Vector3.One;
public Vector3 Position { get; set; } = Vector3.Zero;
public Quaternion Rotation { get; set; } = Quaternion.Identity;
public Vector3 Scale { get; set; } = Vector3.One;
public Vector3 LocalScale { get; set; } = Vector3.One;
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
Matrix4.CreateFromQuaternion(Rotation) *
Matrix4.CreateTranslation(Position);
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
Matrix4.CreateFromQuaternion(Rotation) *
Matrix4.CreateTranslation(Position);
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
private Matrix4 ParentTransformMatrix
private Matrix4 ParentTransformMatrix
{
get
{
get
{
var parent = GameObject.Scene?.Hierarchy.GetParent(GameObject);
return parent == null ? Matrix4.Identity : parent.Transform.TransformMatrix;
}
var parent = GameObject.Scene?.Hierarchy.GetParent(GameObject);
return parent == null ? Matrix4.Identity : parent.Transform.TransformMatrix;
}
}
public Transform Clone()
{
var clone = new Transform
{
Position = Position,
Rotation = Rotation,
Scale = Scale,
LocalScale = LocalScale
};
public Transform Clone()
{
var clone =
new Transform { Position = Position, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale };
return clone;
}
return clone;
}
}

View File

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

View File

@@ -4,133 +4,155 @@ using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public sealed class GameObject
public sealed class GameObject : IUpdate, IRender
{
public Guid Id { get; } = Guid.NewGuid();
public Transform Transform { get; }
public Guid Id { get; } = Guid.NewGuid();
public Transform Transform { get; }
internal Scene? Scene { get; set; }
internal Scene? Scene { get; set; }
private readonly Queue<Action> _componentActions = new();
private readonly Queue<Action> _componentActions = new();
private readonly IList<Component.Component> _components = new List<Component.Component>();
private readonly ISet<Type> _addedComponentTypes = new HashSet<Type>();
private readonly IList<Component.Component> _components = new List<Component.Component>();
private readonly ISet<Type> _addedComponentTypes = new HashSet<Type>();
public GameObject()
public GameObject()
{
AddComponent<Transform>();
ProcessChanges();
Transform = GetComponent<Transform>()!;
}
public GameObject(Transform parTransform)
{
AddComponent(parTransform.Clone());
ProcessChanges();
Transform = GetComponent<Transform>()!;
}
public void Awake()
{
foreach (var component in _components)
{
AddComponent<Transform>();
ProcessChanges();
component.Awake();
}
}
Transform = GetComponent<Transform>()!;
public void Start()
{
foreach (var component in _components)
{
component.Start();
}
}
public void Update(double parDeltaTime)
{
foreach (var component in _components)
{
component.Update(parDeltaTime);
}
}
public void Render()
{
foreach (var component in _components)
{
component.Render();
}
}
public void Destroy()
{
foreach (var component in _components)
{
component.Destroy();
}
}
public T? GetComponent<T>() where T : Component.Component
{
return !HasComponent<T>() ? null : _components.OfType<T>().First();
}
public void AddComponent<T>() where T : Component.Component, new()
{
var component = new T();
AddComponent(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,
parArgs,
null
);
if (component == null)
{
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
}
public GameObject(Transform transform)
{
AddComponent(transform.Clone());
ProcessChanges();
AddComponent(component);
}
Transform = GetComponent<Transform>()!;
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)}");
}
parComponent.GameObject = this;
_components.Add(parComponent);
_addedComponentTypes.Add(typeof(T).GetComponentBaseType());
});
}
public void RemoveComponent<T>() where T : Component.Component
{
if (typeof(T) == typeof(Transform))
{
throw new ArgumentException("GameObject cannot remove Transform component");
}
public void Awake()
_componentActions.Enqueue(() =>
{
foreach (var component in _components)
component.Awake();
}
if (!HasComponent<T>())
{
throw new ArgumentException($"GameObject does not have component of type {typeof(T)}");
}
public void Start()
var component = GetComponent<T>();
if (component == null)
{
return;
}
_components.Remove(component);
_addedComponentTypes.Remove(typeof(T));
});
}
public bool HasComponent<T>() where T : Component.Component
{
var baseType = typeof(T).GetComponentBaseType();
return _addedComponentTypes.Contains(baseType);
}
internal void ProcessChanges()
{
while (_componentActions.TryDequeue(out var action))
{
foreach (var component in _components)
component.Start();
}
public void Update()
{
foreach (var component in _components)
component.Update();
}
public void Render()
{
foreach (var component in _components)
component.Render();
}
public void Destroy()
{
foreach (var component in _components)
component.Destroy();
}
public T? GetComponent<T>() where T : Component.Component
{
return !HasComponent<T>() ? null : _components.OfType<T>().First();
}
public void AddComponent<T>() where T : Component.Component, new()
{
var component = new T();
AddComponent(component);
}
public void AddComponent<T>(params object?[] args) where T : Component.Component
{
var component = (T?)Activator.CreateInstance(
typeof(T),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance |
BindingFlags.OptionalParamBinding,
null,
args,
null
);
if (component == null)
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
AddComponent(component);
}
public void AddComponent<T>(T component) 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);
_addedComponentTypes.Add(typeof(T).GetComponentBaseType());
});
}
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));
});
}
public bool HasComponent<T>() where T : Component.Component
{
var baseType = typeof(T).GetComponentBaseType();
return _addedComponentTypes.Contains(baseType);
}
internal void ProcessChanges()
{
while (_componentActions.TryDequeue(out var action))
action();
action();
}
}
}

View File

@@ -5,136 +5,156 @@ using Engine.Util;
namespace Engine.Scene;
public class Hierarchy<T> : IEnumerable<T>
where T : class
where T : class
{
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
private readonly Dictionary<T, T?> _parentLookup = new();
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
private readonly Dictionary<T, T?> _parentLookup = new();
private readonly ConcurrentQueue<Action> _hierarchyActions = new();
private readonly ConcurrentQueue<Action> _hierarchyActions = new();
public Hierarchy()
public Hierarchy()
{
_childrenLookup.Add(new NullableObject<T>(), new List<T>());
}
internal void ProcessChanges()
{
while (_hierarchyActions.TryDequeue(out var action))
{
_childrenLookup.Add(new NullableObject<T>(), new List<T>());
action();
}
}
public void Add(T parObj)
{
_hierarchyActions.Enqueue(() =>
{
if (_parentLookup.ContainsKey(parObj))
{
throw new ArgumentException("Object is already added to hierarchy");
}
_childrenLookup.Add(parObj, new List<T>());
_parentLookup.Add(parObj, null);
_childrenLookup[null].Add(parObj);
});
}
public void Remove(T parObj)
{
foreach (var child in GetChildren(parObj))
{
Remove(child);
}
internal void ProcessChanges()
_hierarchyActions.Enqueue(() =>
{
while (_hierarchyActions.TryDequeue(out var action))
action();
var parent = GetParent(parObj);
_childrenLookup[parent].Remove(parObj);
_parentLookup.Remove(parObj);
_childrenLookup.Remove(parObj);
});
}
public void AddChild(T parEnt, T parChild)
{
SetParent(parChild, parEnt);
}
private void SetParent(T parChild, T? parEnt)
{
if (parChild.Equals(parEnt))
{
throw new InvalidOperationException("Child cannot be parent");
}
public void Add(T obj)
_hierarchyActions.Enqueue(() =>
{
_hierarchyActions.Enqueue(() =>
{
if (_parentLookup.ContainsKey(obj))
throw new ArgumentException("Object is already added to hierarchy");
if (IsInHierarchy(parChild, parEnt))
{
throw new InvalidOperationException("Parent is a child of child");
}
_childrenLookup.Add(obj, new List<T>());
_parentLookup.Add(obj, null);
_childrenLookup[null].Add(obj);
});
var oldParent = GetParent(parChild);
_childrenLookup[oldParent].Remove(parChild);
_childrenLookup[parEnt].Add(parChild);
_parentLookup[parChild] = parEnt;
});
}
public bool Contains(T parObj)
{
return _parentLookup.ContainsKey(parObj) && _childrenLookup.ContainsKey(parObj);
}
public T? GetParent(T parChild)
{
return _parentLookup.TryGetValue(parChild, out var parent)
? parent
: throw new InvalidOperationException($"Child {parChild} is not in hierarchy");
}
public IEnumerable<T> GetChildren(T? parObj = null)
{
return _childrenLookup.TryGetValue(parObj, out IList<T>? children) ? children : Enumerable.Empty<T>();
}
public bool IsInHierarchy(T? parAncestor, T? parChild)
{
if (parChild == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
{
return false;
}
public void Remove(T obj)
if (parAncestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
{
foreach (var child in GetChildren(obj))
Remove(child);
_hierarchyActions.Enqueue(() =>
{
var parent = GetParent(obj);
_childrenLookup[parent].Remove(obj);
_parentLookup.Remove(obj);
_childrenLookup.Remove(obj);
});
return false;
}
public void AddChild(T parent, T child)
if (parAncestor.Equals(parChild))
{
SetParent(child, parent);
return true;
}
private void SetParent(T child, T? parent)
var parent = GetParent(parChild);
if (parent == null)
{
if (child.Equals(parent))
throw new InvalidOperationException("Child cannot be parent");
_hierarchyActions.Enqueue(() =>
{
if (IsInHierarchy(child, parent))
throw new InvalidOperationException("Parent is a child of child");
var oldParent = GetParent(child);
_childrenLookup[oldParent].Remove(child);
_childrenLookup[parent].Add(child);
_parentLookup[child] = parent;
});
return false;
}
public bool Contains(T obj)
if (parAncestor.Equals(parent))
{
return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj);
return true;
}
public T? GetParent(T child)
return IsInHierarchy(parAncestor, parent);
}
public IEnumerable<T> GetAllChildren(T? parObj = null)
{
IEnumerable<T>? children = GetChildren(parObj);
foreach (var child in children)
{
return _parentLookup.TryGetValue(child, out var parent)
? parent
: throw new InvalidOperationException($"Child {child} is not in hierarchy");
yield return child;
foreach (var descendant in GetAllChildren(child))
{
yield return descendant;
}
}
}
public IEnumerable<T> GetChildren(T? obj = null)
{
return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty<T>();
}
public IEnumerator<T> GetEnumerator()
{
return _parentLookup.Keys.GetEnumerator();
}
public bool IsInHierarchy(T? ancestor, T? child)
{
if (child == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
return false;
if (ancestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
return false;
if (ancestor.Equals(child))
return true;
var parent = GetParent(child);
if (parent == null)
return false;
if (ancestor.Equals(parent))
return true;
return IsInHierarchy(ancestor, parent);
}
public IEnumerable<T> GetAllChildren(T? obj = null)
{
var children = GetChildren(obj);
foreach (var child in children)
{
yield return child;
foreach (var descendant in GetAllChildren(child))
{
yield return descendant;
}
}
}
public IEnumerator<T> GetEnumerator()
{
return _parentLookup.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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