2025-04-25 15:07:22 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 网格合并工具类
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static class MeshCombineUtils
|
|
|
|
|
|
{
|
2025-08-01 17:40:16 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 无限制常量,用于表示某个维度无边界限制
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public const float UNLIMITED = -1f;
|
|
|
|
|
|
|
2025-04-25 15:07:22 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 收集渲染器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static List<Renderer> CollectRenderers(List<Transform> roots)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<Renderer> allRenderers = new();
|
|
|
|
|
|
foreach (var root in roots)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!root)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var render = root.GetComponentsInChildren<Renderer>();
|
|
|
|
|
|
if (render is not { Length: > 0 })
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
allRenderers.AddRange(render);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return allRenderers;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-14 16:34:24 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查找第一个未处理的渲染器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static Renderer FindFirstUnprocessedRenderer(List<Renderer> renderers, HashSet<Renderer> processedRenderers)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var renderer in renderers)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!processedRenderers.Contains(renderer))
|
|
|
|
|
|
{
|
|
|
|
|
|
return renderer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-01 17:40:16 +08:00
|
|
|
|
|
2025-05-14 16:34:24 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过邻近距离扩展组
|
|
|
|
|
|
/// </summary>
|
2025-08-01 17:40:16 +08:00
|
|
|
|
private static void ExpandGroupByProximity(List<Renderer> allRenderers, List<Renderer> currentGroup,
|
2025-05-14 16:34:24 +08:00
|
|
|
|
HashSet<Renderer> processedRenderers, float maxDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool addedNew = true;
|
|
|
|
|
|
while (addedNew)
|
|
|
|
|
|
{
|
|
|
|
|
|
addedNew = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找与当前组中任意一个渲染器距离在阈值内的未处理渲染器
|
|
|
|
|
|
foreach (var renderer in allRenderers)
|
2025-04-25 15:07:22 +08:00
|
|
|
|
{
|
2025-05-14 16:34:24 +08:00
|
|
|
|
if (processedRenderers.Contains(renderer))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否与组内任何一个渲染器距离小于阈值
|
|
|
|
|
|
if (IsCloseToAnyInGroup(renderer, currentGroup, maxDistance))
|
2025-04-25 15:07:22 +08:00
|
|
|
|
{
|
2025-05-14 16:34:24 +08:00
|
|
|
|
currentGroup.Add(renderer);
|
|
|
|
|
|
processedRenderers.Add(renderer);
|
|
|
|
|
|
addedNew = true;
|
2025-04-25 15:07:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-14 16:34:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查渲染器是否与组内任一渲染器距离小于阈值
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static bool IsCloseToAnyInGroup(Renderer renderer, List<Renderer> group, float maxDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var groupRenderer in group)
|
|
|
|
|
|
{
|
|
|
|
|
|
float distance = CalculateDistance(renderer, groupRenderer);
|
|
|
|
|
|
if (distance <= maxDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-01 17:40:16 +08:00
|
|
|
|
|
2025-05-14 16:34:24 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 计算两个渲染器之间的距离
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static float CalculateDistance(Renderer renderer1, Renderer renderer2)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Vector3.Distance(renderer1.transform.position, renderer2.transform.position);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 计算渲染组的中心点
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static Vector3 CalculateGroupCenter(List<Renderer> group)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (group.Count == 0)
|
|
|
|
|
|
return Vector3.zero;
|
|
|
|
|
|
|
|
|
|
|
|
var sum = Vector3.zero;
|
|
|
|
|
|
foreach (var renderer in group)
|
|
|
|
|
|
{
|
|
|
|
|
|
sum += renderer.transform.position;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sum / group.Count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-01 17:40:16 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据相互距离对渲染器进行分组(优化算法)
|
|
|
|
|
|
/// 只要物体与组内任意一个物体距离小于 maxDistance,就将其加入该组
|
|
|
|
|
|
/// 可选择性添加最大宽高限制,防止组范围过大
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="renderers">要分组的渲染器列表</param>
|
|
|
|
|
|
/// <param name="maxDistance">相邻物体的最大距离</param>
|
|
|
|
|
|
/// <param name="maxWidth">组的最大宽度(-1表示宽度无限制)</param>
|
|
|
|
|
|
/// <param name="maxHeight">组的最大高度(-1表示高度无限制)</param>
|
|
|
|
|
|
/// <returns>分组结果</returns>
|
|
|
|
|
|
public static Dictionary<string, List<Renderer>> GroupRenderersByProximity(List<Renderer> renderers,
|
|
|
|
|
|
float maxDistance, float maxWidth = UNLIMITED, float maxHeight = UNLIMITED)
|
|
|
|
|
|
{
|
|
|
|
|
|
var groups = new Dictionary<string, List<Renderer>>();
|
|
|
|
|
|
var groupIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有边界限制(只要有一个维度限制就启用边界检查)
|
|
|
|
|
|
bool hasBoundingLimit = maxWidth > 0 || maxHeight > 0;
|
|
|
|
|
|
var maxGroupBounds = hasBoundingLimit ? new Vector2(maxWidth, maxHeight) : Vector2.zero;
|
|
|
|
|
|
|
|
|
|
|
|
// 已分组的渲染器
|
|
|
|
|
|
var processedRenderers = new HashSet<Renderer>();
|
|
|
|
|
|
|
|
|
|
|
|
// 对所有渲染器进行处理
|
|
|
|
|
|
while (processedRenderers.Count < renderers.Count)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 找到第一个未处理的渲染器作为新组的起点
|
|
|
|
|
|
Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers);
|
|
|
|
|
|
|
|
|
|
|
|
if (startRenderer == null)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新组并添加起点渲染器
|
|
|
|
|
|
var currentGroup = new List<Renderer> { startRenderer };
|
|
|
|
|
|
processedRenderers.Add(startRenderer);
|
|
|
|
|
|
|
|
|
|
|
|
// 扩展当前组
|
|
|
|
|
|
if (hasBoundingLimit)
|
|
|
|
|
|
{
|
|
|
|
|
|
ExpandGroupWithBoundingBoxCheck(renderers, currentGroup, processedRenderers, maxDistance,
|
|
|
|
|
|
maxGroupBounds);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
ExpandGroupByProximity(renderers, currentGroup, processedRenderers, maxDistance);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加组到结果
|
|
|
|
|
|
groups.Add($"Group_{groupIndex}", currentGroup);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算最终组的边界
|
|
|
|
|
|
var finalBounds = CalculateGroupBounds(currentGroup);
|
|
|
|
|
|
float finalWidth = finalBounds.max.x - finalBounds.min.x;
|
|
|
|
|
|
float finalHeight = finalBounds.max.z - finalBounds.min.z;
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[分组] 组{groupIndex}完成,包含{currentGroup.Count}个物体,最终尺寸:{finalWidth:F2}x{finalHeight:F2}");
|
|
|
|
|
|
Debug.Log($"[分组] =====================================");
|
|
|
|
|
|
|
|
|
|
|
|
groupIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string limitInfo;
|
|
|
|
|
|
if (!hasBoundingLimit)
|
|
|
|
|
|
{
|
|
|
|
|
|
limitInfo = ",无边界限制";
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
string widthLimit = maxWidth > 0 ? maxWidth.ToString() : "无限制";
|
|
|
|
|
|
string heightLimit = maxHeight > 0 ? maxHeight.ToString() : "无限制";
|
|
|
|
|
|
limitInfo = $",最大范围为{widthLimit}x{heightLimit}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return groups;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 计算正六边形地图边界
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="hexagonCountWidth">水平方向六边形个数</param>
|
|
|
|
|
|
/// <param name="hexagonCountHeight">垂直方向六边形个数</param>
|
|
|
|
|
|
/// <param name="hexagonInscribedDiameter">内接圆直径</param>
|
|
|
|
|
|
/// <param name="pointyTop">是否为尖头向上排列(true=尖头向上, false=平边向上)</param>
|
|
|
|
|
|
/// <returns>边界范围(x=宽度, y=高度)</returns>
|
|
|
|
|
|
public static Vector2 CalculateHexagonMapBounds(int hexagonCountWidth, int hexagonCountHeight, float hexagonInscribedDiameter, bool pointyTop = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 六边形内接圆半径
|
|
|
|
|
|
float inscribedRadius = hexagonInscribedDiameter / 2f;
|
|
|
|
|
|
|
|
|
|
|
|
// 六边形边长 = 内接圆半径 / cos(30°) = 内接圆半径 / (√3/2) = 内接圆半径 * 2 / √3
|
|
|
|
|
|
float hexagonSideLength = inscribedRadius * 2f / Mathf.Sqrt(3f);
|
|
|
|
|
|
|
|
|
|
|
|
float singleHexWidth, singleHexHeight;
|
|
|
|
|
|
|
|
|
|
|
|
if (pointyTop)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 尖头向上排列 ◊(如图所示的排列方式)
|
|
|
|
|
|
// 单个六边形水平宽度 = √3 * 边长
|
|
|
|
|
|
singleHexWidth = Mathf.Sqrt(3f) * hexagonSideLength;
|
|
|
|
|
|
// 垂直方向:蜂窝排列时,行与行之间的间距 = 1.5 * 边长
|
|
|
|
|
|
singleHexHeight = 1.5f * hexagonSideLength;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 平边向上排列
|
|
|
|
|
|
// 水平方向:蜂窝排列时,列与列之间的间距 = 1.5 * 边长
|
|
|
|
|
|
singleHexWidth = 1.5f * hexagonSideLength;
|
|
|
|
|
|
// 单个六边形垂直高度 = √3 * 边长
|
|
|
|
|
|
singleHexHeight = Mathf.Sqrt(3f) * hexagonSideLength;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 总的最大范围
|
|
|
|
|
|
float maxWidth = hexagonCountWidth * singleHexWidth;
|
|
|
|
|
|
float maxHeight = hexagonCountHeight * singleHexHeight;
|
|
|
|
|
|
|
|
|
|
|
|
return new Vector2(maxWidth, maxHeight);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为当前项目六边形地图计算边界的便利方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="hexagonCountWidth">水平方向六边形个数</param>
|
|
|
|
|
|
/// <param name="hexagonCountHeight">垂直方向六边形个数</param>
|
|
|
|
|
|
/// <returns>边界范围</returns>
|
|
|
|
|
|
public static Vector2 CalculateCurrentHexagonMapBounds(int hexagonCountWidth = 12, int hexagonCountHeight = 9)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 使用项目配置:内接圆直径3,尖头向上排列
|
|
|
|
|
|
return CalculateHexagonMapBounds(hexagonCountWidth, hexagonCountHeight, 3f, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 带边界框检查的组扩展
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static void ExpandGroupWithBoundingBoxCheck(List<Renderer> allRenderers, List<Renderer> currentGroup,
|
|
|
|
|
|
HashSet<Renderer> processedRenderers, float maxDistance, Vector2 maxGroupBounds)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool addedNew = true;
|
|
|
|
|
|
while (addedNew)
|
|
|
|
|
|
{
|
|
|
|
|
|
addedNew = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找与当前组中任意一个渲染器距离在阈值内的未处理渲染器
|
|
|
|
|
|
foreach (var renderer in allRenderers)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (processedRenderers.Contains(renderer))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否与组内任何一个渲染器距离小于阈值
|
|
|
|
|
|
if (!IsCloseToAnyInGroup(renderer, currentGroup, maxDistance))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查添加该渲染器后组的边界框是否超过最大范围
|
|
|
|
|
|
if (WouldExceedGroupBounds(renderer, currentGroup, maxGroupBounds))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
currentGroup.Add(renderer);
|
|
|
|
|
|
processedRenderers.Add(renderer);
|
|
|
|
|
|
addedNew = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查添加新渲染器后是否会超过组的最大边界
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static bool WouldExceedGroupBounds(Renderer newRenderer, List<Renderer> currentGroup,
|
|
|
|
|
|
Vector2 maxGroupBounds)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 计算当前组的边界框
|
|
|
|
|
|
var bounds = CalculateGroupBounds(currentGroup);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加新渲染器的位置
|
|
|
|
|
|
var newPos = newRenderer.transform.position;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算添加新渲染器后的边界框
|
|
|
|
|
|
float minX = Mathf.Min(bounds.min.x, newPos.x);
|
|
|
|
|
|
float maxX = Mathf.Max(bounds.max.x, newPos.x);
|
|
|
|
|
|
float minZ = Mathf.Min(bounds.min.z, newPos.z);
|
|
|
|
|
|
float maxZ = Mathf.Max(bounds.max.z, newPos.z);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算新的宽度和高度(在XZ平面上,因为相机是倾斜的)
|
|
|
|
|
|
float newWidth = maxX - minX;
|
|
|
|
|
|
float newHeight = maxZ - minZ;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否超过最大范围(只检查大于0的限制维度)
|
|
|
|
|
|
bool exceedsWidth = maxGroupBounds.x > 0 && newWidth > maxGroupBounds.x;
|
|
|
|
|
|
bool exceedsHeight = maxGroupBounds.y > 0 && newHeight > maxGroupBounds.y;
|
|
|
|
|
|
|
|
|
|
|
|
bool wouldExceed = exceedsWidth || exceedsHeight;
|
|
|
|
|
|
|
|
|
|
|
|
return wouldExceed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 计算组的边界框
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static Bounds CalculateGroupBounds(List<Renderer> group)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (group.Count == 0)
|
|
|
|
|
|
return new Bounds();
|
|
|
|
|
|
|
|
|
|
|
|
var firstPos = group[0].transform.position;
|
|
|
|
|
|
var bounds = new Bounds(firstPos, Vector3.zero);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var renderer in group)
|
|
|
|
|
|
{
|
|
|
|
|
|
bounds.Encapsulate(renderer.transform.position);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return bounds;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-14 16:34:24 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据圆形区域对渲染器进行分组(原始算法,保留作参考)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static Dictionary<string, List<Renderer>> GroupRenderersByCircularDistance(List<Renderer> renderers,
|
|
|
|
|
|
float maxDistance)
|
|
|
|
|
|
{
|
|
|
|
|
|
var groups = new Dictionary<string, List<Renderer>>();
|
|
|
|
|
|
var groupIndex = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 已分组的渲染器
|
|
|
|
|
|
var processedRenderers = new HashSet<Renderer>();
|
|
|
|
|
|
|
|
|
|
|
|
// 对所有渲染器进行处理
|
|
|
|
|
|
while (processedRenderers.Count < renderers.Count)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 找到第一个未处理的渲染器作为新组的起点
|
|
|
|
|
|
Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers);
|
2025-04-25 15:07:22 +08:00
|
|
|
|
|
|
|
|
|
|
if (startRenderer == null)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新组
|
|
|
|
|
|
var currentGroup = new List<Renderer>();
|
|
|
|
|
|
currentGroup.Add(startRenderer);
|
|
|
|
|
|
processedRenderers.Add(startRenderer);
|
|
|
|
|
|
|
|
|
|
|
|
bool addedNew = true;
|
|
|
|
|
|
while (addedNew)
|
|
|
|
|
|
{
|
|
|
|
|
|
addedNew = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前组的中心点
|
|
|
|
|
|
var groupCenter = CalculateGroupCenter(currentGroup);
|
|
|
|
|
|
|
|
|
|
|
|
// 查找距离中心点在阈值内的未处理渲染器
|
|
|
|
|
|
foreach (var renderer in renderers)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (processedRenderers.Contains(renderer))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
var distance = Vector3.Distance(groupCenter, renderer.transform.position);
|
|
|
|
|
|
if (!(distance <= maxDistance)) continue;
|
|
|
|
|
|
currentGroup.Add(renderer);
|
|
|
|
|
|
processedRenderers.Add(renderer);
|
|
|
|
|
|
addedNew = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
groups.Add($"Group_{groupIndex}", currentGroup);
|
|
|
|
|
|
groupIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Debug.Log($"基于圆形区域分组:将{renderers.Count}个渲染器分成{groups.Count}个组,半径为{maxDistance}");
|
|
|
|
|
|
return groups;
|
|
|
|
|
|
}
|
2025-08-01 17:40:16 +08:00
|
|
|
|
}
|