structure du projet, ajout de la lib hexagonal

This commit is contained in:
2025-03-11 23:37:12 +01:00
parent 7213e43d67
commit a43df23e89
52 changed files with 2783 additions and 2 deletions

45
.gitignore vendored Normal file
View File

@@ -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

50
Giants.sln Normal file
View File

@@ -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

6
README.adoc Normal file
View File

@@ -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)

View File

@@ -1,2 +0,0 @@
# Giants

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Giants.Infrastructure\Giants.Infrastructure.csproj" />
<ProjectReference Include="..\Giants.Core\Giants.Core.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,18 @@
namespace Giants.Application;
using Giants.Core.Interfaces;
using Giants.Infrastructure;
/// <summary>
/// Une application Giants permettant l'instanciations des services, utilisé pour une console
/// ou autre archi sans injections de dependances
/// /// </summary>
public class GiantApplication
{
/// <summary> Constructeur de base </summary>
public GiantApplication()
{
IHexagonalGrid grid = new HexagonalGridImpl();
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,23 @@
using Giants.Core.Interfaces;
namespace Giants.Core;
/// <summary>
/// Il s'agit d'une instance d'une partie de Giants, c'est la racine de toutes les entitées de l'instance
/// /// </summary>
public class Match
{
#region données statiques
IHexagonalGrid _grid;
#endregion
#region données dynamiques
#endregion
/// <summary> Construction de base </summary>
/// <param name="grid"> La grid du jeu</param>
public Match(IHexagonalGrid grid)
{
_grid = grid;
}
}

View File

@@ -0,0 +1,9 @@
namespace Giants.Core.Interfaces;
/// <summary>
/// represente un outils qui permet de gérer une grille hexagonal
/// </summary>
public interface IHexagonalGrid
{
}

View File

@@ -0,0 +1,9 @@
namespace Giants.Core;
/// <summary>
/// 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
/// </summary>
public class BoardLayout
{
}

View File

@@ -0,0 +1,8 @@
namespace Giants.Core;
/// <summary>
/// Represente les information statiques d'une case. Ses voisins, son type, aucune informations qui peut changer pendant une partie
/// </summary>
public class TileInfos
{
}

View File

@@ -0,0 +1,5 @@
namespace Giants.Core;
public class TilePosition
{
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Giants.Core\Giants.Core.csproj" />
<ProjectReference Include="..\HexagonalLib\src\HexagonalLib.Net\HexagonalLib.Net\HexagonalLib.Net.csproj" />
</ItemGroup>
</Project>

View File

@@ -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);
}
}

127
Src/HexagonalLib/README.md Normal file
View File

@@ -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)_

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -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

4
Src/HexagonalLib/src/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.bin/*
.obj/*
.idea/*
.packages/*

View File

@@ -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};");
}
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>HexagonalLib.Tests</RootNamespace>
<IsPackable>false</IsPackable>
<TargetFrameworks>net472;net48;netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>..\..\.bin\HexagonalLib.Net.Tests\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>..\..\.bin\HexagonalLib.Net.Tests\Release\</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HexagonalLib.Net\HexagonalLib.Net.csproj" />
</ItemGroup>
</Project>

View File

@@ -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}");
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>HexagonalLib</RootNamespace>
<TargetFrameworks>net472;net48;netcoreapp3.1</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Title>HexagonalLib</Title>
<Authors>Ivan Murashka</Authors>
<AssemblyVersion>1.0.0</AssemblyVersion>
<PackageProjectUrl>https://github.com/imurashka/HexagonalLib</PackageProjectUrl>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>..\..\.bin\HexagonalLib.Net\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>..\..\.bin\HexagonalLib.Net\Release\</OutputPath>
</PropertyGroup>
<Import Project="..\..\HexagonalLib\HexagonalLib.projitems" Label="Shared" />
</Project>

View File

@@ -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));
}
}
}

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\.packages\NUnit.3.12.0\build\NUnit.props" Condition="Exists('..\..\.packages\NUnit.3.12.0\build\NUnit.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1E807875-014C-4745-86AB-E1E35916F8B3}</ProjectGuid>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>HexagonalLib.Tests</RootNamespace>
<AssemblyName>HexagonalLib.Unity.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\.bin\HexagonalLib.Unity.Tests\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\.bin\HexagonalLib.Unity.Tests\Debug\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=3.12.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb">
<HintPath>..\..\.packages\NUnit.3.12.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\..\.packages\Unity3D.UnityEngine.2018.3.5.1\lib\UnityEngine.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="HexagonalGridTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HexagonalLib.Unity\HexagonalLib.Unity.csproj">
<Project>{ee497232-ac7a-48d0-9059-53b152c6bf01}</Project>
<Name>HexagonalLib.Unity</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\.packages\NUnit.3.12.0\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\.packages\NUnit.3.12.0\build\NUnit.props'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -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")]

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.12.0" targetFramework="net472" />
<package id="Unity3D.UnityEngine" version="2018.3.5.1" targetFramework="net472" />
</packages>

View File

@@ -0,0 +1,7 @@
namespace HexagonalLib.Coordinates
{
public partial struct Axial
{
}
}

View File

@@ -0,0 +1,6 @@
namespace HexagonalLib.Coordinates
{
public readonly partial struct Cubic
{
}
}

View File

@@ -0,0 +1,6 @@
namespace HexagonalLib.Coordinates
{
public readonly partial struct Offset
{
}
}

View File

@@ -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
/// <summary>
/// Convert point to offset coordinate
/// </summary>
public Offset ToOffset(Vector2 vector)
{
return ToOffset(vector.AsTuple());
}
/// <summary>
/// Convert point to offset coordinate
/// </summary>
public Offset ToOffset(Vector3 vector)
{
return ToOffset(vector.AsTuple());
}
#endregion
#region ToAxial
/// <summary>
/// Convert point to axial coordinate
/// </summary>
public Axial ToAxial(Vector2 vector)
{
return ToAxial(vector.AsTuple());
}
/// <summary>
/// Convert point to axial coordinate
/// </summary>
public Axial ToAxial(Vector3 vector)
{
return ToAxial(vector.AsTuple());
}
#endregion
#region ToCubic
/// <summary>
/// Convert point to cubic coordinate
/// </summary>
public Cubic ToCubic(Vector2 vector)
{
return ToCubic(vector.AsTuple());
}
/// <summary>
/// Convert point to cubic coordinate
/// </summary>
public Cubic ToCubic(Vector3 vector)
{
return ToCubic(vector.AsTuple());
}
#endregion
#region ToVector
/// <summary>
/// Convert hex based on its offset coordinate to it center position in 2d space
/// </summary>
public Vector2 ToVector2(Offset coord)
{
return ToPoint2(coord).AsVector2();
}
/// <summary>
/// Convert hex based on its axial coordinate to it center position in 2d space
/// </summary>
public Vector2 ToVector2(Axial coord)
{
return ToPoint2(coord).AsVector2();
}
/// <summary>
/// Convert hex based on its cubic coordinate to it center position in 2d space
/// </summary>
public Vector2 ToVector2(Cubic coord)
{
return ToPoint2(coord).AsVector2();
}
/// <summary>
/// Convert hex based on its offset coordinate to it center position in 3d space OZ
/// </summary>
public Vector3 ToVector3(Offset coord, float y = 0)
{
return ToPoint2(coord).AsVector3(y);
}
/// <summary>
/// Convert hex based on its axial coordinate to it center position in 3d space OZ
/// </summary>
public Vector3 ToVector3(Axial coord, float y = 0)
{
return ToPoint2(coord).AsVector3(y);
}
/// <summary>
/// Convert hex based on its cubic coordinate to it center position in 3d space OZ
/// </summary>
public Vector3 ToVector3(Cubic coord, float y = 0)
{
return ToPoint2(coord).AsVector3(y);
}
#endregion
#region CreateMesh
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public Mesh CreateMesh(IReadOnlyList<Offset> hexes, int subdivide)
{
return CreateMesh(hexes, subdivide, CreateMesh);
}
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public Mesh CreateMesh(IReadOnlyList<Axial> hexes, int subdivide)
{
return CreateMesh(hexes, subdivide, CreateMesh);
}
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public Mesh CreateMesh(IReadOnlyList<Cubic> hexes, int subdivide)
{
return CreateMesh(hexes, subdivide, CreateMesh);
}
private Mesh CreateMesh<T>(IReadOnlyList<T> hexes, int subdivide, Action<IEnumerable<T>, int, Action<int, (float X, float Y)>, Action<int, int>> 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
}
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EE497232-AC7A-48D0-9059-53B152C6BF01}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>HexagonalLib</RootNamespace>
<AssemblyName>HexagonalLib.Unity</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\.bin\HexagonalLib.Unity\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\.bin\HexagonalLib.Unity\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>..\..\.packages\Unity3D.UnityEngine.2018.3.5.1\lib\UnityEngine.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Coordinates\Axial.cs" />
<Compile Include="Coordinates\Cubic.cs" />
<Compile Include="Coordinates\Offset.cs" />
<Compile Include="HexagonalGrid.cs" />
<Compile Include="HexagonalMath.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utility\VectorUtility.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\HexagonalLib\HexagonalLib.projitems" Label="Shared" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,23 @@
using UnityEngine;
namespace HexagonalLib
{
public static partial class HexagonalMath
{
/// <summary>
/// Compares two vectors and returns true if they are similar.
/// </summary>
public static bool SimilarTo(this in Vector2 a, in Vector2 b)
{
return a.x.SimilarTo(b.x) && a.y.SimilarTo(b.y);
}
/// <summary>
/// Compares two vectors and returns true if they are similar.
/// </summary>
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);
}
}
}

View File

@@ -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")]

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Unity3D.UnityEngine" version="2018.3.5.1" targetFramework="net472" />
</packages>

View File

@@ -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

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
namespace HexagonalLib.Coordinates
{
[Serializable]
public readonly partial struct Axial : IEquatable<Axial>, IEqualityComparer<Axial>
{
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}]";
}
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
namespace HexagonalLib.Coordinates
{
[Serializable]
public readonly partial struct Cubic : IEquatable<Cubic>, IEqualityComparer<Cubic>
{
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;
}
/// <summary>
/// Round float coordinates to nearest valid coordinate
/// </summary>
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}]";
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace HexagonalLib.Coordinates
{
[Serializable]
public readonly partial struct Offset : IEquatable<Offset>, IEqualityComparer<Offset>
{
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}]";
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,812 @@
using System;
using System.Collections.Generic;
using HexagonalLib.Coordinates;
using static System.Math;
namespace HexagonalLib
{
/// <summary>
/// Represent geometry logic for infinity hexagonal grid
/// </summary>
public readonly partial struct HexagonalGrid
{
/// <summary>
/// Total count of edges in one Hex
/// </summary>
public const int EdgesCount = 6;
public static readonly float Sqrt3 = (float) Sqrt(3);
/// <summary>
/// Inscribed radius of the hex
/// </summary>
public readonly float InscribedRadius;
/// <summary>
/// Described radius of hex
/// </summary>
public readonly float DescribedRadius;
/// <summary>
/// Hexagon side length
/// </summary>
public float Side => DescribedRadius;
/// <summary>
/// Inscribed diameter of hex
/// </summary>
public float InscribedDiameter => InscribedRadius * 2;
/// <summary>
/// Described diameter of hex
/// </summary>
public float DescribedDiameter => DescribedRadius * 2;
/// <summary>
/// Orientation and layout of this grid
/// </summary>
public readonly HexagonalGridType Type;
/// <summary>
/// Offset between hex and its right-side neighbour on X axis
/// </summary>
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);
}
}
}
/// <summary>
/// Offset between hex and its up-side neighbour on Y axis
/// </summary>
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);
}
}
}
/// <summary>
/// The angle between the centers of any hex and its first neighbor relative to the vector (0, 1) clockwise
/// </summary>
/// <exception cref="HexagonalException"></exception>
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);
}
}
}
/// <summary>
/// Base constructor for hexagonal grid
/// </summary>
/// <param name="type">Orientation and layout of the grid</param>
/// <param name="radius">Inscribed radius</param>
public HexagonalGrid(HexagonalGridType type, float radius)
{
Type = type;
InscribedRadius = radius;
DescribedRadius = (float) (radius / Cos(PI / EdgesCount));
}
#region ToOffset
/// <summary>
/// Convert cubic coordinate to offset
/// </summary>
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));
}
}
/// <summary>
/// Convert axial coordinate to offset
/// </summary>
public Offset ToOffset(Axial axial)
{
return ToOffset(ToCubic(axial));
}
/// <summary>
/// Returns the offset coordinate of the hex which contains a point
/// </summary>
public Offset ToOffset(float x, float y)
{
return ToOffset(ToCubic(x, y));
}
/// <summary>
/// Returns the offset coordinate of the hex which contains a point
/// </summary>
public Offset ToOffset((float X, float Y) point)
{
return ToOffset(ToCubic(point.X, point.Y));
}
#endregion
#region ToAxial
/// <summary>
/// Convert cubic coordinate to axial
/// </summary>
public Axial ToAxial(Cubic cubic)
{
return new Axial(cubic.X, cubic.Z);
}
/// <summary>
/// Convert offset coordinate to axial
/// </summary>
public Axial ToAxial(Offset offset)
{
return ToAxial(ToCubic(offset));
}
/// <summary>
/// Returns the axial coordinate of the hex which contains a point
/// </summary>
public Axial ToAxial(float x, float y)
{
return ToAxial(ToCubic(x, y));
}
/// <summary>
/// Returns the axial coordinate of the hex which contains a point
/// </summary>
public Axial ToAxial((float X, float Y) point)
{
return ToAxial(ToCubic(point.X, point.Y));
}
#endregion
#region ToCubic
/// <summary>
/// Convert offset coordinate to cubic
/// </summary>
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));
}
}
/// <summary>
/// Convert axial coordinate to cubic
/// </summary>
public Cubic ToCubic(Axial axial)
{
return new Cubic(axial.Q, -axial.Q - axial.R, axial.R);
}
/// <summary>
/// Returns the cubic coordinate of the hex which contains a point
/// </summary>
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));
}
}
/// <summary>
/// Returns the cubic coordinate of the hex which contains a point
/// </summary>
public Cubic ToCubic((float X, float Y) point)
{
return ToCubic(point.X, point.Y);
}
#endregion
#region ToPoint2
/// <summary>
/// Convert hex based on its offset coordinate to it center position in 2d space
/// </summary>
public (float X, float Y) ToPoint2(Offset coord)
{
return ToPoint2(ToAxial(coord));
}
/// <summary>
/// Convert hex based on its axial coordinate to it center position in 2d space
/// </summary>
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));
}
}
/// <summary>
/// Convert hex based on its cubic coordinate to it center position in 2d space
/// </summary>
public (float X, float Y) ToPoint2(Cubic coord)
{
return ToPoint2(ToAxial(coord));
}
#endregion
#region GetCornerPoint
/// <summary>
/// Returns corner point in 2d space of given coordinate
/// </summary>
public (float X, float Y) GetCornerPoint(Offset coord, int edge)
{
return GetCornerPoint(coord, edge, ToPoint2);
}
/// <summary>
/// Returns corner point in 2d space of given coordinate
/// </summary>
public (float X, float Y) GetCornerPoint(Axial coord, int edge)
{
return GetCornerPoint(coord, edge, ToPoint2);
}
/// <summary>
/// Returns corner point in 2d space of given coordinate
/// </summary>
public (float X, float Y) GetCornerPoint(Cubic coord, int edge)
{
return GetCornerPoint(coord, edge, ToPoint2);
}
/// <summary>
/// Returns corner point in 2d space of given coordinate
/// </summary>
private (float X, float Y) GetCornerPoint<T>(T coord, int edge, Func<T, (float X, float Y)> 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
/// <summary>
/// Returns the neighbor at the specified index.
/// </summary>
public Offset GetNeighbor(Offset coord, int neighborIndex)
{
return coord + GetNeighborsOffsets(coord)[NormalizeIndex(neighborIndex)];
}
/// <summary>
/// Returns the neighbor at the specified index.
/// </summary>
public Axial GetNeighbor(Axial coord, int neighborIndex)
{
return coord + _axialNeighbors[NormalizeIndex(neighborIndex)];
}
/// <summary>
/// Returns the neighbor at the specified index.
/// </summary>
public Cubic GetNeighbor(Cubic coord, int neighborIndex)
{
return coord + _cubicNeighbors[NormalizeIndex(neighborIndex)];
}
#endregion
#region GetNeighbors
/// <summary>
/// Return all neighbors of the hex
/// </summary>
public IEnumerable<Offset> GetNeighbors(Offset hex)
{
foreach (var offset in GetNeighborsOffsets(hex))
{
yield return offset + hex;
}
}
/// <summary>
/// Return all neighbors of the hex
/// </summary>
public IEnumerable<Axial> GetNeighbors(Axial hex)
{
foreach (var offset in _axialNeighbors)
{
yield return offset + hex;
}
}
/// <summary>
/// Return all neighbors of the hex
/// </summary>
public IEnumerable<Cubic> GetNeighbors(Cubic hex)
{
foreach (var offset in _cubicNeighbors)
{
yield return offset + hex;
}
}
#endregion
#region IsNeighbors
/// <summary>
/// Checks whether the two hexes are neighbors or no
/// </summary>
public bool IsNeighbors(Offset coord1, Offset coord2)
{
return IsNeighbors(coord1, coord2, GetNeighbor);
}
/// <summary>
/// Checks whether the two hexes are neighbors or no
/// </summary>
public bool IsNeighbors(Axial coord1, Axial coord2)
{
Func<Axial, int, Axial> getNeighbor = GetNeighbor;
return IsNeighbors(coord1, coord2, getNeighbor);
}
/// <summary>
/// Checks whether the two hexes are neighbors or no
/// </summary>
public bool IsNeighbors(Cubic coord1, Cubic coord2)
{
return IsNeighbors(coord1, coord2, GetNeighbor);
}
/// <summary>
/// Checks whether the two hexes are neighbors or no
/// </summary>
public bool IsNeighbors<T>(T coord1, T coord2, in Func<T, int, T> getNeighbor)
where T : struct, IEqualityComparer<T>
{
for (var neighborIndex = 0; neighborIndex < EdgesCount; neighborIndex++)
{
var neighbor = getNeighbor(coord1, neighborIndex);
if (neighbor.Equals(coord2))
{
return true;
}
}
return false;
}
#endregion
#region GetNeighborsRing
/// <summary>
/// Returns a ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Offset> GetNeighborsRing(Offset center, int radius)
{
return GetNeighborsRing(center, radius, GetNeighbor);
}
/// <summary>
/// Returns a ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Axial> GetNeighborsRing(Axial center, int radius)
{
return GetNeighborsRing(center, radius, GetNeighbor);
}
/// <summary>
/// Returns a ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Cubic> GetNeighborsRing(Cubic center, int radius)
{
return GetNeighborsRing(center, radius, GetNeighbor);
}
/// <summary>
/// Returns a ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
private static IEnumerable<T> GetNeighborsRing<T>(T center, int radius, Func<T, int, T> 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
/// <summary>
/// Returns a all hexes in the ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Offset> GetNeighborsAround(Offset center, int radius)
{
return GetNeighborsAround(center, radius, GetNeighborsRing);
}
/// <summary>
/// Returns a all hexes in the ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Axial> GetNeighborsAround(Axial center, int radius)
{
return GetNeighborsAround(center, radius, GetNeighborsRing);
}
/// <summary>
/// Returns a all hexes in the ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
public IEnumerable<Cubic> GetNeighborsAround(Cubic center, int radius)
{
return GetNeighborsAround(center, radius, GetNeighborsRing);
}
/// <summary>
/// Returns a all hexes in the ring with a radius of <see cref="radius"/> hexes around the given <see cref="center"/>.
/// </summary>
private static IEnumerable<T> GetNeighborsAround<T>(T center, int radius, Func<T, int, IEnumerable<T>> getNeighborRing)
where T : struct
{
for (var i = 0; i < radius; i++)
{
foreach (var hex in getNeighborRing(center, i))
{
yield return hex;
}
}
}
#endregion
#region GetNeighborIndex
/// <summary>
/// Returns the bypass index to the specified neighbor
/// </summary>
public byte GetNeighborIndex(Offset center, Offset neighbor)
{
return GetNeighborIndex(center, neighbor, GetNeighbors);
}
/// <summary>
/// Returns the bypass index to the specified neighbor
/// </summary>
public byte GetNeighborIndex(Axial center, Axial neighbor)
{
return GetNeighborIndex(center, neighbor, GetNeighbors);
}
/// <summary>
/// Returns the bypass index to the specified neighbor
/// </summary>
public byte GetNeighborIndex(Cubic center, Cubic neighbor)
{
return GetNeighborIndex(center, neighbor, GetNeighbors);
}
/// <summary>
/// Returns the bypass index to the specified neighbor
/// </summary>
private byte GetNeighborIndex<T>(T center, T neighbor, Func<T, IEnumerable<T>> getNeighbors)
where T : struct, IEqualityComparer<T>
{
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
/// <summary>
/// Returns the midpoint of the boundary segment of two neighbors
/// </summary>
public (float x, float y) GetPointBetweenTwoNeighbours(Offset coord1, Offset coord2)
{
return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2);
}
/// <summary>
/// Returns the midpoint of the boundary segment of two neighbors
/// </summary>
public (float x, float y) GetPointBetweenTwoNeighbours(Axial coord1, Axial coord2)
{
return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2);
}
/// <summary>
/// Returns the midpoint of the boundary segment of two neighbors
/// </summary>
public (float x, float y) GetPointBetweenTwoNeighbours(Cubic coord1, Cubic coord2)
{
return GetPointBetweenTwoNeighbours(coord1, coord2, IsNeighbors, ToPoint2);
}
/// <summary>
/// Returns the midpoint of the boundary segment of two neighbors
/// </summary>
private (float x, float y) GetPointBetweenTwoNeighbours<T>(T coord1, T coord2, Func<T, T, bool> isNeighbor, Func<T, (float X, float Y)> 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
/// <summary>
/// Manhattan distance between two hexes
/// </summary>
public int CubeDistance(Offset h1, Offset h2)
{
var cubicFrom = ToCubic(h1);
var cubicTo = ToCubic(h2);
return CubeDistance(cubicFrom, cubicTo);
}
/// <summary>
/// Manhattan distance between two hexes
/// </summary>
public int CubeDistance(Axial h1, Axial h2)
{
var cubicFrom = ToCubic(h1);
var cubicTo = ToCubic(h2);
return CubeDistance(cubicFrom, cubicTo);
}
/// <summary>
/// Manhattan distance between two hexes
/// </summary>
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
/// <summary>
/// Return all neighbors offsets of the hex
/// </summary>
private IReadOnlyList<Offset> 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<Offset> _pointyOddNeighbors = new List<Offset>
{
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<Offset> _pointyEvenNeighbors = new List<Offset>
{
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<Offset> _flatOddNeighbors = new List<Offset>
{
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<Offset> _flatEvenNeighbors = new List<Offset>
{
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<Axial> _axialNeighbors = new List<Axial>
{
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<Cubic> _cubicNeighbors = new List<Cubic>
{
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;
}
}
}

View File

@@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using HexagonalLib.Coordinates;
namespace HexagonalLib
{
public readonly partial struct HexagonalGrid
{
/// <summary>
/// Calculate count of vertices and indices needed for build mesh.
/// </summary>
/// <param name="hexesCount">Count on hexes in mesh</param>
/// <param name="subdivide">Count of triangles splits for each hex</param>
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);
}
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public void CreateMesh(IEnumerable<Offset> hexes, int subdivide, Action<int, (float X, float Y)> setVertex, Action<int, int> setIndex)
{
CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2);
}
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public void CreateMesh(IEnumerable<Axial> hexes, int subdivide, Action<int, (float X, float Y)> setVertex, Action<int, int> setIndex)
{
CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2);
}
/// <summary>
/// Generates a mesh for list of hex. The generation algorithm is taken from the site:
/// </summary>
public void CreateMesh(IEnumerable<Cubic> hexes, int subdivide, Action<int, (float X, float Y)> setVertex, Action<int, int> setIndex)
{
CreateMesh(hexes, subdivide, setVertex, setIndex, ToPoint2);
}
/// <summary>
/// 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/
/// </summary>
private void CreateMesh<T>(IEnumerable<T> hexes, int subdivide, Action<int, (float X, float Y)> setVertex, Action<int, int> setIndex, Func<T, (float X, float Y)> 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;
}
}
/// <summary>
/// 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/
/// </summary>
public void CreateMesh(int subdivide, Action<int, (float X, float Y)> setVertex, Action<int, int> 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;
}
}
}
}

View File

@@ -0,0 +1,28 @@
namespace HexagonalLib
{
/// <summary>
/// The typical layouts and orientations for hex grids
/// </summary>
public enum HexagonalGridType : byte
{
/// <summary>
/// Horizontal layout shoves odd rows right [odd-r]
/// </summary>
PointyOdd,
/// <summary>
/// Horizontal layout shoves even rows right [even-r]
/// </summary>
PointyEven,
/// <summary>
/// Vertical layout shoves odd columns down [odd-q]
/// </summary>
FlatOdd,
/// <summary>
/// Vertical layout shoves even columns down [even-q]
/// </summary>
FlatEven,
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>1FDA753A-7694-41B7-8C00-AA39FD0780CA</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>HexagonalLib</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Coordinates\Axial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Coordinates\Cubic.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Coordinates\Offset.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HexagonalException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HexagonalGrid.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HexagonalGridMesh.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HexagonalGridType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)HexagonalMath.cs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{30905D5D-8D2E-4A25-ABEB-B4B4B0C440E6}</ProjectGuid>
<AssemblyName>HexagonalLib</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<Import Project="HexagonalLib.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,47 @@
using System;
namespace HexagonalLib
{
public static partial class HexagonalMath
{
/// <summary>
/// Rotate 2d vector around z Axis clockwise
/// </summary>
/// <param name="vector">Vector to rotate</param>
/// <param name="degrees">Angle of rotation</param>
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);
}
/// <summary>
/// Returns this vector with a magnitude of 1
/// </summary>
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));
}
/// <summary>
/// Compares two floating point values and returns true if they are similar.
/// </summary>
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);
}
/// <summary>
/// Compares two vectors and returns true if they are similar.
/// </summary>
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);
}
}
}

View File

@@ -0,0 +1,5 @@
<configuration>
<config>
<add key="repositoryPath" value=".packages" />
</config>
</configuration>

View File

@@ -0,0 +1,12 @@
namespace Giants.Core.Tests;
using Giants.Application;
public class ApplicationTests
{
[Fact]
public void ApplicationCreationExample()
{
GiantApplication app = new GiantApplication();
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Src\Giants.Core\Giants.Core.csproj" />
<ProjectReference Include="..\..\Src\Giants.Application\Giants.Application.csproj" />
</ItemGroup>
</Project>