using System.Collections.Generic; using UnityEngine; /// /// 网格合并工具类 /// public static class MeshCombineUtils { /// /// 无限制常量,用于表示某个维度无边界限制 /// public const float UNLIMITED = -1f; /// /// 收集渲染器 /// public static List CollectRenderers(List roots) { List allRenderers = new(); foreach (var root in roots) { if (!root) { continue; } var render = root.GetComponentsInChildren(); if (render is not { Length: > 0 }) { continue; } allRenderers.AddRange(render); } return allRenderers; } /// /// 查找第一个未处理的渲染器 /// private static Renderer FindFirstUnprocessedRenderer(List renderers, HashSet processedRenderers) { foreach (var renderer in renderers) { if (!processedRenderers.Contains(renderer)) { return renderer; } } return null; } /// /// 通过邻近距离扩展组 /// private static void ExpandGroupByProximity(List allRenderers, List currentGroup, HashSet processedRenderers, float maxDistance) { bool addedNew = true; while (addedNew) { addedNew = false; // 查找与当前组中任意一个渲染器距离在阈值内的未处理渲染器 foreach (var renderer in allRenderers) { if (processedRenderers.Contains(renderer)) continue; // 检查是否与组内任何一个渲染器距离小于阈值 if (IsCloseToAnyInGroup(renderer, currentGroup, maxDistance)) { currentGroup.Add(renderer); processedRenderers.Add(renderer); addedNew = true; } } } } /// /// 检查渲染器是否与组内任一渲染器距离小于阈值 /// private static bool IsCloseToAnyInGroup(Renderer renderer, List group, float maxDistance) { foreach (var groupRenderer in group) { float distance = CalculateDistance(renderer, groupRenderer); if (distance <= maxDistance) { return true; } } return false; } /// /// 计算两个渲染器之间的距离 /// private static float CalculateDistance(Renderer renderer1, Renderer renderer2) { return Vector3.Distance(renderer1.transform.position, renderer2.transform.position); } /// /// 计算渲染组的中心点 /// public static Vector3 CalculateGroupCenter(List 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; } /// /// 根据相互距离对渲染器进行分组(优化算法) /// 只要物体与组内任意一个物体距离小于 maxDistance,就将其加入该组 /// 可选择性添加最大宽高限制,防止组范围过大 /// /// 要分组的渲染器列表 /// 相邻物体的最大距离 /// 组的最大宽度(-1表示宽度无限制) /// 组的最大高度(-1表示高度无限制) /// 分组结果 public static Dictionary> GroupRenderersByProximity(List renderers, float maxDistance, float maxWidth = UNLIMITED, float maxHeight = UNLIMITED) { var groups = new Dictionary>(); var groupIndex = 0; // 检查是否有边界限制(只要有一个维度限制就启用边界检查) bool hasBoundingLimit = maxWidth > 0 || maxHeight > 0; var maxGroupBounds = hasBoundingLimit ? new Vector2(maxWidth, maxHeight) : Vector2.zero; // 已分组的渲染器 var processedRenderers = new HashSet(); // 对所有渲染器进行处理 while (processedRenderers.Count < renderers.Count) { // 找到第一个未处理的渲染器作为新组的起点 Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers); if (startRenderer == null) break; // 创建新组并添加起点渲染器 var currentGroup = new List { 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; } /// /// 计算正六边形地图边界 /// /// 水平方向六边形个数 /// 垂直方向六边形个数 /// 内接圆直径 /// 是否为尖头向上排列(true=尖头向上, false=平边向上) /// 边界范围(x=宽度, y=高度) 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); } /// /// 为当前项目六边形地图计算边界的便利方法 /// /// 水平方向六边形个数 /// 垂直方向六边形个数 /// 边界范围 public static Vector2 CalculateCurrentHexagonMapBounds(int hexagonCountWidth = 12, int hexagonCountHeight = 9) { // 使用项目配置:内接圆直径3,尖头向上排列 return CalculateHexagonMapBounds(hexagonCountWidth, hexagonCountHeight, 3f, true); } /// /// 带边界框检查的组扩展 /// private static void ExpandGroupWithBoundingBoxCheck(List allRenderers, List currentGroup, HashSet 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; } } } /// /// 检查添加新渲染器后是否会超过组的最大边界 /// private static bool WouldExceedGroupBounds(Renderer newRenderer, List 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; } /// /// 计算组的边界框 /// private static Bounds CalculateGroupBounds(List 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; } /// /// 根据圆形区域对渲染器进行分组(原始算法,保留作参考) /// public static Dictionary> GroupRenderersByCircularDistance(List renderers, float maxDistance) { var groups = new Dictionary>(); var groupIndex = 0; // 已分组的渲染器 var processedRenderers = new HashSet(); // 对所有渲染器进行处理 while (processedRenderers.Count < renderers.Count) { // 找到第一个未处理的渲染器作为新组的起点 Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers); if (startRenderer == null) break; // 创建新组 var currentGroup = new List(); 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; } }