NLDClient-yudde/ProjectNLD/Assets/Editor/Scene/PostProcesser/Utils/MeshCombineUtils.cs

392 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 网格合并工具类
/// </summary>
public static class MeshCombineUtils
{
/// <summary>
/// 无限制常量,用于表示某个维度无边界限制
/// </summary>
public const float UNLIMITED = -1f;
/// <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;
}
/// <summary>
/// 查找第一个未处理的渲染器
/// </summary>
private static Renderer FindFirstUnprocessedRenderer(List<Renderer> renderers, HashSet<Renderer> processedRenderers)
{
foreach (var renderer in renderers)
{
if (!processedRenderers.Contains(renderer))
{
return renderer;
}
}
return null;
}
/// <summary>
/// 通过邻近距离扩展组
/// </summary>
private static void ExpandGroupByProximity(List<Renderer> allRenderers, List<Renderer> currentGroup,
HashSet<Renderer> 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;
}
}
}
}
/// <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;
}
}
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;
}
/// <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;
}
/// <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);
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;
}
}