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:
+
+
+
+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
+
+
+
+```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
+
+
+
+```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:
+
+
+
+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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+