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


	public class HexInsideHexGenerator : IInitializable
	{
		private const float Sqrt3 = 1.7320508f;
		private const float HexApothemFactor = Sqrt3 * 0.5f;
		private readonly Vector2Int[] AxialDirections =
		{
			new Vector2Int(1, 0),
			new Vector2Int(1, -1),
			new Vector2Int(0, -1),
			new Vector2Int(-1, 0),
			new Vector2Int(-1, 1),
			new Vector2Int(0, 1)
		};
		private BusinesCapabilitiesFragmentFactory fragmentFactory;
		private MainConfigHolder mainConfigHolder;
		private DataManager dataManager;
		private BusinessCapabilitiesInDepartmentConfig config;

		public HexInsideHexGenerator(BusinesCapabilitiesFragmentFactory fragmentFactory,
			MainConfigHolder mainConfigHolder, DataManager dataManager)
		{
			this.fragmentFactory = fragmentFactory;
			this.mainConfigHolder = mainConfigHolder;
			this.dataManager = dataManager;
		}

		public void Initialize()
		{
			config = mainConfigHolder.BusinessCapabilitiesInDepartmentConfig;
		}

		public List<FbcDataFragment> Generate(int count, List<long> ids, FbcDataFragment prefab, GameObject emptyPrefab,
			Transform root, Transform outerHex)
		{
			if (prefab == null)
			{
				Debug.LogWarning("inner hex prefab is not assigned.");
				return new List<FbcDataFragment>();
			}
			
			if (ids.IsEmpty())
			{
				Debug.LogWarning("ids is empty");
				return new List<FbcDataFragment>();
			}

			if (root == null)
			{
				Debug.LogWarning("cannot resolve generated root.");
				return new List<FbcDataFragment>();
			}

			ClearChildren(root, prefab);
			Transform actualOuterVisualRoot = outerHex;
			if (!TryCalculateLocalBounds(actualOuterVisualRoot, outerHex, out Bounds outerLocalBounds, root))
			{
				Debug.LogWarning("cannot calculate outer bounds.");
				return new List<FbcDataFragment>();
			}

			if (!TryCalculatePrefabLocalBounds(prefab.gameObject, outerHex, out Bounds innerLocalBounds))
			{
				Debug.LogWarning("cannot calculate inner prefab bounds.");
				return new List<FbcDataFragment>();
			}

			float outerRadius = ResolveHexRadius(outerLocalBounds);
			float innerBaseRadius = ResolveHexRadius(innerLocalBounds);
			float effectiveOuterRadius = ApplyPaddingRatioToHexRadius(outerRadius, config.InHexPadding);
			if (outerRadius <= 0f)
			{
				Debug.LogWarning("outer radius is zero.");
				return new List<FbcDataFragment>();
			}

			if (effectiveOuterRadius <= 0f)
			{
				Debug.LogWarning("effective outer radius is zero. Reduce outer padding ratio.");
				return new List<FbcDataFragment>();
			}

			if (innerBaseRadius <= 0f)
			{
				Debug.LogWarning("inner radius is zero.");
				return new List<FbcDataFragment>();
			}

			List<Vector2Int> cells = BuildSpiralCells(count);
			float finalScale =
				FindMaximumFittingScale(cells, effectiveOuterRadius, innerBaseRadius, config.InHexGapRatio);
			float finalInnerRadius = innerBaseRadius * finalScale;
			Vector2 outerCenterXZ = GetXZPosition(outerLocalBounds.center);
			List<Vector2> localXZPositions = BuildCenteredXZPositions(cells, finalInnerRadius, config.InHexGapRatio);

			var tmpScale = prefab.transform.localScale;
			var tmpPos = prefab.transform.position;
			prefab.transform.localScale *= finalScale;
			var prefabBounds = ObjectToFrustumFitter.GetBoundsWithChildren(prefab.gameObject);
			var outerBounds = ObjectToFrustumFitter.GetBoundsWithChildren(outerHex.gameObject);
			prefab.transform.SetY(outerBounds.max.y - prefabBounds.size.y);
			
			var localY = prefab.transform.localPosition.y;
			prefab.transform.localScale = tmpScale;
			prefab.transform.position = tmpPos;
			
			List<FbcDataFragment> results = new List<FbcDataFragment>(localXZPositions.Count);
			for (int i = 0; i < localXZPositions.Count; i++)
			{
				Vector2 finalXZPosition = outerCenterXZ + localXZPositions[i];
				FbcDataFragment fragment = fragmentFactory.CreateFBC(prefab,
					new Vector3(finalXZPosition.x, config.InHexSurfaceOffset, finalXZPosition.y),
					prefab.transform.localRotation, finalScale, root.transform, ids[i]);
				fragment.name = $"{prefab.name} {i}";
				
				
				fragment.transform.localPosition = new Vector3(finalXZPosition.x, localY, finalXZPosition.y);
				fragment.transform.localRotation = prefab.transform.localRotation;
				fragment.transform.localScale = prefab.transform.localScale * finalScale;
				results.Add(fragment);
			}

			return results;
		}

		private void ClearChildren(Transform root, FbcDataFragment prefab)
		{
			for (int childIndex = root.childCount - 1; childIndex >= 0; childIndex--)
			{
				var target = root.GetChild(childIndex).gameObject;
				if (prefab.gameObject == target)
					continue;

				DestroySmart(target);
			}
		}

		private void DestroySmart(Object target)
		{
			if (target == null)
				return;
			if (Application.isPlaying)
				Object.Destroy(target);
			else
				Object.DestroyImmediate(target);
		}

		private bool TryCalculatePrefabLocalBounds(GameObject prefab, Transform outerHex, out Bounds localBounds)
		{
			GameObject probe = UnityEngine.Object.Instantiate(prefab, outerHex, false);
			probe.name = $"{prefab.name}_BoundsProbe";
			probe.gameObject.SetActive(true);
			bool result = TryCalculateLocalBounds(probe.transform, outerHex, out localBounds, null);
			DestroySmart(probe);
			return result;
		}

		private bool TryCalculateLocalBounds(Transform sourceRoot, Transform relativeTo, out Bounds localBounds,
			Transform excludedRoot)
		{
			localBounds = default;
			bool hasBounds = false;
			Renderer[] renderers = sourceRoot.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer renderer in renderers)
			{
				if (excludedRoot != null && renderer.transform.IsChildOf(excludedRoot))
				{
					continue;
				}

				EncapsulateWorldBoundsAsLocal(renderer.bounds, relativeTo, ref localBounds, ref hasBounds);
			}

			if (!hasBounds)
			{
				Collider[] colliders = sourceRoot.GetComponentsInChildren<Collider>(true);
				foreach (Collider collider in colliders)
				{
					if (excludedRoot != null && collider.transform.IsChildOf(excludedRoot))
					{
						continue;
					}

					EncapsulateWorldBoundsAsLocal(collider.bounds, relativeTo, ref localBounds, ref hasBounds);
				}
			}

			return hasBounds;
		}

		private void EncapsulateWorldBoundsAsLocal(Bounds worldBounds, Transform relativeTo, ref Bounds localBounds,
			ref bool hasBounds)
		{
			Vector3 min = worldBounds.min;
			Vector3 max = worldBounds.max;
			for (int xIndex = 0; xIndex <= 1; xIndex++)
			{
				for (int yIndex = 0; yIndex <= 1; yIndex++)
				{
					for (int zIndex = 0; zIndex <= 1; zIndex++)
					{
						Vector3 worldCorner = new Vector3(xIndex == 0 ? min.x : max.x, yIndex == 0 ? min.y : max.y,
							zIndex == 0 ? min.z : max.z);
						Vector3 localCorner = relativeTo.InverseTransformPoint(worldCorner);
						if (!hasBounds)
						{
							localBounds = new Bounds(localCorner, Vector3.zero);
							hasBounds = true;
						}
						else
						{
							localBounds.Encapsulate(localCorner);
						}
					}
				}
			}
		}

		private float ResolveHexRadius(Bounds localBounds)
		{
			Vector2 extentsXZ = GetXZExtents(localBounds);
			return Mathf.Min(extentsXZ.x, extentsXZ.y * 2f / Sqrt3);
		}

		private Vector2 GetXZExtents(Bounds bounds)
		{
			return new Vector2(bounds.extents.x, bounds.extents.z);
		}

		private Vector2 GetXZPosition(Vector3 position)
		{
			return new Vector2(position.x, position.z);
		}

		private float ApplyPaddingRatioToHexRadius(float radius, float paddingRatio)
		{
			float clampedPaddingRatio = Mathf.Clamp01(paddingRatio);
			return radius * (1f - clampedPaddingRatio);
		}

		private float CalculateGapUnits(float innerRadius, float gapRatio)
		{
			float safeGapRatio = Mathf.Max(0f, gapRatio);
			float innerFlatToFlatSize = innerRadius * Sqrt3;
			return innerFlatToFlatSize * safeGapRatio;
		}

		private List<Vector2Int> BuildSpiralCells(int count)
		{
			List<Vector2Int> cells = new List<Vector2Int>(count) {Vector2Int.zero};
			for (int radius = 1; cells.Count < count; radius++)
			{
				Vector2Int cell = Multiply(AxialDirections[4], radius);
				for (int sideIndex = 0; sideIndex < 6 && cells.Count < count; sideIndex++)
				{
					for (int stepIndex = 0; stepIndex < radius && cells.Count < count; stepIndex++)
					{
						cells.Add(cell);
						cell += AxialDirections[sideIndex];
					}
				}
			}

			return cells;
		}

		private Vector2Int Multiply(Vector2Int value, int multiplier)
		{
			return new Vector2Int(value.x * multiplier, value.y * multiplier);
		}

		private List<Vector2> BuildCenteredXZPositions(IReadOnlyList<Vector2Int> cells, float innerRadius,
			float gapRatio)
		{
			float gap = CalculateGapUnits(innerRadius, gapRatio);
			float pitchRadius = innerRadius + gap / Sqrt3;
			List<Vector2> positions = new List<Vector2>(cells.Count);
			Vector2 positionsSum = Vector2.zero;
			for (int cellIndex = 0; cellIndex < cells.Count; cellIndex++)
			{
				Vector2 position = AxialToXZ(cells[cellIndex], pitchRadius);
				positions.Add(position);
				positionsSum += position;
			}

			Vector2 groupCenter = positionsSum / cells.Count;
			for (int positionIndex = 0; positionIndex < positions.Count; positionIndex++)
			{
				positions[positionIndex] -= groupCenter;
			}

			return positions;
		}

		private Vector2 AxialToXZ(Vector2Int axial, float pitchRadius)
		{
			float q = axial.x;
			float r = axial.y;
			return new Vector2(1.5f * pitchRadius * q, Sqrt3 * pitchRadius * (r + q * 0.5f));
		}

		private float FindMaximumFittingScale(IReadOnlyList<Vector2Int> cells, float outerRadius, float innerBaseRadius,
			float gapRatio)
		{
			float low = 0f;
			float high = outerRadius / innerBaseRadius;
			for (int iteration = 0; iteration < 48; iteration++)
			{
				float middle = (low + high) * 0.5f;
				bool fits = CanFitScale(cells, outerRadius, innerBaseRadius, gapRatio, middle);
				if (fits)
				{
					low = middle;
				}
				else
				{
					high = middle;
				}
			}

			return low;
		}

		private bool CanFitScale(IReadOnlyList<Vector2Int> cells, float outerRadius, float innerBaseRadius,
			float gapRatio, float scale)
		{
			float innerRadius = innerBaseRadius * scale;
			List<Vector2> positions = BuildCenteredXZPositions(cells, innerRadius, gapRatio);
			for (int positionIndex = 0; positionIndex < positions.Count; positionIndex++)
			{
				Vector2[] innerVertices = CreateHexVertices(positions[positionIndex], innerRadius);
				for (int vertexIndex = 0; vertexIndex < innerVertices.Length; vertexIndex++)
				{
					if (!IsPointInsideHex(innerVertices[vertexIndex], outerRadius))
					{
						return false;
					}
				}
			}

			return true;
		}

		private Vector2[] CreateHexVertices(Vector2 center, float radius)
		{
			Vector2[] vertices = new Vector2[6];
			float angleOffsetDegrees = 0f;
			for (int vertexIndex = 0; vertexIndex < vertices.Length; vertexIndex++)
			{
				float angleRadians = Mathf.Deg2Rad * (angleOffsetDegrees + 60f * vertexIndex);
				vertices[vertexIndex] =
					center + new Vector2(Mathf.Cos(angleRadians) * radius, Mathf.Sin(angleRadians) * radius);
			}

			return vertices;
		}

		private bool IsPointInsideHex(Vector2 point, float radius)
		{
			float epsilon = Mathf.Epsilon;
			float apothem = radius * HexApothemFactor;
			return Mathf.Abs(point.y) <= apothem + epsilon &&
			       Mathf.Abs(Sqrt3 * 0.5f * point.x + 0.5f * point.y) <= apothem + epsilon &&
			       Mathf.Abs(Sqrt3 * 0.5f * point.x - 0.5f * point.y) <= apothem + epsilon;
		}
	}