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


using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Sbrt.Av.Gameplay
{
	public class AgentsInDepartmentGenerator
	{
		private readonly AgentsFragmentFactory fragmentFactory;
		private readonly MainConfigHolder mainConfigHolder;
		private readonly DataManager dataManager;

		public AgentsInDepartmentGenerator(
			AgentsFragmentFactory fragmentFactory,
			MainConfigHolder mainConfigHolder,
			DataManager dataManager)
		{
			this.fragmentFactory = fragmentFactory;
			this.mainConfigHolder = mainConfigHolder;
			this.dataManager = dataManager;
		}

		public List<DataFragment> SpawnAgents(
			int count,
			float platformWidth,
			List<long> ids,
			DataFragment prefab,
			Transform spawnPlane)
		{
			if (count <= 0)
				return new List<DataFragment>();

			var config = mainConfigHolder.AgentInDepartmentConfig;
			var fragmentBounds = ObjectToFrustumFitter.GetBoundsWithChildren(prefab.gameObject);
			var width = fragmentBounds.size.x;
			var normalizedWidth = width / prefab.transform.lossyScale.x;
			var fragments = new List<DataFragment>(count);

			var rows = Mathf.FloorToInt(Mathf.Sqrt(count));
			rows = Mathf.Max(1, rows);

			var columns = Mathf.CeilToInt((float)count / rows);

			var scale = 1f;
			prefab.transform.localScale = Vector3.one * scale;

			var rowLength = CalculateAgentRowLength(columns, scale, normalizedWidth);

			scale = platformWidth / rowLength;
			scale = Mathf.Clamp(scale, config.MinScale, config.MaxScale);

			prefab.transform.localScale = Vector3.one * scale;

			var startPosition = CalculateAgentStartPosition(columns, rows, normalizedWidth, scale, spawnPlane);
			var fragmentWidth = normalizedWidth * scale;

			var positions = CalculateAgentPositions(count, rows, columns, startPosition, fragmentWidth, config);

			CreateAgents(count, rows, columns, ids, positions, scale, fragments, prefab, spawnPlane);
			SetupAgentsScales(fragments, rows, columns, config, scale);
			AddRandomToAgentPosition(fragments, config);

			return fragments;
		}

		private static Vector3[,] CalculateAgentPositions(
			int agentCount,
			int rows,
			int columns,
			Vector3 startPosition,
			float fragmentWidth,
			AgentInDepartmentConfig config)
		{
			var positions = new Vector3[rows, columns];
			var placedElementsCount = 0;
			var firstPositionX = startPosition.x;
			var currentPositionZ = startPosition.z;

			for (var rowIndex = 0; rowIndex < rows && placedElementsCount < agentCount; rowIndex++)
			{
				if (rowIndex != 0)
				{
					currentPositionZ -= fragmentWidth + fragmentWidth * config.SpacingVertical;
				}

				var elementsInCurrentRow = Math.Min(columns - 1, agentCount - 1 - placedElementsCount);

				for (var columnIndex = 0; columnIndex < columns && placedElementsCount < agentCount; columnIndex++)
				{
					var currentPositionX = 0f;

					if (columnIndex == 0 && rowIndex == 0)
					{
						currentPositionX = firstPositionX;
					}
					else
					{
						if (rowIndex == 0)
						{
							currentPositionX = positions[rowIndex, columnIndex - 1].x +
							                   fragmentWidth +
							                   fragmentWidth * config.SpacingHorizontal;
						}
						else
						{
							currentPositionX = Mathf.Lerp(
								positions[0, 0].x,
								positions[0, columns - 1].x,
								(float)columnIndex / elementsInCurrentRow);
						}
					}

					positions[rowIndex, columnIndex] = new Vector3(currentPositionX, startPosition.y, currentPositionZ);
					placedElementsCount++;
				}
			}

			return positions;
		}

		private void CreateAgents(
			int agentCount,
			int rows,
			int columns,
			List<long> sortedIds,
			Vector3[,] positions,
			float scale,
			List<DataFragment> fragments,
			DataFragment prefab,
			Transform spawnPlane)
		{
			var spawnedElementsCount = 0;

			for (var rowIndex = 0; rowIndex < rows && spawnedElementsCount < agentCount; rowIndex++)
			{
				for (var columnIndex = 0; columnIndex < columns && spawnedElementsCount < agentCount; columnIndex++)
				{
					var fragmentId = sortedIds[spawnedElementsCount];
					var fragment = fragmentFactory.CreateAgent(
						prefab,
						positions[rowIndex, columnIndex],
						spawnPlane.rotation,
						Vector3.one * scale,
						prefab.transform.parent,
						fragmentId);

					var data = dataManager.GetData<AsDataModel>(fragmentId);
					fragment.SetAgent(data.AgentCount > 0);

					fragments.Add(fragment);
					spawnedElementsCount++;
				}
			}
		}

		private void SetupAgentsScales(
			List<DataFragment> fragments,
			int rows,
			int columns,
			AgentInDepartmentConfig config,
			float maxScale)
		{
			var minScale = maxScale * config.MinScaleAspect;
			var scaleStep = config.RowScaleStep;

			for (var fragmentIndex = 0; fragmentIndex < fragments.Count; fragmentIndex++)
			{
				var fragment = fragments[fragmentIndex];
				var rowIndex = fragmentIndex / columns;
				var rowsFromBottom = rows - 1 - rowIndex;

				var scale = minScale + rowsFromBottom * scaleStep;
				scale = Mathf.Clamp(scale, minScale, maxScale);

				fragment.transform.localScale = Vector3.one * scale;
			}
		}

		private float CalculateAgentRowLength(int columns, float scale, float normalizedWidth)
		{
			var config = mainConfigHolder.AsInDepartmentConfig;
			var rowLength = 0f;
			var previousWidth = 0f;

			for (var columnIndex = 0; columnIndex < columns; columnIndex++)
			{
				var currentWidth = normalizedWidth * scale;
				rowLength += previousWidth / 2 + currentWidth / 2;

				if (columnIndex < columns - 1)
					rowLength += currentWidth / 2 * config.AsSpacingHorizontal;

				previousWidth = currentWidth;
			}

			return rowLength;
		}

		private float CalculateAgentColumnLength(int rows, float scale, float normalizedWidth)
		{
			var config = mainConfigHolder.AsInDepartmentConfig;
			var columnLength = 0f;
			var previousWidth = 0f;

			for (var rowIndex = 0; rowIndex < rows; rowIndex++)
			{
				var currentWidth = normalizedWidth * scale;
				columnLength += previousWidth / 2 + currentWidth / 2;

				if (rowIndex < rows - 1)
					columnLength += currentWidth / 2 * config.AsSpacingVertical;

				previousWidth = currentWidth;
			}

			return columnLength;
		}

		private Vector3 CalculateAgentStartPosition(
			int columns,
			int rows,
			float normalizedWidth,
			float scale,
			Transform spawnPlane)
		{
			var startPosition = spawnPlane.position;

			var startPositionX = startPosition.x - CalculateAgentRowLength(columns, scale, normalizedWidth) / 2;
			var startPositionZ = startPosition.z + CalculateAgentColumnLength(rows, scale, normalizedWidth) / 2;

			return new Vector3(startPositionX, startPosition.y, startPositionZ);
		}

		private static void AddRandomToAgentPosition(IList<DataFragment> fragments, AgentInDepartmentConfig config)
		{
			if (fragments == null || fragments.Count == 0)
				return;

			var finalPositions = new Vector3[fragments.Count];
			var fragmentHalfSizes = new Vector2[fragments.Count];

			for (var fragmentIndex = 0; fragmentIndex < fragments.Count; fragmentIndex++)
			{
				var bounds = ObjectToFrustumFitter.GetBoundsWithChildren(fragments[fragmentIndex].gameObject);
				finalPositions[fragmentIndex] = fragments[fragmentIndex].transform.position;
				fragmentHalfSizes[fragmentIndex] = new Vector2(bounds.size.x * 0.5f, bounds.size.z * 0.5f);
			}

			for (var fragmentIndex = 0; fragmentIndex < fragments.Count; fragmentIndex++)
			{
				var basePosition = finalPositions[fragmentIndex];
				var halfExtents = fragmentHalfSizes[fragmentIndex];

				var maxOffsetX = Mathf.Abs(config.AdditionalRandomPositionRangeX) * halfExtents.x * 2f;
				var maxOffsetZ = Mathf.Abs(config.AdditionalRandomPositionRangeZ) * halfExtents.y * 2f;

				var foundValidPosition = false;

				for (var attemptIndex = 0;
				     attemptIndex < config.MaxPlacementIteration && !foundValidPosition;
				     attemptIndex++)
				{
					var candidatePosition = basePosition + new Vector3(
						Random.Range(-maxOffsetX, maxOffsetX),
						0f,
						Random.Range(-maxOffsetZ, maxOffsetZ));

					var overlaps = IsOverlaps(
						fragmentIndex,
						fragmentHalfSizes,
						candidatePosition,
						finalPositions,
						halfExtents);

					if (!overlaps)
					{
						finalPositions[fragmentIndex] = candidatePosition;
						foundValidPosition = true;
					}
					else if ((attemptIndex + 1) % config.IterationBeforeClampOffset == 0)
					{
						maxOffsetX *= 0.85f;
						maxOffsetZ *= 0.85f;
					}
				}
			}

			for (var fragmentIndex = 0; fragmentIndex < fragments.Count; fragmentIndex++)
				fragments[fragmentIndex].transform.position = finalPositions[fragmentIndex];
		}

		private static bool IsOverlaps(
			int currentIndex,
			Vector2[] fragmentHalfSizes,
			Vector3 candidatePosition,
			Vector3[] finalPositions,
			Vector2 halfExtents)
		{
			for (var otherIndex = 0; otherIndex < currentIndex; otherIndex++)
			{
				var otherHalfExtents = fragmentHalfSizes[otherIndex];
				var delta = candidatePosition - finalPositions[otherIndex];
				delta.y = 0f;

				if (Mathf.Abs(delta.x) < halfExtents.x + otherHalfExtents.x &&
				    Mathf.Abs(delta.z) < halfExtents.y + otherHalfExtents.y)
				{
					return true;
				}
			}

			return false;
		}
	}
}