Загрузка данных


using System;
using System.Collections.Generic;
using UnityEngine;

namespace CodeBase.HexGrid
{
    public static class HexGridGenerator
    {
        private const float SquareRootOfThree = 1.7320508f;

        public static HexGridLayout GenerateInRectangle(
            int hexCount,
            Vector3 rectangleCenter,
            float rectangleWidth,
            float rectangleDepth,
            float hexGap = 0f,
            float rectanglePadding = 0f)
        {
            if (hexCount < 0)
                throw new ArgumentOutOfRangeException(nameof(hexCount));

            if (rectangleWidth <= 0f)
                throw new ArgumentOutOfRangeException(nameof(rectangleWidth));

            if (rectangleDepth <= 0f)
                throw new ArgumentOutOfRangeException(nameof(rectangleDepth));

            if (hexCount == 0)
            {
                return new HexGridLayout(
                    Array.Empty<HexCellPlacement>(),
                    0,
                    0,
                    0f);
            }

            float availableWidth = rectangleWidth - rectanglePadding * 2f;
            float availableDepth = rectangleDepth - rectanglePadding * 2f;

            if (availableWidth <= 0f || availableDepth <= 0f)
                throw new ArgumentException("Padding is too large for the rectangle size.");

            hexGap = Mathf.Max(0f, hexGap);

            HexGridDimensions gridDimensions = CalculateGridDimensions(
                hexCount,
                availableWidth,
                availableDepth);

            float hexRadius = CalculateHexRadius(
                gridDimensions.ColumnCount,
                gridDimensions.RowCount,
                availableWidth,
                availableDepth,
                hexGap);

            if (hexRadius <= 0f)
                throw new ArgumentException("Rectangle is too small for the requested hex gap.");

            float horizontalStep = SquareRootOfThree * hexRadius + hexGap;
            float depthStep = 1.5f * hexRadius + hexGap;

            HexCellPlacement[] placements = new HexCellPlacement[hexCount];

            float minimumX = float.PositiveInfinity;
            float maximumX = float.NegativeInfinity;
            float minimumZ = float.PositiveInfinity;
            float maximumZ = float.NegativeInfinity;

            for (int hexIndex = 0; hexIndex < hexCount; hexIndex++)
            {
                int rowIndex = hexIndex / gridDimensions.ColumnCount;
                int columnIndex = hexIndex % gridDimensions.ColumnCount;

                int remainingHexCount = hexCount - rowIndex * gridDimensions.ColumnCount;
                int hexCountInCurrentRow = Mathf.Min(gridDimensions.ColumnCount, remainingHexCount);

                bool isShiftedRow = rowIndex % 2 == 1;

                float shiftedRowOffset = isShiftedRow ? horizontalStep * 0.5f : 0f;
                float incompleteRowCenteringOffset =
                    (gridDimensions.ColumnCount - hexCountInCurrentRow) * horizontalStep * 0.5f;

                float localX = columnIndex * horizontalStep
                               + shiftedRowOffset
                               + incompleteRowCenteringOffset;

                float localZ = rowIndex * depthStep;

                minimumX = Mathf.Min(minimumX, localX);
                maximumX = Mathf.Max(maximumX, localX);
                minimumZ = Mathf.Min(minimumZ, localZ);
                maximumZ = Mathf.Max(maximumZ, localZ);

                placements[hexIndex] = new HexCellPlacement(
                    hexIndex,
                    rowIndex,
                    columnIndex,
                    new Vector3(localX, rectangleCenter.y, localZ));
            }

            float generatedCenterX = (minimumX + maximumX) * 0.5f;
            float generatedCenterZ = (minimumZ + maximumZ) * 0.5f;

            for (int placementIndex = 0; placementIndex < placements.Length; placementIndex++)
            {
                HexCellPlacement placement = placements[placementIndex];

                Vector3 centeredPosition = new Vector3(
                    rectangleCenter.x + placement.Position.x - generatedCenterX,
                    rectangleCenter.y,
                    rectangleCenter.z + placement.Position.z - generatedCenterZ);

                placements[placementIndex] = new HexCellPlacement(
                    placement.Index,
                    placement.RowIndex,
                    placement.ColumnIndex,
                    centeredPosition);
            }

            return new HexGridLayout(
                placements,
                gridDimensions.ColumnCount,
                gridDimensions.RowCount,
                hexRadius);
        }

        private static HexGridDimensions CalculateGridDimensions(
            int hexCount,
            float availableWidth,
            float availableDepth)
        {
            float targetAspect = Mathf.Max(1f, availableWidth / availableDepth);

            int bestColumnCount = hexCount;
            int bestRowCount = 1;
            float bestScore = float.PositiveInfinity;

            int maximumRowCount = Mathf.CeilToInt(Mathf.Sqrt(hexCount));

            for (int rowCount = 1; rowCount <= maximumRowCount; rowCount++)
            {
                int columnCount = Mathf.CeilToInt((float)hexCount / rowCount);

                if (columnCount < rowCount)
                    continue;

                float widthCoefficient = SquareRootOfThree *
                                         (columnCount + (rowCount > 1 ? 0.5f : 0f));

                float depthCoefficient = 1.5f * rowCount + 0.5f;

                float candidateAspect = widthCoefficient / depthCoefficient;
                int emptyCellCount = columnCount * rowCount - hexCount;

                float aspectScore = Mathf.Abs(Mathf.Log(candidateAspect / targetAspect));
                float emptyCellScore = emptyCellCount * 0.025f;

                float score = aspectScore + emptyCellScore;

                if (score < bestScore)
                {
                    bestScore = score;
                    bestColumnCount = columnCount;
                    bestRowCount = rowCount;
                }
            }

            return new HexGridDimensions(bestColumnCount, bestRowCount);
        }

        private static float CalculateHexRadius(
            int columnCount,
            int rowCount,
            float availableWidth,
            float availableDepth,
            float hexGap)
        {
            float shiftedRowCoefficient = rowCount > 1 ? 0.5f : 0f;

            float widthRadiusCoefficient = SquareRootOfThree *
                                           (columnCount + shiftedRowCoefficient);

            float widthGapCoefficient = columnCount - 1 + shiftedRowCoefficient;

            float depthRadiusCoefficient = 1.5f * rowCount + 0.5f;
            float depthGapCoefficient = rowCount - 1;

            float radiusByWidth =
                (availableWidth - hexGap * widthGapCoefficient) / widthRadiusCoefficient;

            float radiusByDepth =
                (availableDepth - hexGap * depthGapCoefficient) / depthRadiusCoefficient;

            return Mathf.Min(radiusByWidth, radiusByDepth);
        }
    }

    public readonly struct HexGridLayout
    {
        public HexGridLayout(
            IReadOnlyList<HexCellPlacement> placements,
            int columnCount,
            int rowCount,
            float hexRadius)
        {
            Placements = placements;
            ColumnCount = columnCount;
            RowCount = rowCount;
            HexRadius = hexRadius;
        }

        public IReadOnlyList<HexCellPlacement> Placements { get; }
        public int ColumnCount { get; }
        public int RowCount { get; }
        public float HexRadius { get; }
    }

    public readonly struct HexCellPlacement
    {
        public HexCellPlacement(
            int index,
            int rowIndex,
            int columnIndex,
            Vector3 position)
        {
            Index = index;
            RowIndex = rowIndex;
            ColumnIndex = columnIndex;
            Position = position;
        }

        public int Index { get; }
        public int RowIndex { get; }
        public int ColumnIndex { get; }
        public Vector3 Position { get; }
    }

    internal readonly struct HexGridDimensions
    {
        public HexGridDimensions(int columnCount, int rowCount)
        {
            ColumnCount = columnCount;
            RowCount = rowCount;
        }

        public int ColumnCount { get; }
        public int RowCount { get; }
    }
}

using CodeBase.HexGrid;
using UnityEngine;

public sealed class HexGridExample : MonoBehaviour
{
    [SerializeField] private GameObject hexPrefab;
    [SerializeField] private int hexCount = 17;
    [SerializeField] private float rectangleWidth = 12f;
    [SerializeField] private float rectangleDepth = 7f;
    [SerializeField] private float hexGap = 0.15f;

    private void Start()
    {
        HexGridLayout layout = HexGridGenerator.GenerateInRectangle(
            hexCount,
            transform.position,
            rectangleWidth,
            rectangleDepth,
            hexGap);

        foreach (HexCellPlacement placement in layout.Placements)
        {
            GameObject hexInstance = Instantiate(
                hexPrefab,
                placement.Position,
                Quaternion.identity,
                transform);

            hexInstance.name = $"Hex_{placement.Index}_Row_{placement.RowIndex}_Column_{placement.ColumnIndex}";
        }
    }
}