diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fb328c --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc +nupkg/ + +# Visual Studio Code +.vscode/* +!.vscode/settings.json + +# Rider +.idea/ + +# Visual Studio +.vs/ + +# Fleet +.fleet/ + +# Code Rush +.cr/ + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo].bj/ +[Oo]ut/ +msbuild.log +msbuild.err +msbuild.wrn \ No newline at end of file diff --git a/Giants.sln b/Giants.sln new file mode 100644 index 0000000..2c9bfe6 --- /dev/null +++ b/Giants.sln @@ -0,0 +1,50 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{824D17BD-3CFE-498A-967D-3F42DF5EE92D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Giants.Core", "Src\Giants.Core\Giants.Core.csproj", "{A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F34871C1-07BB-4012-A310-3FFDD6DC7DBA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Giants.Core.Tests", "Tests\Giants.Core.Tests\Giants.Core.Tests.csproj", "{30032CBC-0B7B-4BF3-9682-C506460F0FE6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Giants.Infrastructure", "Src\Giants.Infrastructure\Giants.Infrastructure.csproj", "{E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Giants.Application", "Src\Giants.Application\Giants.Application.csproj", "{1EF04517-2D31-4130-A687-7954A5431312}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0}.Release|Any CPU.Build.0 = Release|Any CPU + {30032CBC-0B7B-4BF3-9682-C506460F0FE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30032CBC-0B7B-4BF3-9682-C506460F0FE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30032CBC-0B7B-4BF3-9682-C506460F0FE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30032CBC-0B7B-4BF3-9682-C506460F0FE6}.Release|Any CPU.Build.0 = Release|Any CPU + {E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F}.Release|Any CPU.Build.0 = Release|Any CPU + {1EF04517-2D31-4130-A687-7954A5431312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EF04517-2D31-4130-A687-7954A5431312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EF04517-2D31-4130-A687-7954A5431312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EF04517-2D31-4130-A687-7954A5431312}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A7B8B752-2AE9-4FB2-8F51-733F96C4CFD0} = {824D17BD-3CFE-498A-967D-3F42DF5EE92D} + {30032CBC-0B7B-4BF3-9682-C506460F0FE6} = {F34871C1-07BB-4012-A310-3FFDD6DC7DBA} + {E6B2C76F-9CE2-4ADA-97F9-CEE24D48B65F} = {824D17BD-3CFE-498A-967D-3F42DF5EE92D} + {1EF04517-2D31-4130-A687-7954A5431312} = {824D17BD-3CFE-498A-967D-3F42DF5EE92D} + EndGlobalSection +EndGlobal diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..6645404 --- /dev/null +++ b/README.adoc @@ -0,0 +1,6 @@ +# Giants + +Projet d'adaptation du jeux de société Giants par matagot + +## Sources +- Lib de map hexagonal : HexagonalLib implementation for .NET(https://github.com/imurashka/HexagonalLib?tab=readme-ov-file) \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index f752b57..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Giants - diff --git a/Src/Giants.Application/Giants.Application.csproj b/Src/Giants.Application/Giants.Application.csproj new file mode 100644 index 0000000..e93fa9f --- /dev/null +++ b/Src/Giants.Application/Giants.Application.csproj @@ -0,0 +1,14 @@ + + + + + + + + + net8.0 + enable + enable + + + diff --git a/Src/Giants.Application/Src/GiantApplication.cs b/Src/Giants.Application/Src/GiantApplication.cs new file mode 100644 index 0000000..21bb61b --- /dev/null +++ b/Src/Giants.Application/Src/GiantApplication.cs @@ -0,0 +1,18 @@ +namespace Giants.Application; + +using Giants.Core.Interfaces; +using Giants.Infrastructure; + +/// +/// Une application Giants permettant l'instanciations des services, utilisé pour une console +/// ou autre archi sans injections de dependances +/// /// +public class GiantApplication +{ + + /// Constructeur de base + public GiantApplication() + { + IHexagonalGrid grid = new HexagonalGridImpl(); + } +} \ No newline at end of file diff --git a/Src/Giants.Core/Giants.Core.csproj b/Src/Giants.Core/Giants.Core.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Src/Giants.Core/Giants.Core.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Src/Giants.Core/Src/Entities/Match.cs b/Src/Giants.Core/Src/Entities/Match.cs new file mode 100644 index 0000000..308797c --- /dev/null +++ b/Src/Giants.Core/Src/Entities/Match.cs @@ -0,0 +1,23 @@ +using Giants.Core.Interfaces; + +namespace Giants.Core; + +/// +/// Il s'agit d'une instance d'une partie de Giants, c'est la racine de toutes les entitées de l'instance +/// /// +public class Match +{ + #region données statiques + IHexagonalGrid _grid; + #endregion + + #region données dynamiques + #endregion + + /// Construction de base + /// La grid du jeu + public Match(IHexagonalGrid grid) + { + _grid = grid; + } +} \ No newline at end of file diff --git a/Src/Giants.Core/Src/Interfaces/IHexagonalGrid.cs b/Src/Giants.Core/Src/Interfaces/IHexagonalGrid.cs new file mode 100644 index 0000000..037403c --- /dev/null +++ b/Src/Giants.Core/Src/Interfaces/IHexagonalGrid.cs @@ -0,0 +1,9 @@ +namespace Giants.Core.Interfaces; + +/// +/// represente un outils qui permet de gérer une grille hexagonal +/// +public interface IHexagonalGrid +{ + +} \ No newline at end of file diff --git a/Src/Giants.Core/Src/ValuesObjects/BoardLayout.cs b/Src/Giants.Core/Src/ValuesObjects/BoardLayout.cs new file mode 100644 index 0000000..b8960bd --- /dev/null +++ b/Src/Giants.Core/Src/ValuesObjects/BoardLayout.cs @@ -0,0 +1,9 @@ +namespace Giants.Core; + +/// +/// Represente l'organisation d'un plateau, il ne contient aucune informations liées a une partie mais il permet +/// d'effectuer des recherches de chemins ou de type de tuiles +/// +public class BoardLayout +{ +} diff --git a/Src/Giants.Core/Src/ValuesObjects/TileInfos.cs b/Src/Giants.Core/Src/ValuesObjects/TileInfos.cs new file mode 100644 index 0000000..50ce365 --- /dev/null +++ b/Src/Giants.Core/Src/ValuesObjects/TileInfos.cs @@ -0,0 +1,8 @@ +namespace Giants.Core; + +/// +/// Represente les information statiques d'une case. Ses voisins, son type, aucune informations qui peut changer pendant une partie +/// +public class TileInfos +{ +} \ No newline at end of file diff --git a/Src/Giants.Core/Src/ValuesObjects/TilePosition.cs b/Src/Giants.Core/Src/ValuesObjects/TilePosition.cs new file mode 100644 index 0000000..cef932d --- /dev/null +++ b/Src/Giants.Core/Src/ValuesObjects/TilePosition.cs @@ -0,0 +1,5 @@ +namespace Giants.Core; + +public class TilePosition +{ +} \ No newline at end of file diff --git a/Src/Giants.Infrastructure/Giants.Infrastructure.csproj b/Src/Giants.Infrastructure/Giants.Infrastructure.csproj new file mode 100644 index 0000000..ec2a73a --- /dev/null +++ b/Src/Giants.Infrastructure/Giants.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Src/Giants.Infrastructure/Src/HexagonalGridImpl.cs b/Src/Giants.Infrastructure/Src/HexagonalGridImpl.cs new file mode 100644 index 0000000..a270b71 --- /dev/null +++ b/Src/Giants.Infrastructure/Src/HexagonalGridImpl.cs @@ -0,0 +1,16 @@ + +namespace Giants.Infrastructure; + +using Giants.Core.Interfaces; + +using HexagonalLib; + +public class HexagonalGridImpl : IHexagonalGrid +{ + private readonly HexagonalGrid _grid; + + public HexagonalGridImpl() + { + _grid = new HexagonalGrid(HexagonalGridType.PointyEven, 1.0f); + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/README.md b/Src/HexagonalLib/README.md new file mode 100644 index 0000000..69bf62a --- /dev/null +++ b/Src/HexagonalLib/README.md @@ -0,0 +1,127 @@ +# HexagonalLib implementation for .NET + +_I was highly inspired by this article: [https://www.redblobgames.com/grids/hexagons/](https://www.redblobgames.com/grids/hexagons/) and this repo is mostly just C# implementation of it. Recommended for reading. Here I will describe some technical details regarding implementation._ + +## Hexagonal grid + +To use hexagonal grids you need to create [HexagonalGrid](src/HexagonalLib/HexagonalGrid.cs) and initialize it with grid type and inscribed radius of hex. + +```C# +var grid = new HexagonalGrid(HexagonalGridType.PointyEven, 1.0f); +``` + +Grids layouts and orientations are merged to one enum [HexagonalGridType](src/HexagonalLib/HexagonalGridType.cs). + +* PointyOdd - Horizontal layout shoves odd rows right [odd-r] +* PointyEven - Horizontal layout shoves even rows right [even-r] +* FlatOdd - Vertical layout shoves odd columns down [odd-q] +* FlatEven - Vertical layout shoves even columns down [even-q] + + +## Coordinates systems + +There is three coordinates systems represented in lib: + +* [Offset](src/HexagonalLib/Coordinates/Offset.cs) - offset coordinates ([link](https://www.redblobgames.com/grids/hexagons/#coordinates-offset)) +* [Cubic](src/HexagonalLib/Coordinates/Cubic.cs) - cube coordinates ([link](https://www.redblobgames.com/grids/hexagons/#coordinates-cube)) +* [Axial](src/HexagonalLib/Coordinates/Axial.cs) - axial coordinates ([link](https://www.redblobgames.com/grids/hexagons/#coordinates-axial)) + +_Note: there is no [Doubled](https://www.redblobgames.com/grids/hexagons/#coordinates-doubled) coordinates yet. I will add them in later versions._ + + +Struct [HexagonalGrid](src/HexagonalLib/HexagonalGrid.cs) contains a bunch of methods for coordinates conversion (all details are [described](https://www.redblobgames.com/grids/hexagons/#conversions) in original article): + +```C# +var offsetFromCubic = grid.ToOffset(cubic); +var offsetFromAxial = grid.ToOffset(axial); + +var cubicFromOffset = grid.ToCubic(offset); +var cubicFromAxial = grid.ToCubic(axial); + +var axialFromOffset = grid.ToAxial(offset); +var axialFromCubic = grid.ToAxial(cubic); +``` + +## Neighbors + +There is the order of neighbors of hex: + +![](img/hex-neighbors-order.png) + +You can take a neighbor of any hex by providing a coordinate of hex and required neighbor index. + +```C# +var oNeighbor = grid.GetNeighbor(offset, neighborIndex); +var cNeighbor = grid.GetNeighbor(cubic, neighborIndex); +var aNeighbor = grid.GetNeighbor(axial, neighborIndex); +``` +_The neighbor index can be negative or greater than 5 (it will be normalized)_ + +Or you can take all neighbors of particular hex + +```C# +var oNeighbors = grid.GetNeighbors(offset); +var cNeighbors = grid.GetNeighbors(cubic); +var aNeighbors = grid.GetNeighbors(axial); +``` + +There is also option to check are two hexes neighbors or not: + +```C# +var isNeighbors1 = grid.IsNeighbors(offset, oNeighbor); +var isNeighbors2 = grid.IsNeighbors(cubic, cNeighbor); +var isNeighbors3 = grid.IsNeighbors(axial, aNeighbor); +``` + +### Ring of neighbors +You also can get ring of neighbor for particular hex + +![](img/hex-rings.png) + +```C# +var oNeighbors = grid.GetNeighborsRing(offset, radius); +var cNeighbors = grid.GetNeighborsRing(cubic, radius); +var aNeighbors = grid.GetNeighborsRing(axial, radius); +``` + +### Circle of neighbors +Same as ring but you will receive hexes from all rings + +![](img/hex-around.png) + +```C# +var oNeighbors = grid.GetNeighborsAround(offset, radius); +var cNeighbors = grid.GetNeighborsAround(cubic, radius); +var aNeighbors = grid.GetNeighborsAround(axial, radius); +``` + +## 2D space + +There is also list of methods to convert hex coordinate to its center (point represented as tuple): + +```C# +var pointFromOffset = grid.ToPoint2(offset); +var pointFromCubic = grid.ToPoint2(cubic); +var pointFromAxial = grid.ToPoint2(axial); +``` + +To convert point into a hex coordinate which contains this point you can use: + +```C# +var offsetFromPoint = grid.ToOffset(x, y); +var cubicFromPoint = grid.ToCubic(x, y); +var axialFromPoint = grid.ToAxial(x, y); +``` + +Hexagons have 6 sides and 6 corners. There is corners order of the hexes: + +![](img/hex-corners-order.png) + +To take them in code simly use: + +```C# +var cornerFromOffset = grid.GetCorner(offset, cornerIndex); +var cornerFromCubic = grid.GetCorner(cubic, cornerIndex); +var cornerFromAxial = grid.GetCorner(axial, cornerIndex); +``` +_The corner index can be negative or greater than 5 (it will be normalized)_ \ No newline at end of file diff --git a/Src/HexagonalLib/img/hex-around.png b/Src/HexagonalLib/img/hex-around.png new file mode 100644 index 0000000..10b0e8a Binary files /dev/null and b/Src/HexagonalLib/img/hex-around.png differ diff --git a/Src/HexagonalLib/img/hex-corners-order.png b/Src/HexagonalLib/img/hex-corners-order.png new file mode 100644 index 0000000..403a092 Binary files /dev/null and b/Src/HexagonalLib/img/hex-corners-order.png differ diff --git a/Src/HexagonalLib/img/hex-neighbors-order.png b/Src/HexagonalLib/img/hex-neighbors-order.png new file mode 100644 index 0000000..696f9ac Binary files /dev/null and b/Src/HexagonalLib/img/hex-neighbors-order.png differ diff --git a/Src/HexagonalLib/img/hex-rings.png b/Src/HexagonalLib/img/hex-rings.png new file mode 100644 index 0000000..14661f1 Binary files /dev/null and b/Src/HexagonalLib/img/hex-rings.png differ diff --git a/Src/HexagonalLib/src/.editorconfig b/Src/HexagonalLib/src/.editorconfig new file mode 100644 index 0000000..b9c21e4 --- /dev/null +++ b/Src/HexagonalLib/src/.editorconfig @@ -0,0 +1,70 @@ +[*] +charset=utf-8 +end_of_line=crlf +trim_trailing_whitespace=false +insert_final_newline=false +indent_style=space +indent_size=4 + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers=false +csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_style_var_elsewhere=true:suggestion +csharp_style_var_for_built_in_types=true:suggestion +csharp_style_var_when_type_is_apparent=true:suggestion +dotnet_naming_rule.private_constants_rule.severity=warning +dotnet_naming_rule.private_constants_rule.style=upper_camel_case_style +dotnet_naming_rule.private_constants_rule.symbols=private_constants_symbols +dotnet_naming_rule.private_static_readonly_rule.severity=warning +dotnet_naming_rule.private_static_readonly_rule.style=lower_camel_case_style +dotnet_naming_rule.private_static_readonly_rule.symbols=private_static_readonly_symbols +dotnet_naming_style.lower_camel_case_style.capitalization=camel_case +dotnet_naming_style.lower_camel_case_style.required_prefix=_ +dotnet_naming_style.upper_camel_case_style.capitalization=pascal_case +dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities=private +dotnet_naming_symbols.private_constants_symbols.applicable_kinds=field +dotnet_naming_symbols.private_constants_symbols.required_modifiers=const +dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities=private +dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds=field +dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers=static,readonly +dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion +dotnet_style_predefined_type_for_member_access=true:suggestion +dotnet_style_qualification_for_event=false:suggestion +dotnet_style_qualification_for_field=false:suggestion +dotnet_style_qualification_for_method=false:suggestion +dotnet_style_qualification_for_property=false:suggestion +dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion + +# ReSharper properties +resharper_autodetect_indent_settings=true +resharper_csharp_max_attribute_length_for_same_line=80 +resharper_csharp_max_line_length=170 +resharper_keep_existing_attribute_arrangement=true +resharper_max_attribute_length_for_same_line=20 +resharper_place_field_attribute_on_same_line=false +resharper_use_indent_from_vs=false + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting=hint +resharper_arrange_this_qualifier_highlighting=hint +resharper_arrange_type_member_modifiers_highlighting=hint +resharper_arrange_type_modifiers_highlighting=hint +resharper_built_in_type_reference_style_for_member_access_highlighting=hint +resharper_built_in_type_reference_style_highlighting=hint +resharper_member_can_be_private_global_highlighting=none +resharper_partial_type_with_single_part_highlighting=none +resharper_redundant_base_qualifier_highlighting=warning +resharper_suggest_var_or_type_built_in_types_highlighting=hint +resharper_suggest_var_or_type_elsewhere_highlighting=hint +resharper_suggest_var_or_type_simple_types_highlighting=hint +resharper_web_config_module_not_resolved_highlighting=warning +resharper_web_config_type_not_resolved_highlighting=warning +resharper_web_config_wrong_module_highlighting=warning + +[*.{appxmanifest,asax,ascx,aspx,build,cs,cshtml,dtd,fs,fsi,fsscript,fsx,master,ml,mli,nuspec,razor,resw,resx,skin,vb,xaml,xamlx,xoml,xsd}] +indent_style=space +indent_size=4 +tab_width=4 diff --git a/Src/HexagonalLib/src/.gitignore b/Src/HexagonalLib/src/.gitignore new file mode 100644 index 0000000..a064aee --- /dev/null +++ b/Src/HexagonalLib/src/.gitignore @@ -0,0 +1,4 @@ +.bin/* +.obj/* +.idea/* +.packages/* \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalGridTests.cs b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalGridTests.cs new file mode 100644 index 0000000..7b0979d --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalGridTests.cs @@ -0,0 +1,135 @@ +using HexagonalLib.Coordinates; +using NUnit.Framework; +using static System.Math; + +namespace HexagonalLib.Tests +{ + [TestFixture(TestOf = typeof(HexagonalGrid))] + public class HexagonalGridTests + { + private float InscribedRadius => 0.5f; + private float DescribedRadius => (float) (InscribedRadius / Cos(PI / HexagonalGrid.EdgesCount)); + + [Test(Author = "Ivan Murashka", Description = "Check initial properties values for flat grids after creation")] + [TestCase(HexagonalGridType.FlatEven)] + [TestCase(HexagonalGridType.FlatOdd)] + public void FlatPropertiesTest(HexagonalGridType type) + { + var grid = new HexagonalGrid(type, InscribedRadius); + Assert.AreEqual(grid.InscribedRadius, InscribedRadius); + Assert.AreEqual(grid.DescribedRadius, DescribedRadius); + Assert.AreEqual(grid.InscribedDiameter, InscribedRadius * 2); + Assert.AreEqual(grid.DescribedDiameter, DescribedRadius * 2); + Assert.AreEqual(grid.HorizontalOffset, DescribedRadius * 1.5f); + Assert.AreEqual(grid.VerticalOffset, InscribedRadius * 2.0f); + Assert.AreEqual(grid.Side, DescribedRadius); + Assert.AreEqual(grid.AngleToFirstNeighbor, 30.0f); + } + + [Test(Author = "Ivan Murashka", Description = "Check initial properties values for pointy grids after creation")] + [TestCase(HexagonalGridType.PointyEven)] + [TestCase(HexagonalGridType.PointyOdd)] + public void PointyPropertiesTest(HexagonalGridType type) + { + var grid = new HexagonalGrid(type, InscribedRadius); + Assert.AreEqual(grid.InscribedRadius, InscribedRadius); + Assert.AreEqual(grid.DescribedRadius, DescribedRadius); + Assert.AreEqual(grid.InscribedDiameter, InscribedRadius * 2); + Assert.AreEqual(grid.DescribedDiameter, DescribedRadius * 2); + Assert.AreEqual(grid.HorizontalOffset, InscribedRadius * 2.0f); + Assert.AreEqual(grid.VerticalOffset, DescribedRadius * 1.5f); + Assert.AreEqual(grid.Side, DescribedRadius); + Assert.AreEqual(grid.AngleToFirstNeighbor, 0.0f); + } + + [Test(Author = "Ivan Murashka", Description = "Check coordinate conversion from Offset")] + public void CoordinateConversionTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + Assert.IsTrue(cubic.IsValid(), $"Invalid cubic coordinate: {cubic.X}-{cubic.Y}-{cubic.Z}"); + Assert.AreEqual(offset, grid.ToOffset(axial)); + Assert.AreEqual(offset, grid.ToOffset(cubic)); + Assert.AreEqual(axial, grid.ToAxial(offset)); + Assert.AreEqual(axial, grid.ToAxial(cubic)); + Assert.AreEqual(cubic, grid.ToCubic(offset)); + Assert.AreEqual(cubic, grid.ToCubic(axial)); + } + + [Test(Author = "Ivan Murashka", Description = "Check conversion to Point2 from Offset")] + public void PointConversionTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + + var fromOffset = grid.ToPoint2(offset); + var fromAxial = grid.ToPoint2(axial); + var fromCubic = grid.ToPoint2(cubic); + + Assert.IsTrue(fromOffset.SimilarTo(fromAxial), $"Expected: {fromAxial.X}:{fromAxial.Y}; Actual: {fromOffset.X}:{fromOffset.Y}"); + Assert.IsTrue(fromOffset.SimilarTo(fromCubic), $"Expected: {fromCubic.X}:{fromCubic.Y}; Actual: {fromOffset.X}:{fromOffset.Y}"); + + Assert.AreEqual(offset, grid.ToOffset(fromOffset)); + Assert.AreEqual(axial, grid.ToAxial(fromAxial)); + Assert.AreEqual(cubic, grid.ToCubic(fromCubic)); + } + + [Test(Author = "Ivan Murashka", Description = "Check IsNeighbor methods for all coordinates types")] + public void IsNeighborTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY, + [Values(-1, 0, 1, 2, 3, 4, 5, 6)] int neighborIndex) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + Assert.IsTrue(cubic.IsValid(), $"Invalid cubic coordinate: {cubic.X}-{cubic.Y}-{cubic.Z}"); + + var oNeighbor = grid.GetNeighbor(offset, neighborIndex); + var aNeighbor = grid.GetNeighbor(axial, neighborIndex); + var cNeighbor = grid.GetNeighbor(cubic, neighborIndex); + + Assert.IsTrue(cNeighbor.IsValid(), $"Invalid cubic coordinate: {cNeighbor.X}-{cNeighbor.Y}-{cNeighbor.Z}"); + Assert.IsTrue(grid.IsNeighbors(offset, oNeighbor), $"Neighbor1={offset}; Neighbor2={oNeighbor}; Index={neighborIndex};"); + Assert.IsTrue(grid.IsNeighbors(axial, aNeighbor), $"Neighbor1={axial}; Neighbor2={aNeighbor}; Index={neighborIndex};"); + Assert.IsTrue(grid.IsNeighbors(cubic, cNeighbor), $"Neighbor1={cubic}; Neighbor2={cNeighbor}; Index={neighborIndex};"); + } + + [Test(Author = "Ivan Murashka", Description = "Check neighbors order for all coordinates types")] + public void NeighborsOrderTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY, + [Values(-1, 0, 1, 2, 3, 4, 5, 6)] int neighborIndex) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + + var oNeighbor = grid.GetNeighbor(offset, neighborIndex); + var aNeighbor = grid.GetNeighbor(axial, neighborIndex); + var cNeighbor = grid.GetNeighbor(cubic, neighborIndex); + + Assert.IsTrue(cNeighbor.IsValid(), $"Invalid cubic coordinate: {cNeighbor.X}-{cNeighbor.Y}-{cNeighbor.Z}"); + + var fromAxial = grid.ToOffset(aNeighbor); + var fromCubic = grid.ToOffset(cNeighbor); + + Assert.AreEqual(oNeighbor, fromAxial, $"Center=({offset} - {axial}); Current=({oNeighbor} - {aNeighbor}); Index={neighborIndex};"); + Assert.AreEqual(oNeighbor, fromCubic, $"Center=({offset} - {cubic}); Current=({oNeighbor} - {cNeighbor}); Index={neighborIndex};"); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalLib.Net.Tests.csproj b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalLib.Net.Tests.csproj new file mode 100644 index 0000000..ce25b27 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalLib.Net.Tests.csproj @@ -0,0 +1,26 @@ + + + + HexagonalLib.Tests + false + net472;net48;netcoreapp3.1 + + + + ..\..\.bin\HexagonalLib.Net.Tests\Debug\ + + + + ..\..\.bin\HexagonalLib.Net.Tests\Release\ + + + + + + + + + + + + diff --git a/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalMathTests.cs b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalMathTests.cs new file mode 100644 index 0000000..eb7f9e3 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net.Tests/HexagonalMathTests.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; + +namespace HexagonalLib.Tests +{ + [TestFixture(TestOf = typeof(HexagonalMath))] + public class HexagonalMathTests + { + [Test(Author = "Ivan Murashka", Description = "Check HexagonalMath.Rotate method")] + [TestCase(0, 1, 0, 0, 1)] + [TestCase(0, 1, 45, 0.5f, 0.5f)] + [TestCase(0, 1, 90, 1, 0)] + [TestCase(0, 1, 135, 0.5f, -0.5f)] + [TestCase(0, 1, 180, 0, -1)] + [TestCase(0, 1, 225, -0.5f, -0.5f)] + [TestCase(0, 1, 270, -1, 0)] + [TestCase(0, 1, 315, -0.5f, 0.5f)] + [TestCase(0, 1, 360, 0, 1)] + public void RotateTest(float x, float y, float degrees, float resultX, float resultY) + { + var vector = (x, y).Rotate(degrees); + var result = (resultX, resultY).Normalize(); + Assert.IsTrue(vector.SimilarTo(result), $"Expected: {result.X}:{result.Y}; Actual: {vector.X}:{vector.Y}"); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net/HexagonalLib.Net.csproj b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net/HexagonalLib.Net.csproj new file mode 100644 index 0000000..400ca8b --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Net/HexagonalLib.Net/HexagonalLib.Net.csproj @@ -0,0 +1,23 @@ + + + + HexagonalLib + net472;net48;netcoreapp3.1 + true + HexagonalLib + Ivan Murashka + 1.0.0 + https://github.com/imurashka/HexagonalLib + + + + ..\..\.bin\HexagonalLib.Net\Debug\ + + + + ..\..\.bin\HexagonalLib.Net\Release\ + + + + + diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalGridTests.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalGridTests.cs new file mode 100644 index 0000000..d5de6c4 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalGridTests.cs @@ -0,0 +1,57 @@ +using HexagonalLib.Coordinates; +using NUnit.Framework; + +namespace HexagonalLib.Tests +{ + [TestFixture] + public class HexagonalGridTests + { + private float InscribedRadius => 0.5f; + + [Test(Author = "Ivan Murashka", Description = "Check coordinate conversion from Offset to Unity vectors")] + public void Vector2ConversionTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + + var fromOffset = grid.ToVector2(offset); + var fromAxial = grid.ToVector2(axial); + var fromCubic = grid.ToVector2(cubic); + + Assert.IsTrue(fromOffset.SimilarTo(fromAxial), $"Expected: {fromAxial}; Actual: {fromOffset}"); + Assert.IsTrue(fromOffset.SimilarTo(fromCubic), $"Expected: {fromCubic}; Actual: {fromOffset}"); + + Assert.AreEqual(offset, grid.ToOffset(fromOffset)); + Assert.AreEqual(axial, grid.ToAxial(fromAxial)); + Assert.AreEqual(cubic, grid.ToCubic(fromCubic)); + } + + [Test(Author = "Ivan Murashka", Description = "Check coordinate conversion from Offset to Unity vectors")] + public void Vector3ConversionTest( + [Values] HexagonalGridType type, + [Values(-13, -8, 0, 15, 22)] int offsetX, + [Values(-13, -8, 0, 15, 22)] int offsetY) + { + var grid = new HexagonalGrid(type, InscribedRadius); + var offset = new Offset(offsetX, offsetY); + var axial = grid.ToAxial(offset); + var cubic = grid.ToCubic(offset); + + var fromOffset = grid.ToVector3(offset); + var fromAxial = grid.ToVector3(axial); + var fromCubic = grid.ToVector3(cubic); + + Assert.IsTrue(fromOffset.SimilarTo(fromAxial), $"Expected: {fromAxial}; Actual: {fromOffset}"); + Assert.IsTrue(fromOffset.SimilarTo(fromCubic), $"Expected: {fromCubic}; Actual: {fromOffset}"); + + Assert.AreEqual(offset, grid.ToOffset(fromOffset)); + Assert.AreEqual(axial, grid.ToAxial(fromAxial)); + Assert.AreEqual(cubic, grid.ToCubic(fromCubic)); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalLib.Unity.Tests.csproj b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalLib.Unity.Tests.csproj new file mode 100644 index 0000000..a813ad0 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/HexagonalLib.Unity.Tests.csproj @@ -0,0 +1,78 @@ + + + + + + Debug + AnyCPU + {1E807875-014C-4745-86AB-E1E35916F8B3} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + HexagonalLib.Tests + HexagonalLib.Unity.Tests + v4.7.2 + 512 + + + AnyCPU + true + full + false + ..\..\.bin\HexagonalLib.Unity.Tests\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\..\.bin\HexagonalLib.Unity.Tests\Debug\ + TRACE + prompt + 4 + + + + ..\..\.packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + True + + + + + + + ..\..\.packages\Unity3D.UnityEngine.2018.3.5.1\lib\UnityEngine.dll + True + + + + + + + + + {ee497232-ac7a-48d0-9059-53b152c6bf01} + HexagonalLib.Unity + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + + + diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/Properties/AssemblyInfo.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2436b4e --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("HexagonalLib.Unity.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("HexagonalLib.Unity.Tests")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1E807875-014C-4745-86AB-E1E35916F8B3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/packages.config b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/packages.config new file mode 100644 index 0000000..5fff8f5 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity.Tests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Axial.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Axial.cs new file mode 100644 index 0000000..6023081 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Axial.cs @@ -0,0 +1,7 @@ +namespace HexagonalLib.Coordinates +{ + public partial struct Axial + { + + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Cubic.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Cubic.cs new file mode 100644 index 0000000..10fae70 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Cubic.cs @@ -0,0 +1,6 @@ +namespace HexagonalLib.Coordinates +{ + public readonly partial struct Cubic + { + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Offset.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Offset.cs new file mode 100644 index 0000000..6548187 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Coordinates/Offset.cs @@ -0,0 +1,6 @@ +namespace HexagonalLib.Coordinates +{ + public readonly partial struct Offset + { + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalGrid.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalGrid.cs new file mode 100644 index 0000000..a8c0c47 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalGrid.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using HexagonalLib.Coordinates; +using HexagonalLib.Utility; +using UnityEngine; + +namespace HexagonalLib +{ + public partial struct HexagonalGrid + { + #region ToOffset + + /// + /// Convert point to offset coordinate + /// + public Offset ToOffset(Vector2 vector) + { + return ToOffset(vector.AsTuple()); + } + + /// + /// Convert point to offset coordinate + /// + public Offset ToOffset(Vector3 vector) + { + return ToOffset(vector.AsTuple()); + } + + #endregion + + #region ToAxial + + /// + /// Convert point to axial coordinate + /// + public Axial ToAxial(Vector2 vector) + { + return ToAxial(vector.AsTuple()); + } + + /// + /// Convert point to axial coordinate + /// + public Axial ToAxial(Vector3 vector) + { + return ToAxial(vector.AsTuple()); + } + + #endregion + + #region ToCubic + + /// + /// Convert point to cubic coordinate + /// + public Cubic ToCubic(Vector2 vector) + { + return ToCubic(vector.AsTuple()); + } + + /// + /// Convert point to cubic coordinate + /// + public Cubic ToCubic(Vector3 vector) + { + return ToCubic(vector.AsTuple()); + } + + #endregion + + #region ToVector + + /// + /// Convert hex based on its offset coordinate to it center position in 2d space + /// + public Vector2 ToVector2(Offset coord) + { + return ToPoint2(coord).AsVector2(); + } + + /// + /// Convert hex based on its axial coordinate to it center position in 2d space + /// + public Vector2 ToVector2(Axial coord) + { + return ToPoint2(coord).AsVector2(); + } + + /// + /// Convert hex based on its cubic coordinate to it center position in 2d space + /// + public Vector2 ToVector2(Cubic coord) + { + return ToPoint2(coord).AsVector2(); + } + + /// + /// Convert hex based on its offset coordinate to it center position in 3d space OZ + /// + public Vector3 ToVector3(Offset coord, float y = 0) + { + return ToPoint2(coord).AsVector3(y); + } + + /// + /// Convert hex based on its axial coordinate to it center position in 3d space OZ + /// + public Vector3 ToVector3(Axial coord, float y = 0) + { + return ToPoint2(coord).AsVector3(y); + } + + /// + /// Convert hex based on its cubic coordinate to it center position in 3d space OZ + /// + public Vector3 ToVector3(Cubic coord, float y = 0) + { + return ToPoint2(coord).AsVector3(y); + } + + #endregion + + #region CreateMesh + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public Mesh CreateMesh(IReadOnlyList hexes, int subdivide) + { + return CreateMesh(hexes, subdivide, CreateMesh); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public Mesh CreateMesh(IReadOnlyList hexes, int subdivide) + { + return CreateMesh(hexes, subdivide, CreateMesh); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public Mesh CreateMesh(IReadOnlyList hexes, int subdivide) + { + return CreateMesh(hexes, subdivide, CreateMesh); + } + + private Mesh CreateMesh(IReadOnlyList hexes, int subdivide, Action, int, Action, Action> createMesh) + { + var meshData = GetMeshData(hexes.Count, subdivide); + var vertices = new Vector3[meshData.VerticesCount]; + var triangles = new int[meshData.IndicesCount]; + + createMesh(hexes, subdivide, + (i, point) => vertices[i] = new Vector3(point.X, 0, point.Y), + (i, index) => triangles[i] = index); + + var mesh = new Mesh + { + vertices = vertices, + triangles = triangles + }; + + mesh.RecalculateNormals(); + return mesh; + } + + #endregion + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalLib.Unity.csproj b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalLib.Unity.csproj new file mode 100644 index 0000000..fdbc0f8 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalLib.Unity.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {EE497232-AC7A-48D0-9059-53B152C6BF01} + Library + Properties + HexagonalLib + HexagonalLib.Unity + v4.7.2 + 512 + + + AnyCPU + true + full + false + ..\..\.bin\HexagonalLib.Unity\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + ..\..\.bin\HexagonalLib.Unity\Release\ + TRACE + prompt + 4 + + + + + + + + ..\..\.packages\Unity3D.UnityEngine.2018.3.5.1\lib\UnityEngine.dll + True + + + + + + + + + + + + + + + + + + + diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalMath.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalMath.cs new file mode 100644 index 0000000..fbd913d --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/HexagonalMath.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +namespace HexagonalLib +{ + public static partial class HexagonalMath + { + /// + /// Compares two vectors and returns true if they are similar. + /// + public static bool SimilarTo(this in Vector2 a, in Vector2 b) + { + return a.x.SimilarTo(b.x) && a.y.SimilarTo(b.y); + } + + /// + /// Compares two vectors and returns true if they are similar. + /// + public static bool SimilarTo(this in Vector3 a, in Vector3 b) + { + return a.x.SimilarTo(b.x) && a.y.SimilarTo(b.y) && a.z.SimilarTo(b.z); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Properties/AssemblyInfo.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..156538b --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("HexagonalLib.Unity")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("HexagonalLib.Unity")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("EE497232-AC7A-48D0-9059-53B152C6BF01")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Utility/VectorUtility.cs b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Utility/VectorUtility.cs new file mode 100644 index 0000000..e4822e9 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/Utility/VectorUtility.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +namespace HexagonalLib.Utility +{ + internal static class VectorUtility + { + internal static (float x, float y) AsTuple(this Vector2 vector) + { + return (vector.x, vector.y); + } + + internal static (float x, float y) AsTuple(this Vector3 vector) + { + return (vector.x, vector.z); + } + + internal static Vector2 AsVector2(this (float x, float y) tuple) + { + return new Vector2(tuple.x, tuple.y); + } + + internal static Vector3 AsVector3(this (float x, float y) tuple, float y = 0.0f) + { + return new Vector3(tuple.x, y, tuple.y); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/packages.config b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/packages.config new file mode 100644 index 0000000..986d258 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.Unity/HexagonalLib.Unity/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib.sln b/Src/HexagonalLib/src/HexagonalLib.sln new file mode 100644 index 0000000..b6ceba8 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib.sln @@ -0,0 +1,50 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "HexagonalLib", "HexagonalLib\HexagonalLib.shproj", "{30905D5D-8D2E-4A25-ABEB-B4B4B0C440E6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HexagonalLib.Unity", "HexagonalLib.Unity", "{C02A807F-4B02-4900-895F-67E25F8CF774}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexagonalLib.Unity.Tests", "HexagonalLib.Unity\HexagonalLib.Unity.Tests\HexagonalLib.Unity.Tests.csproj", "{1E807875-014C-4745-86AB-E1E35916F8B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexagonalLib.Unity", "HexagonalLib.Unity\HexagonalLib.Unity\HexagonalLib.Unity.csproj", "{EE497232-AC7A-48D0-9059-53B152C6BF01}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HexagonalLib.Net", "HexagonalLib.Net", "{1F507917-8D29-4061-BC98-7B64DABADED3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexagonalLib.Net.Tests", "HexagonalLib.Net\HexagonalLib.Net.Tests\HexagonalLib.Net.Tests.csproj", "{2608E591-E33C-446A-9E95-AFED738B2512}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexagonalLib.Net", "HexagonalLib.Net\HexagonalLib.Net\HexagonalLib.Net.csproj", "{CF391253-3550-40CF-810B-A6F8FCA2EBF2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Any CPU = Release|Any CPU + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {30905D5D-8D2E-4A25-ABEB-B4B4B0C440E6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30905D5D-8D2E-4A25-ABEB-B4B4B0C440E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE497232-AC7A-48D0-9059-53B152C6BF01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE497232-AC7A-48D0-9059-53B152C6BF01}.Release|Any CPU.Build.0 = Release|Any CPU + {EE497232-AC7A-48D0-9059-53B152C6BF01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE497232-AC7A-48D0-9059-53B152C6BF01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E807875-014C-4745-86AB-E1E35916F8B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E807875-014C-4745-86AB-E1E35916F8B3}.Release|Any CPU.Build.0 = Release|Any CPU + {1E807875-014C-4745-86AB-E1E35916F8B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E807875-014C-4745-86AB-E1E35916F8B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF391253-3550-40CF-810B-A6F8FCA2EBF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF391253-3550-40CF-810B-A6F8FCA2EBF2}.Release|Any CPU.Build.0 = Release|Any CPU + {CF391253-3550-40CF-810B-A6F8FCA2EBF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF391253-3550-40CF-810B-A6F8FCA2EBF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2608E591-E33C-446A-9E95-AFED738B2512}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2608E591-E33C-446A-9E95-AFED738B2512}.Release|Any CPU.Build.0 = Release|Any CPU + {2608E591-E33C-446A-9E95-AFED738B2512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2608E591-E33C-446A-9E95-AFED738B2512}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1E807875-014C-4745-86AB-E1E35916F8B3} = {C02A807F-4B02-4900-895F-67E25F8CF774} + {EE497232-AC7A-48D0-9059-53B152C6BF01} = {C02A807F-4B02-4900-895F-67E25F8CF774} + {CF391253-3550-40CF-810B-A6F8FCA2EBF2} = {1F507917-8D29-4061-BC98-7B64DABADED3} + {2608E591-E33C-446A-9E95-AFED738B2512} = {1F507917-8D29-4061-BC98-7B64DABADED3} + EndGlobalSection +EndGlobal diff --git a/Src/HexagonalLib/src/HexagonalLib/Coordinates/Axial.cs b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Axial.cs new file mode 100644 index 0000000..9a50817 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Axial.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; + +namespace HexagonalLib.Coordinates +{ + [Serializable] + public readonly partial struct Axial : IEquatable, IEqualityComparer + { + public static Axial Zero => new Axial(0, 0); + + public readonly int Q; + public readonly int R; + + public Axial(int q, int r) + : this() + { + Q = q; + R = r; + } + + public static bool operator ==(Axial coord1, Axial coord2) + { + return (coord1.Q, coord1.R) == (coord2.Q, coord2.R); + } + + public static bool operator !=(Axial coord1, Axial coord2) + { + return (coord1.Q, coord1.R) != (coord2.Q, coord2.R); + } + + public static Axial operator +(Axial coord1, Axial coord2) + { + return new Axial(coord1.Q + coord2.Q, coord1.R + coord2.R); + } + + public static Axial operator +(Axial coord, int offset) + { + return new Axial(coord.Q + offset, coord.R + offset); + } + + public static Axial operator -(Axial coord1, Axial coord2) + { + return new Axial(coord1.Q - coord2.Q, coord1.R - coord2.R); + } + + public static Axial operator -(Axial coord, int offset) + { + return new Axial(coord.Q - offset, coord.R - offset); + } + + public static Axial operator *(Axial coord, int offset) + { + return new Axial(coord.Q * offset, coord.R * offset); + } + + public override bool Equals(object other) + { + return other is Axial axial && Equals(axial); + } + + public bool Equals(Axial other) + { + return (Q, R) == (other.Q, other.R); + } + + public bool Equals(Axial coord1, Axial coord2) + { + return coord1.Equals(coord2); + } + + public override int GetHashCode() + { + return (Q, R).GetHashCode(); + } + + public int GetHashCode(Axial axial) + { + return axial.GetHashCode(); + } + + public override string ToString() + { + return $"A-[{Q}:{R}]"; + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/Coordinates/Cubic.cs b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Cubic.cs new file mode 100644 index 0000000..a03b2d2 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Cubic.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; + +namespace HexagonalLib.Coordinates +{ + [Serializable] + public readonly partial struct Cubic : IEquatable, IEqualityComparer + { + public static Cubic Zero => new Cubic(0, 0, 0); + + public readonly int X; + public readonly int Y; + public readonly int Z; + + public Cubic(int x, int y, int z) + : this() + { + X = x; + Y = y; + Z = z; + } + + /// + /// Round float coordinates to nearest valid coordinate + /// + public Cubic(float x, float y, float z) + { + var rx = (int) Math.Round(x); + var ry = (int) Math.Round(y); + var rz = (int) Math.Round(z); + + var xDiff = Math.Abs(rx - x); + var yDiff = Math.Abs(ry - y); + var zDiff = Math.Abs(rz - z); + + if (xDiff > yDiff && xDiff > zDiff) + { + rx = -ry - rz; + } + else if (yDiff > zDiff) + { + ry = -rx - rz; + } + else + { + rz = -rx - ry; + } + + X = rx; + Y = ry; + Z = rz; + } + + public static bool operator ==(Cubic coord1, Cubic coord2) + { + return (coord1.X, coord1.Y, coord1.Z) == (coord2.X, coord2.Y, coord2.Z); + } + + public static bool operator !=(Cubic coord1, Cubic coord2) + { + return (coord1.X, coord1.Y, coord1.Z) != (coord2.X, coord2.Y, coord2.Z); + } + + public static Cubic operator +(Cubic coord1, Cubic coord2) + { + return new Cubic(coord1.X + coord2.X, coord1.Y + coord2.Y, coord1.Z + coord2.Z); + } + + public static Cubic operator +(Cubic coord, int offset) + { + return new Cubic(coord.X + offset, coord.Y + offset, coord.Z + offset); + } + + public static Cubic operator -(Cubic coord1, Cubic coord2) + { + return new Cubic(coord1.X - coord2.X, coord1.Y - coord2.Y, coord1.Z - coord2.Z); + } + + public static Cubic operator -(Cubic coord, int offset) + { + return new Cubic(coord.X - offset, coord.Y - offset, coord.Z - offset); + } + + public static Cubic operator *(Cubic coord, int offset) + { + return new Cubic(coord.X * offset, coord.Y * offset, coord.Z * offset); + } + + public static Cubic operator *(Cubic coord, float delta) + { + return new Cubic(coord.X * delta, coord.Y * delta, coord.Z * delta); + } + + public bool IsValid() + { + return X + Y + Z == 0; + } + + public Cubic RotateToRight() + { + var x = -Y; + var y = -Z; + var z = -X; + return new Cubic(x, y, z); + } + + public Cubic RotateToRight(int times) + { + var cur = this; + for (var i = 0; i < times; i++) + { + cur = cur.RotateToRight(); + } + + return cur; + } + + public override bool Equals(object obj) + { + return obj is Cubic other && Equals(other); + } + + public bool Equals(Cubic other) + { + return (X, Y, Z) == (other.X, other.Y, other.Z); + } + + public bool Equals(Cubic coord1, Cubic coord2) + { + return coord1.Equals(coord2); + } + + public override int GetHashCode() + { + return (X, Y, Z).GetHashCode(); + } + + public int GetHashCode(Cubic coord) + { + return coord.GetHashCode(); + } + + public override string ToString() + { + return !IsValid() ? "C-[Invalid]" : $"C-[{X}:{Y}:{Z}]"; + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/Coordinates/Offset.cs b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Offset.cs new file mode 100644 index 0000000..2725aa8 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/Coordinates/Offset.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; + +namespace HexagonalLib.Coordinates +{ + [Serializable] + public readonly partial struct Offset : IEquatable, IEqualityComparer + { + public static Offset Zero => new Offset(0, 0); + + public readonly int X; + public readonly int Y; + + public Offset(int x, int y) + : this() + { + X = x; + Y = y; + } + + public Offset Add(int xOffset, int yOffset) + { + return new Offset(X + xOffset, Y + yOffset); + } + + public static Offset Clamp(Offset coord, Offset min, Offset max) + { + var x = Clamp(coord.X, min.X, max.X); + var y = Clamp(coord.Y, min.Y, max.Y); + + return new Offset(x, y); + } + + private static int Clamp(int value, int min, int max) + { + if (value < min) + { + value = min; + } + else if (value > max) + { + value = max; + } + + return value; + } + + public static bool operator ==(Offset coord1, Offset coord2) + { + return (coord1.X, coord1.Y) == (coord2.X, coord2.Y); + } + + public static bool operator !=(Offset coord1, Offset coord2) + { + return (coord1.X, coord1.Y) != (coord2.X, coord2.Y); + } + + public static Offset operator +(Offset coord1, Offset coord2) + { + return new Offset(coord1.X + coord2.X, coord1.Y + coord2.Y); + } + + public static Offset operator +(Offset coord, int offset) + { + return new Offset(coord.X + offset, coord.Y + offset); + } + + public static Offset operator -(Offset coord, Offset index2) + { + return new Offset(coord.X - index2.X, coord.Y - index2.Y); + } + + public static Offset operator /(Offset coord, int value) + { + return new Offset(coord.X / value, coord.Y / value); + } + + public static Offset operator *(Offset coord, int offset) + { + return new Offset(coord.X * offset, coord.Y * offset); + } + + public override bool Equals(object obj) + { + return obj is Offset other && Equals(other); + } + + public bool Equals(Offset other) + { + return (X, Y) == (other.X, other.Y); + } + + public bool Equals(Offset coord1, Offset coord2) + { + return coord1.Equals(coord2); + } + + public override int GetHashCode() + { + return (X, Y).GetHashCode(); + } + + public int GetHashCode(Offset coord) + { + return coord.GetHashCode(); + } + + public override string ToString() + { + return $"O-[{X}:{Y}]"; + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalException.cs b/Src/HexagonalLib/src/HexagonalLib/HexagonalException.cs new file mode 100644 index 0000000..7d48b8b --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalException.cs @@ -0,0 +1,72 @@ +using System; +using System.Text; + +namespace HexagonalLib +{ + public class HexagonalException : Exception + { + private class MessageBuilder + { + private readonly StringBuilder _message = new StringBuilder(); + + public MessageBuilder Append(string message) + { + _message.AppendLine(message); + return this; + } + + public MessageBuilder Append(HexagonalGrid grid) + { + Append(nameof(grid.Type), grid.Type); + Append(nameof(grid.InscribedRadius), grid.InscribedRadius); + Append(nameof(grid.DescribedRadius), grid.DescribedRadius); + return this; + } + + public MessageBuilder Append(params (string, object)[] fields) + { + foreach (var (paramName, paramValue) in fields) + { + Append(paramName, paramValue); + } + + return this; + } + + private void Append(string paramName, object paramValue) + { + _message.Append($"{paramName}={paramValue}; "); + } + + public override string ToString() + { + return _message.ToString(); + } + } + + public HexagonalException(string message) + : base(message) + { + } + + public HexagonalException(string message, params (string, object)[] fields) + : base(CreateBuilder(message).Append(fields).ToString()) + { + } + + public HexagonalException(string message, HexagonalGrid grid) + : base(CreateBuilder(message).Append(grid).ToString()) + { + } + + public HexagonalException(string message, HexagonalGrid grid, params (string, object)[] fields) + : base(CreateBuilder(message).Append(grid).Append(fields).ToString()) + { + } + + private static MessageBuilder CreateBuilder(string message) + { + return new MessageBuilder().Append(message); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalGrid.cs b/Src/HexagonalLib/src/HexagonalLib/HexagonalGrid.cs new file mode 100644 index 0000000..ad67ee1 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalGrid.cs @@ -0,0 +1,812 @@ +using System; +using System.Collections.Generic; +using HexagonalLib.Coordinates; +using static System.Math; + +namespace HexagonalLib +{ + /// + /// Represent geometry logic for infinity hexagonal grid + /// + public readonly partial struct HexagonalGrid + { + /// + /// Total count of edges in one Hex + /// + public const int EdgesCount = 6; + + public static readonly float Sqrt3 = (float) Sqrt(3); + + /// + /// Inscribed radius of the hex + /// + public readonly float InscribedRadius; + + /// + /// Described radius of hex + /// + public readonly float DescribedRadius; + + /// + /// Hexagon side length + /// + public float Side => DescribedRadius; + + /// + /// Inscribed diameter of hex + /// + public float InscribedDiameter => InscribedRadius * 2; + + /// + /// Described diameter of hex + /// + public float DescribedDiameter => DescribedRadius * 2; + + /// + /// Orientation and layout of this grid + /// + public readonly HexagonalGridType Type; + + /// + /// Offset between hex and its right-side neighbour on X axis + /// + public float HorizontalOffset + { + get + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + case HexagonalGridType.PointyEven: + return InscribedRadius * 2.0f; + case HexagonalGridType.FlatOdd: + case HexagonalGridType.FlatEven: + return DescribedRadius * 1.5f; + default: + throw new HexagonalException($"Can't get {nameof(HorizontalOffset)} with unexpected {nameof(Type)}", this); + } + } + } + + /// + /// Offset between hex and its up-side neighbour on Y axis + /// + public float VerticalOffset + { + get + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + case HexagonalGridType.PointyEven: + return DescribedRadius * 1.5f; + case HexagonalGridType.FlatOdd: + case HexagonalGridType.FlatEven: + return InscribedRadius * 2.0f; + default: + throw new HexagonalException($"Can't get {nameof(VerticalOffset)} with unexpected {nameof(Type)}", this); + } + } + } + + /// + /// The angle between the centers of any hex and its first neighbor relative to the vector (0, 1) clockwise + /// + /// + public float AngleToFirstNeighbor + { + get + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + case HexagonalGridType.PointyEven: + return 0.0f; + case HexagonalGridType.FlatOdd: + case HexagonalGridType.FlatEven: + return 30.0f; + default: + throw new HexagonalException($"Can't get {nameof(AngleToFirstNeighbor)} with unexpected {nameof(Type)}", this); + } + } + } + + /// + /// Base constructor for hexagonal grid + /// + /// Orientation and layout of the grid + /// Inscribed radius + public HexagonalGrid(HexagonalGridType type, float radius) + { + Type = type; + InscribedRadius = radius; + DescribedRadius = (float) (radius / Cos(PI / EdgesCount)); + } + + #region ToOffset + + /// + /// Convert cubic coordinate to offset + /// + public Offset ToOffset(Cubic coord) + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + { + var col = coord.X + (coord.Z - (coord.Z & 1)) / 2; + var row = coord.Z; + return new Offset(col, row); + } + case HexagonalGridType.PointyEven: + { + var col = coord.X + (coord.Z + (coord.Z & 1)) / 2; + var row = coord.Z; + return new Offset(col, row); + } + case HexagonalGridType.FlatOdd: + { + var col = coord.X; + var row = coord.Z + (coord.X - (coord.X & 1)) / 2; + return new Offset(col, row); + } + case HexagonalGridType.FlatEven: + { + var col = coord.X; + var row = coord.Z + (coord.X + (coord.X & 1)) / 2; + return new Offset(col, row); + } + default: + throw new HexagonalException($"{nameof(ToOffset)} failed with unexpected {nameof(Type)}", this, (nameof(coord), coord)); + } + } + + /// + /// Convert axial coordinate to offset + /// + public Offset ToOffset(Axial axial) + { + return ToOffset(ToCubic(axial)); + } + + /// + /// Returns the offset coordinate of the hex which contains a point + /// + public Offset ToOffset(float x, float y) + { + return ToOffset(ToCubic(x, y)); + } + + /// + /// Returns the offset coordinate of the hex which contains a point + /// + public Offset ToOffset((float X, float Y) point) + { + return ToOffset(ToCubic(point.X, point.Y)); + } + + #endregion + + #region ToAxial + + /// + /// Convert cubic coordinate to axial + /// + public Axial ToAxial(Cubic cubic) + { + return new Axial(cubic.X, cubic.Z); + } + + /// + /// Convert offset coordinate to axial + /// + public Axial ToAxial(Offset offset) + { + return ToAxial(ToCubic(offset)); + } + + /// + /// Returns the axial coordinate of the hex which contains a point + /// + public Axial ToAxial(float x, float y) + { + return ToAxial(ToCubic(x, y)); + } + + /// + /// Returns the axial coordinate of the hex which contains a point + /// + public Axial ToAxial((float X, float Y) point) + { + return ToAxial(ToCubic(point.X, point.Y)); + } + + #endregion + + #region ToCubic + + /// + /// Convert offset coordinate to cubic + /// + public Cubic ToCubic(Offset coord) + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + { + var x = coord.X - (coord.Y - (coord.Y & 1)) / 2; + var z = coord.Y; + var y = -x - z; + return new Cubic(x, y, z); + } + case HexagonalGridType.PointyEven: + { + var x = coord.X - (coord.Y + (coord.Y & 1)) / 2; + var z = coord.Y; + var y = -x - z; + return new Cubic(x, y, z); + } + case HexagonalGridType.FlatOdd: + { + var x = coord.X; + var z = coord.Y - (coord.X - (coord.X & 1)) / 2; + var y = -x - z; + return new Cubic(x, y, z); + } + case HexagonalGridType.FlatEven: + { + var x = coord.X; + var z = coord.Y - (coord.X + (coord.X & 1)) / 2; + var y = -x - z; + return new Cubic(x, y, z); + } + default: + throw new HexagonalException($"{nameof(ToCubic)} failed with unexpected {nameof(Type)}", this, (nameof(coord), coord)); + } + } + + /// + /// Convert axial coordinate to cubic + /// + public Cubic ToCubic(Axial axial) + { + return new Cubic(axial.Q, -axial.Q - axial.R, axial.R); + } + + /// + /// Returns the cubic coordinate of the hex which contains a point + /// + public Cubic ToCubic(float x, float y) + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + case HexagonalGridType.PointyEven: + { + var q = (x * Sqrt3 / 3.0f - y / 3.0f) / Side; + var r = y * 2.0f / 3.0f / Side; + return new Cubic(q, -q - r, r); + } + case HexagonalGridType.FlatOdd: + case HexagonalGridType.FlatEven: + { + var q = x * 2.0f / 3.0f / Side; + var r = (-x / 3.0f + Sqrt3 / 3.0f * y) / Side; + return new Cubic(q, -q - r, r); + } + default: + throw new HexagonalException($"{nameof(ToCubic)} failed with unexpected {nameof(Type)}", this, (nameof(x), x), (nameof(y), y)); + } + } + + /// + /// Returns the cubic coordinate of the hex which contains a point + /// + public Cubic ToCubic((float X, float Y) point) + { + return ToCubic(point.X, point.Y); + } + + #endregion + + #region ToPoint2 + + /// + /// Convert hex based on its offset coordinate to it center position in 2d space + /// + public (float X, float Y) ToPoint2(Offset coord) + { + return ToPoint2(ToAxial(coord)); + } + + /// + /// Convert hex based on its axial coordinate to it center position in 2d space + /// + public (float X, float Y) ToPoint2(Axial coord) + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + case HexagonalGridType.PointyEven: + { + var x = Side * (Sqrt3 * coord.Q + Sqrt3 / 2 * coord.R); + var y = Side * (3.0f / 2.0f * coord.R); + return (x, y); + } + case HexagonalGridType.FlatOdd: + case HexagonalGridType.FlatEven: + { + var x = Side * (3.0f / 2.0f * coord.Q); + var y = Side * (Sqrt3 / 2 * coord.Q + Sqrt3 * coord.R); + return (x, y); + } + default: + throw new HexagonalException($"{nameof(ToPoint2)} failed with unexpected {nameof(Type)}", this, (nameof(coord), coord)); + } + } + + /// + /// Convert hex based on its cubic coordinate to it center position in 2d space + /// + public (float X, float Y) ToPoint2(Cubic coord) + { + return ToPoint2(ToAxial(coord)); + } + + #endregion + + #region GetCornerPoint + + /// + /// Returns corner point in 2d space of given coordinate + /// + public (float X, float Y) GetCornerPoint(Offset coord, int edge) + { + return GetCornerPoint(coord, edge, ToPoint2); + } + + /// + /// Returns corner point in 2d space of given coordinate + /// + public (float X, float Y) GetCornerPoint(Axial coord, int edge) + { + return GetCornerPoint(coord, edge, ToPoint2); + } + + /// + /// Returns corner point in 2d space of given coordinate + /// + public (float X, float Y) GetCornerPoint(Cubic coord, int edge) + { + return GetCornerPoint(coord, edge, ToPoint2); + } + + /// + /// Returns corner point in 2d space of given coordinate + /// + private (float X, float Y) GetCornerPoint(T coord, int edge, Func toPoint) + where T : struct + { + edge = NormalizeIndex(edge); + var angleDeg = 60 * edge; + if (Type == HexagonalGridType.PointyEven || Type == HexagonalGridType.PointyOdd) + { + angleDeg -= 30; + } + + var center = toPoint(coord); + var angleRad = PI / 180 * angleDeg; + var x = (float) (center.X + DescribedRadius * Cos(angleRad)); + var y = (float) (center.Y + DescribedRadius * Sin(angleRad)); + return (x, y); + } + + #endregion + + #region GetNeighbor + + /// + /// Returns the neighbor at the specified index. + /// + public Offset GetNeighbor(Offset coord, int neighborIndex) + { + return coord + GetNeighborsOffsets(coord)[NormalizeIndex(neighborIndex)]; + } + + /// + /// Returns the neighbor at the specified index. + /// + public Axial GetNeighbor(Axial coord, int neighborIndex) + { + return coord + _axialNeighbors[NormalizeIndex(neighborIndex)]; + } + + /// + /// Returns the neighbor at the specified index. + /// + public Cubic GetNeighbor(Cubic coord, int neighborIndex) + { + return coord + _cubicNeighbors[NormalizeIndex(neighborIndex)]; + } + + #endregion + + #region GetNeighbors + + /// + /// Return all neighbors of the hex + /// + public IEnumerable GetNeighbors(Offset hex) + { + foreach (var offset in GetNeighborsOffsets(hex)) + { + yield return offset + hex; + } + } + + /// + /// Return all neighbors of the hex + /// + public IEnumerable GetNeighbors(Axial hex) + { + foreach (var offset in _axialNeighbors) + { + yield return offset + hex; + } + } + + /// + /// Return all neighbors of the hex + /// + public IEnumerable GetNeighbors(Cubic hex) + { + foreach (var offset in _cubicNeighbors) + { + yield return offset + hex; + } + } + + #endregion + + #region IsNeighbors + + /// + /// Checks whether the two hexes are neighbors or no + /// + public bool IsNeighbors(Offset coord1, Offset coord2) + { + return IsNeighbors(coord1, coord2, GetNeighbor); + } + + /// + /// Checks whether the two hexes are neighbors or no + /// + public bool IsNeighbors(Axial coord1, Axial coord2) + { + Func getNeighbor = GetNeighbor; + return IsNeighbors(coord1, coord2, getNeighbor); + } + + /// + /// Checks whether the two hexes are neighbors or no + /// + public bool IsNeighbors(Cubic coord1, Cubic coord2) + { + return IsNeighbors(coord1, coord2, GetNeighbor); + } + + /// + /// Checks whether the two hexes are neighbors or no + /// + public bool IsNeighbors(T coord1, T coord2, in Func getNeighbor) + where T : struct, IEqualityComparer + { + for (var neighborIndex = 0; neighborIndex < EdgesCount; neighborIndex++) + { + var neighbor = getNeighbor(coord1, neighborIndex); + if (neighbor.Equals(coord2)) + { + return true; + } + } + + return false; + } + + #endregion + + #region GetNeighborsRing + + /// + /// Returns a ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsRing(Offset center, int radius) + { + return GetNeighborsRing(center, radius, GetNeighbor); + } + + /// + /// Returns a ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsRing(Axial center, int radius) + { + return GetNeighborsRing(center, radius, GetNeighbor); + } + + /// + /// Returns a ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsRing(Cubic center, int radius) + { + return GetNeighborsRing(center, radius, GetNeighbor); + } + + /// + /// Returns a ring with a radius of hexes around the given . + /// + private static IEnumerable GetNeighborsRing(T center, int radius, Func getNeighbor) + where T : struct + { + if (radius == 0) + { + yield return center; + yield break; + } + + for (var i = 0; i < radius; i++) + { + center = getNeighbor(center, 4); + } + + for (var i = 0; i < 6; i++) + { + for (var j = 0; j < radius; j++) + { + yield return center; + center = getNeighbor(center, i); + } + } + } + + #endregion + + #region GetNeighborsAround + + /// + /// Returns a all hexes in the ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsAround(Offset center, int radius) + { + return GetNeighborsAround(center, radius, GetNeighborsRing); + } + + /// + /// Returns a all hexes in the ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsAround(Axial center, int radius) + { + return GetNeighborsAround(center, radius, GetNeighborsRing); + } + + /// + /// Returns a all hexes in the ring with a radius of hexes around the given . + /// + public IEnumerable GetNeighborsAround(Cubic center, int radius) + { + return GetNeighborsAround(center, radius, GetNeighborsRing); + } + + /// + /// Returns a all hexes in the ring with a radius of hexes around the given . + /// + private static IEnumerable GetNeighborsAround(T center, int radius, Func> getNeighborRing) + where T : struct + { + for (var i = 0; i < radius; i++) + { + foreach (var hex in getNeighborRing(center, i)) + { + yield return hex; + } + } + } + + #endregion + + #region GetNeighborIndex + + /// + /// Returns the bypass index to the specified neighbor + /// + public byte GetNeighborIndex(Offset center, Offset neighbor) + { + return GetNeighborIndex(center, neighbor, GetNeighbors); + } + + /// + /// Returns the bypass index to the specified neighbor + /// + public byte GetNeighborIndex(Axial center, Axial neighbor) + { + return GetNeighborIndex(center, neighbor, GetNeighbors); + } + + /// + /// Returns the bypass index to the specified neighbor + /// + public byte GetNeighborIndex(Cubic center, Cubic neighbor) + { + return GetNeighborIndex(center, neighbor, GetNeighbors); + } + + /// + /// Returns the bypass index to the specified neighbor + /// + private byte GetNeighborIndex(T center, T neighbor, Func> getNeighbors) + where T : struct, IEqualityComparer + { + byte neighborIndex = 0; + foreach (var current in getNeighbors(center)) + { + if (current.Equals(neighbor)) + { + return neighborIndex; + } + + neighborIndex++; + } + + throw new HexagonalException($"Can't find bypass index", this, (nameof(center), center), (nameof(neighbor), neighbor)); + } + + #endregion + + #region GetPointBetweenTwoNeighbours + + /// + /// Returns the midpoint of the boundary segment of two neighbors + /// + public (float x, float y) GetPointBetweenTwoNeighbours(Offset coord1, Offset coord2) + { + return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2); + } + + /// + /// Returns the midpoint of the boundary segment of two neighbors + /// + public (float x, float y) GetPointBetweenTwoNeighbours(Axial coord1, Axial coord2) + { + return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2); + } + + /// + /// Returns the midpoint of the boundary segment of two neighbors + /// + public (float x, float y) GetPointBetweenTwoNeighbours(Cubic coord1, Cubic coord2) + { + return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2); + } + + /// + /// Returns the midpoint of the boundary segment of two neighbors + /// + private (float x, float y) GetPointBetweenTwoNeighbours(T coord1, T coord2, Func isNeighbor, Func toPoint) + where T : struct + { + if (!isNeighbor(coord1, coord2)) + { + throw new HexagonalException($"Can't calculate point between not neighbors", this, (nameof(coord1), coord1), (nameof(coord2), coord2)); + } + + var c1 = toPoint(coord1); + var c2 = toPoint(coord2); + + return ((c1.X + c2.X) / 2, (c1.Y + c2.Y) / 2); + } + + #endregion + + #region CubeDistance + + /// + /// Manhattan distance between two hexes + /// + public int CubeDistance(Offset h1, Offset h2) + { + var cubicFrom = ToCubic(h1); + var cubicTo = ToCubic(h2); + return CubeDistance(cubicFrom, cubicTo); + } + + /// + /// Manhattan distance between two hexes + /// + public int CubeDistance(Axial h1, Axial h2) + { + var cubicFrom = ToCubic(h1); + var cubicTo = ToCubic(h2); + return CubeDistance(cubicFrom, cubicTo); + } + + /// + /// Manhattan distance between two hexes + /// + public static int CubeDistance(Cubic h1, Cubic h2) + { + return (Abs(h1.X - h2.X) + Abs(h1.Y - h2.Y) + Abs(h1.Z - h2.Z)) / 2; + } + + #endregion + + #region Neighbors + + /// + /// Return all neighbors offsets of the hex + /// + private IReadOnlyList GetNeighborsOffsets(Offset coord) + { + switch (Type) + { + case HexagonalGridType.PointyOdd: + return Abs(coord.Y % 2) == 0 ? _pointyEvenNeighbors : _pointyOddNeighbors; + case HexagonalGridType.PointyEven: + return Abs(coord.Y % 2) == 1 ? _pointyEvenNeighbors : _pointyOddNeighbors; + case HexagonalGridType.FlatOdd: + return Abs(coord.X % 2) == 0 ? _flatEvenNeighbors : _flatOddNeighbors; + case HexagonalGridType.FlatEven: + return Abs(coord.X % 2) == 1 ? _flatEvenNeighbors : _flatOddNeighbors; + default: + throw new HexagonalException($"{nameof(GetNeighborsOffsets)} failed with unexpected {nameof(Type)}", this, (nameof(coord), coord)); + } + } + + private static readonly List _pointyOddNeighbors = new List + { + new Offset(+1, 0), new Offset(+1, -1), new Offset(0, -1), + new Offset(-1, 0), new Offset(0, +1), new Offset(+1, +1), + }; + + private static readonly List _pointyEvenNeighbors = new List + { + new Offset(+1, 0), new Offset(0, -1), new Offset(-1, -1), + new Offset(-1, 0), new Offset(-1, +1), new Offset(0, +1), + }; + + private static readonly List _flatOddNeighbors = new List + { + new Offset(+1, +1), new Offset(+1, 0), new Offset(0, -1), + new Offset(-1, 0), new Offset(-1, +1), new Offset(0, +1), + }; + + private static readonly List _flatEvenNeighbors = new List + { + new Offset(+1, 0), new Offset(+1, -1), new Offset(0, -1), + new Offset(-1, -1), new Offset(-1, 0), new Offset(0, +1), + }; + + private static readonly List _axialNeighbors = new List + { + new Axial(+1, 0), new Axial(+1, -1), new Axial(0, -1), + new Axial(-1, 0), new Axial(-1, +1), new Axial(0, +1), + }; + + private static readonly List _cubicNeighbors = new List + { + new Cubic(+1, -1, 0), new Cubic(+1, 0, -1), new Cubic(0, +1, -1), + new Cubic(-1, +1, 0), new Cubic(-1, 0, +1), new Cubic(0, -1, +1), + }; + + #endregion + + private static int NormalizeIndex(int index) + { + index = index % EdgesCount; + if (index < 0) + { + index += EdgesCount; + } + + return index; + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalGridMesh.cs b/Src/HexagonalLib/src/HexagonalLib/HexagonalGridMesh.cs new file mode 100644 index 0000000..5334b83 --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalGridMesh.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using HexagonalLib.Coordinates; + +namespace HexagonalLib +{ + public readonly partial struct HexagonalGrid + { + /// + /// Calculate count of vertices and indices needed for build mesh. + /// + /// Count on hexes in mesh + /// Count of triangles splits for each hex + public (int VerticesCount, int IndicesCount) GetMeshData(int hexesCount, int subdivide) + { + // Calculate number of vertices we need to generate + // numVertices = 1 + Σ {S, i = 1} (i * 6) + var numVertices = 1; + + // Calculate number of indices we need to generate + // numIndices = Σ {S, i = 1} (36 * i - 18) + var numIndices = 0; + + for (int i = 1; i <= subdivide; i++) + { + numVertices += i * 6; + numIndices += 36 * i - 18; + } + + return (numVertices * hexesCount, numIndices * hexesCount); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public void CreateMesh(IEnumerable hexes, int subdivide, Action setVertex, Action setIndex) + { + CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public void CreateMesh(IEnumerable hexes, int subdivide, Action setVertex, Action setIndex) + { + CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// + public void CreateMesh(IEnumerable hexes, int subdivide, Action setVertex, Action setIndex) + { + CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2); + } + + /// + /// Generates a mesh for list of hex. The generation algorithm is taken from the site: + /// http://www.voidinspace.com/2014/07/project-twa-part-1-generating-a-hexagonal-tile-and-its-triangular-grid/ + /// + private void CreateMesh(IEnumerable hexes, int subdivide, Action setVertex, Action setIndex, Func toPoint) + { + var vertex = 0; + var index = 0; + + var data = GetMeshData(1, subdivide); + + foreach (var hex in hexes) + { + var localVertex = vertex; + var localIndex = index; + var center = toPoint(hex); + + void setVertexLocal(int i, (float X, float Y) currentVertex) + { + var (x, y) = currentVertex; + var shifted = (x + center.X, y + center.Y); + setVertex(localVertex + i, shifted); + } + + void setIndexLocal(int i, int currentIndex) => setIndex(localIndex + i, localVertex + currentIndex); + + CreateMesh(subdivide, setVertexLocal, setIndexLocal); + + vertex += data.VerticesCount; + index += data.IndicesCount; + } + } + + /// + /// Generates a mesh for one hex. The generation algorithm is taken from the site: + /// http://www.voidinspace.com/2014/07/project-twa-part-1-generating-a-hexagonal-tile-and-its-triangular-grid/ + /// + public void CreateMesh(int subdivide, Action setVertex, Action setIndex) + { + // We'll need those later to generate our x, z coordinates + var radius = InscribedRadius / (float) Math.Cos(Math.PI / 6); + var sin60 = (float) Math.Sin(Math.PI / 3); + var invTan60 = 1.0f / (float) Math.Tan(Math.PI / 3.0f); + var rdq = radius / subdivide; + + var verticesIndex = 0; + var indicesIndex = 0; + + // We'll need those to calculate our indices + var currentNumPoints = 0; + var prevRowNumPoints = 0; + + // [colMin, colMax] + var npCol0 = 2 * subdivide + 1; + var colMin = -subdivide; + var colMax = subdivide; + + // + // Now let's generate the grid + // First we'll iterate through the Columns, starting from the bottom (fA) + for (var itC = colMin; itC <= colMax; itC++) + { + // Calculate z for this row + // That's the same for each point in this column + var x = sin60 * rdq * itC; + + // Calculate how many points (z values) we need to generate on for this column + var npColI = npCol0 - Math.Abs(itC); + + // [rowMin, rowMax] + var rowMin = -subdivide; + if (itC < 0) + { + rowMin += Math.Abs(itC); + } + + var rowMax = rowMin + npColI - 1; + + // We need this for the indices + currentNumPoints += npColI; + + // Iterate through the Rows (fB) + for (var itR = rowMin; itR <= rowMax; itR++) + { + // Calculate z + var z = invTan60 * x + rdq * itR; + setVertex(verticesIndex, (x, z).Rotate(AngleToFirstNeighbor)); + + // Indices + // From each point we'll try to create triangles left and right + if (verticesIndex < (currentNumPoints - 1)) + { + // Triangles left from this column + if (itC >= colMin && itC < colMax) + { + // To get the point above + var padLeft = 0; + if (itC < 0) + { + padLeft = 1; + } + + setIndex(indicesIndex++, verticesIndex + npColI + padLeft); + setIndex(indicesIndex++, verticesIndex + 1); + setIndex(indicesIndex++, verticesIndex); + } + + // Triangles right from this column + if (itC > colMin && itC <= colMax) + { + // To get point bellow + var padRight = 0; + if (itC > 0) + { + padRight = 1; + } + + setIndex(indicesIndex++, verticesIndex - prevRowNumPoints + padRight); + setIndex(indicesIndex++, verticesIndex); + setIndex(indicesIndex++, verticesIndex + 1); + } + } + + // Next vertex... + verticesIndex++; + } + + // Store the previous row number of points, used to calculate the index buffer + prevRowNumPoints = npColI; + } + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalGridType.cs b/Src/HexagonalLib/src/HexagonalLib/HexagonalGridType.cs new file mode 100644 index 0000000..57b86fb --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalGridType.cs @@ -0,0 +1,28 @@ +namespace HexagonalLib +{ + /// + /// The typical layouts and orientations for hex grids + /// + public enum HexagonalGridType : byte + { + /// + /// Horizontal layout shoves odd rows right [odd-r] + /// + PointyOdd, + + /// + /// Horizontal layout shoves even rows right [even-r] + /// + PointyEven, + + /// + /// Vertical layout shoves odd columns down [odd-q] + /// + FlatOdd, + + /// + /// Vertical layout shoves even columns down [even-q] + /// + FlatEven, + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.projitems b/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.projitems new file mode 100644 index 0000000..1329fbf --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.projitems @@ -0,0 +1,21 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 1FDA753A-7694-41B7-8C00-AA39FD0780CA + + + HexagonalLib + + + + + + + + + + + + \ No newline at end of file diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.shproj b/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.shproj new file mode 100644 index 0000000..e6c65de --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalLib.shproj @@ -0,0 +1,18 @@ + + + + {30905D5D-8D2E-4A25-ABEB-B4B4B0C440E6} + HexagonalLib + + + bin\Debug\ + + + bin\Release\ + + + + + + + diff --git a/Src/HexagonalLib/src/HexagonalLib/HexagonalMath.cs b/Src/HexagonalLib/src/HexagonalLib/HexagonalMath.cs new file mode 100644 index 0000000..629c77c --- /dev/null +++ b/Src/HexagonalLib/src/HexagonalLib/HexagonalMath.cs @@ -0,0 +1,47 @@ +using System; + +namespace HexagonalLib +{ + public static partial class HexagonalMath + { + /// + /// Rotate 2d vector around z Axis clockwise + /// + /// Vector to rotate + /// Angle of rotation + public static (float X, float Y) Rotate(this in (float X, float Y) vector, float degrees) + { + var radians = Math.PI / 180.0 * degrees; + var sin = Math.Sin(radians); + var cos = Math.Cos(radians); + var x = (float) Math.Round(cos * vector.X - sin * vector.Y, 6); + var y = (float) Math.Round(sin * vector.X + cos * vector.Y, 6); + return (-x, y); + } + + /// + /// Returns this vector with a magnitude of 1 + /// + public static (float X, float Y) Normalize(this in (float X, float Y) vector) + { + var distance = Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y); + return ((float) (vector.X / distance), (float) (vector.Y / distance)); + } + + /// + /// Compares two floating point values and returns true if they are similar. + /// + public static bool SimilarTo(this in float a, in float b) + { + return Math.Abs(b - a) < (double) Math.Max(1E-06f * Math.Max(Math.Abs(a), Math.Abs(b)), float.Epsilon * 8f); + } + + /// + /// Compares two vectors and returns true if they are similar. + /// + public static bool SimilarTo(this in (float X, float Y) a, in (float X, float Y) b) + { + return a.X.SimilarTo(b.X) && a.Y.SimilarTo(b.Y); + } + } +} \ No newline at end of file diff --git a/Src/HexagonalLib/src/nuget.config b/Src/HexagonalLib/src/nuget.config new file mode 100644 index 0000000..4258823 --- /dev/null +++ b/Src/HexagonalLib/src/nuget.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Tests/Giants.Core.Tests/ApplicationTests.cs b/Tests/Giants.Core.Tests/ApplicationTests.cs new file mode 100644 index 0000000..3c97a2e --- /dev/null +++ b/Tests/Giants.Core.Tests/ApplicationTests.cs @@ -0,0 +1,12 @@ +namespace Giants.Core.Tests; + +using Giants.Application; + +public class ApplicationTests +{ + [Fact] + public void ApplicationCreationExample() + { + GiantApplication app = new GiantApplication(); + } +} diff --git a/Tests/Giants.Core.Tests/Giants.Core.Tests.csproj b/Tests/Giants.Core.Tests/Giants.Core.Tests.csproj new file mode 100644 index 0000000..0916eb4 --- /dev/null +++ b/Tests/Giants.Core.Tests/Giants.Core.Tests.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + +