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

707 lines
26 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;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BzKovSoft.ObjectSlicer;
using UnityEditor;
using UnityEngine;
public class ModelSlicerUtil
{
private static ModelSlicerUtil _instance;
public static ModelSlicerUtil Instance => _instance ??= new ModelSlicerUtil();
private Material _defaultSliceMaterial;
private bool _debugMode = false;
private bool _saveMeshesToAssets = false;
private string _meshSavePath = "SlicedMeshes";
private bool _addTimestampToMeshName = true;
/// <summary>
/// 对模型进行切割处理
/// </summary>
public async Task SliceModels(
List<Transform> rootsToSlice, GameObject groundObj, Material sliceMaterial,
Vector3 planeDirection = default, bool ignoreObjectBoundsCheck = false,
bool asynchronously = false, bool preserveOriginalModels = false, Transform slicerRoot = null,
bool saveMeshesToAssets = false, string meshSavePath = "SlicedMeshes", bool addTimestampToMeshName = true)
{
// 设置选项
_debugMode = preserveOriginalModels;
_saveMeshesToAssets = saveMeshesToAssets;
_meshSavePath = string.IsNullOrEmpty(meshSavePath) ? "SlicedMeshes" : meshSavePath;
_addTimestampToMeshName = addTimestampToMeshName;
_defaultSliceMaterial = sliceMaterial;
if (rootsToSlice == null || rootsToSlice.Count == 0 || groundObj == null)
{
Debug.LogWarning("切割模型参数不完整,跳过切割处理");
return;
}
// 准备保存目录
if (_saveMeshesToAssets) EnsureSaveDirectoryExists();
// 处理切割
Dictionary<Transform, Transform> originalToProcessedMap = new Dictionary<Transform, Transform>();
Plane slicePlane = CreateSlicePlane(groundObj, planeDirection);
List<Task> sliceTasks = new List<Task>();
// 处理每个根节点
foreach (var root in rootsToSlice)
{
if (root == null) continue;
Transform processedModel = await ProcessRootObject(root, slicePlane, ignoreObjectBoundsCheck, asynchronously, sliceTasks, slicerRoot);
if (processedModel != null) originalToProcessedMap[root] = processedModel;
}
// 等待所有切割任务完成
if (sliceTasks.Count > 0) await Task.WhenAll(sliceTasks);
// 处理替换或保存
if (!_debugMode) ReplaceOriginalModels(originalToProcessedMap);
if (_saveMeshesToAssets) SaveMeshes(originalToProcessedMap);
}
/// <summary>
/// 保存处理后的网格
/// </summary>
private void SaveMeshes(Dictionary<Transform, Transform> originalToProcessedMap)
{
int savedCount = 0;
foreach (var pair in originalToProcessedMap)
{
savedCount += SaveProcessedMeshes(pair.Value);
}
if (savedCount > 0)
{
Debug.Log($"<color=green>已成功保存 {savedCount} 个切割后的网格到路径: Assets/{_meshSavePath}</color>");
AssetDatabase.Refresh();
}
else
{
Debug.LogWarning($"没有网格被保存到路径: Assets/{_meshSavePath}");
}
}
/// <summary>
/// 确保保存目录存在
/// </summary>
private void EnsureSaveDirectoryExists()
{
string fullPath = Path.Combine(Application.dataPath, _meshSavePath);
if (!Directory.Exists(fullPath))
{
try
{
Directory.CreateDirectory(fullPath);
Debug.Log($"创建网格保存目录: {fullPath}");
}
catch (Exception ex)
{
Debug.LogError($"创建网格保存目录失败: {ex.Message}");
_saveMeshesToAssets = false;
}
}
}
/// <summary>
/// 保存处理后模型的所有网格
/// </summary>
private int SaveProcessedMeshes(Transform processedRoot)
{
if (processedRoot == null) return 0;
int savedCount = 0;
MeshFilter[] meshFilters = processedRoot.GetComponentsInChildren<MeshFilter>(true);
string timestamp = _addTimestampToMeshName ? $"_{DateTime.Now:yyyyMMdd_HHmmss}" : "";
foreach (var meshFilter in meshFilters)
{
if (meshFilter == null || meshFilter.sharedMesh == null) continue;
string objectPath = GetHierarchyPath(meshFilter.transform, processedRoot);
string meshName = $"{objectPath}{timestamp}";
if (SaveMeshAsset(meshFilter.sharedMesh, meshName)) savedCount++;
}
return savedCount;
}
/// <summary>
/// 获取物体在层级中的路径
/// </summary>
private string GetHierarchyPath(Transform transform, Transform stopAt)
{
if (transform == null) return "UnknownMesh";
List<string> pathParts = new List<string>();
Transform current = transform;
while (current != null && current != stopAt)
{
pathParts.Insert(0, SanitizeFileName(current.name));
current = current.parent;
}
if (stopAt != null) pathParts.Insert(0, SanitizeFileName(stopAt.name));
return string.Join("_", pathParts);
}
/// <summary>
/// 净化文件名,删除不允许的字符
/// </summary>
private string SanitizeFileName(string fileName)
{
char[] invalidChars = Path.GetInvalidFileNameChars();
foreach (char c in invalidChars)
{
fileName = fileName.Replace(c, '_');
}
return fileName;
}
/// <summary>
/// 将网格保存为资源文件
/// </summary>
private bool SaveMeshAsset(Mesh mesh, string meshName)
{
try
{
Mesh meshToSave = UnityEngine.Object.Instantiate(mesh);
meshToSave.name = meshName;
string assetPath = $"Assets/{_meshSavePath}/{meshName}.asset";
assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
AssetDatabase.CreateAsset(meshToSave, assetPath);
Debug.Log($"已保存网格: {assetPath}");
return true;
}
catch (Exception ex)
{
Debug.LogError($"保存网格 {meshName} 失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 创建切割平面
/// </summary>
private Plane CreateSlicePlane(GameObject groundObj, Vector3 planeDirection)
{
Vector3 planePosition = groundObj.transform.position;
Vector3 planeNormal = (planeDirection == Vector3.zero) ? groundObj.transform.up : planeDirection.normalized;
Plane slicePlane = new Plane(planeNormal, planePosition);
#if UNITY_EDITOR
Debug.DrawRay(planePosition, planeNormal * 5f, Color.red, 10f);
Debug.DrawRay(planePosition, Vector3.right * 5f, Color.green, 10f);
Debug.DrawRay(planePosition, Vector3.forward * 5f, Color.blue, 10f);
#endif
return slicePlane;
}
/// <summary>
/// 处理根对象,返回处理后的模型
/// </summary>
private async Task<Transform> ProcessRootObject(Transform root, Plane slicePlane, bool ignoreObjectBoundsCheck, bool asynchronously, List<Task> sliceTasks, Transform slicerRoot)
{
GameObject copy = null;
try
{
// 创建原始对象的副本
Transform parent = _debugMode ? slicerRoot : null;
copy = UnityEngine.Object.Instantiate(root.gameObject, root.position, root.rotation, parent);
// 处理副本
await SliceObjectHierarchy(copy.transform, slicePlane, ignoreObjectBoundsCheck, asynchronously, sliceTasks, slicerRoot);
return copy.transform;
}
catch (Exception ex)
{
Debug.LogError($"处理物体 {root.name} 时发生错误: {ex.Message}");
if (copy != null) UnityEngine.Object.DestroyImmediate(copy);
return null;
}
}
/// <summary>
/// 替换原始模型
/// </summary>
private void ReplaceOriginalModels(Dictionary<Transform, Transform> originalToProcessedMap)
{
foreach (var pair in originalToProcessedMap)
{
Transform original = pair.Key;
Transform processed = pair.Value;
if (original == null || processed == null) continue;
try
{
Transform originalParent = original.parent;
int originalSiblingIndex = original.GetSiblingIndex();
string originalName = original.name;
processed.SetParent(originalParent);
processed.SetSiblingIndex(originalSiblingIndex);
processed.name = originalName;
UnityEngine.Object.DestroyImmediate(original.gameObject);
}
catch (Exception ex)
{
Debug.LogError($"替换原始模型时发生错误: {ex.Message}");
}
}
}
/// <summary>
/// 递归切割对象层次结构中的所有对象
/// </summary>
private async Task SliceObjectHierarchy(Transform root, Plane plane, bool ignoreObjectBoundsCheck, bool asynchronously, List<Task> sliceTasks, Transform slicerRoot = null)
{
if (root == null || root.gameObject == null) return;
GameObject rootObj = root.gameObject;
// 预收集子物体
Transform[] childrenCopy = new Transform[root.childCount];
for (int i = 0; i < root.childCount; i++)
{
childrenCopy[i] = root.GetChild(i);
}
// 切割当前物体
bool hasMeshFilter = rootObj.GetComponent<MeshFilter>() != null && rootObj.GetComponent<MeshFilter>().sharedMesh != null;
bool hasRenderer = rootObj.GetComponent<Renderer>() != null;
if (hasMeshFilter && hasRenderer)
{
try
{
await SliceGameObject(rootObj, plane, ignoreObjectBoundsCheck, asynchronously, sliceTasks, slicerRoot);
}
catch (Exception ex)
{
Debug.LogError($"切割物体 {rootObj.name} 时发生错误: {ex.Message}");
}
}
// 如果物体被销毁,停止处理子物体
if (rootObj == null || root == null) return;
// 递归处理子物体
foreach (Transform child in childrenCopy)
{
if (child == null || child.gameObject == null) continue;
try
{
await SliceObjectHierarchy(child, plane, ignoreObjectBoundsCheck, asynchronously, sliceTasks, slicerRoot);
}
catch (Exception ex)
{
Debug.LogError($"处理子物体 {child.name} 时发生错误: {ex.Message}");
}
}
}
/// <summary>
/// 对单个游戏对象执行切割
/// </summary>
private async Task SliceGameObject(GameObject gameObject, Plane plane, bool ignoreObjectBoundsCheck, bool asynchronously, List<Task> sliceTasks, Transform slicerRoot = null)
{
if (gameObject == null) return;
Vector3 originalScale = gameObject.transform.localScale;
Transform targetParent = _debugMode && slicerRoot != null ? slicerRoot : gameObject.transform.parent;
// 获取原始材质
Material originalMaterial = null;
Renderer renderer = gameObject.GetComponent<Renderer>();
if (renderer != null && renderer.sharedMaterials.Length > 0)
{
originalMaterial = renderer.sharedMaterials[0];
}
// 设置切割组件
BzSliceableObject sliceable = gameObject.GetComponent<BzSliceableObject>();
bool addedComponent = false;
if (sliceable == null)
{
sliceable = gameObject.AddComponent<BzSliceableObject>();
sliceable.defaultSliceMaterial = _defaultSliceMaterial ?? originalMaterial;
sliceable.asynchronously = false;
addedComponent = true;
}
try
{
// 检查是否需要切割
if (!ignoreObjectBoundsCheck)
{
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter != null && meshFilter.sharedMesh != null && !CheckObjectIntersectsPlane(gameObject, meshFilter, plane))
{
return;
}
}
// 执行切割
BzSliceTryResult result = await sliceable.SliceAsync(plane, null);
// 处理结果
ProcessSliceResult(gameObject, result, originalScale, targetParent, originalMaterial);
}
catch (Exception ex)
{
Debug.LogError($"切割物体 {gameObject.name} 时发生错误: {ex.Message}");
}
finally
{
// 清理组件
if (addedComponent && gameObject != null)
{
try { UnityEngine.Object.DestroyImmediate(sliceable); }
catch (Exception) { /* 忽略组件删除错误 */ }
}
}
}
/// <summary>
/// 检查物体是否与平面相交
/// </summary>
private bool CheckObjectIntersectsPlane(GameObject gameObject, MeshFilter meshFilter, Plane plane)
{
Mesh mesh = meshFilter.sharedMesh;
if (mesh == null) return false;
Matrix4x4 localToWorld = gameObject.transform.localToWorldMatrix;
Vector3[] vertices = mesh.vertices;
bool hasPositive = false;
bool hasNegative = false;
// 采样检查顶点
int step = Mathf.Max(1, vertices.Length / 100);
for (int i = 0; i < vertices.Length; i += step)
{
Vector3 worldVertex = localToWorld.MultiplyPoint3x4(vertices[i]);
float distance = plane.GetDistanceToPoint(worldVertex);
if (distance > 0.01f) hasPositive = true;
else if (distance < -0.01f) hasNegative = true;
if (hasPositive && hasNegative) return true;
}
// 如果完全在平面下方,移除物体
if (!hasPositive && hasNegative)
{
UnityEngine.Object.DestroyImmediate(gameObject);
return false;
}
// 如果完全在平面上方,无需切割
return false;
}
/// <summary>
/// 处理切割结果
/// </summary>
private void ProcessSliceResult(GameObject originalObject, BzSliceTryResult result, Vector3 originalScale, Transform targetParent, Material originalMaterial)
{
if (originalObject == null || result == null || !result.sliced || result.resultObjects == null || result.resultObjects.Length == 0)
{
return;
}
string originalName = originalObject.name;
Vector3 originalPosition = originalObject.transform.position;
Quaternion originalRotation = originalObject.transform.rotation;
// 处理切割结果
foreach (var resultObj in result.resultObjects)
{
if (resultObj == null || resultObj.gameObject == null) continue;
try
{
if (resultObj.side)
{
// 处理上半部分
GameObject upperPart = resultObj.gameObject;
upperPart.name = _debugMode ? originalName + "_Upper" : originalName;
upperPart.transform.localScale = originalScale;
// 处理网格和材质
RemoveSliceFaceSubmesh(upperPart);
EnsureSingleMaterial(upperPart, originalMaterial);
FixMeshUVs(upperPart);
// 设置位置
upperPart.transform.SetParent(null);
upperPart.transform.position = originalPosition;
upperPart.transform.rotation = originalRotation;
if (targetParent != null)
{
upperPart.transform.SetParent(targetParent);
}
// 移除临时组件
BzSliceableObject sliceable = upperPart.GetComponent<BzSliceableObject>();
if (sliceable != null)
{
UnityEngine.Object.DestroyImmediate(sliceable);
}
}
else
{
// 销毁下半部分
UnityEngine.Object.DestroyImmediate(resultObj.gameObject);
}
}
catch (Exception ex)
{
Debug.LogError($"处理切割结果时出错: {ex.Message}");
}
}
}
/// <summary>
/// 删除切割面生成的submesh并重新构建网格
/// </summary>
private void RemoveSliceFaceSubmesh(GameObject gameObject)
{
MeshFilter[] meshFilters = gameObject.GetComponentsInChildren<MeshFilter>(true);
foreach (var meshFilter in meshFilters)
{
if (meshFilter == null || meshFilter.sharedMesh == null) continue;
Mesh originalMesh = meshFilter.sharedMesh;
int submeshCount = originalMesh.subMeshCount;
// 只有多个submesh时处理
if (submeshCount <= 1) continue;
try
{
// 获取第一个submesh数据
int[] triangles = originalMesh.GetTriangles(0);
Vector3[] originalVertices = originalMesh.vertices;
Vector2[] originalUVs = originalMesh.uv;
Vector3[] originalNormals = originalMesh.normals;
Vector4[] originalTangents = originalMesh.tangents;
Color[] originalColors = originalMesh.colors;
// 收集使用的顶点
HashSet<int> usedVertexIndices = new HashSet<int>();
for (int i = 0; i < triangles.Length; i++)
{
usedVertexIndices.Add(triangles[i]);
}
// 建立索引映射
Dictionary<int, int> indexRemapping = new Dictionary<int, int>();
int newVertexIndex = 0;
foreach (int oldIndex in usedVertexIndices)
{
indexRemapping[oldIndex] = newVertexIndex++;
}
// 创建新的顶点数据
Vector3[] newVertices = new Vector3[usedVertexIndices.Count];
Vector2[] newUVs = new Vector2[usedVertexIndices.Count];
Vector3[] newNormals = originalNormals != null && originalNormals.Length > 0 ? new Vector3[usedVertexIndices.Count] : null;
Vector4[] newTangents = originalTangents != null && originalTangents.Length > 0 ? new Vector4[usedVertexIndices.Count] : null;
Color[] newColors = originalColors != null && originalColors.Length > 0 ? new Color[usedVertexIndices.Count] : null;
// 重映射数据
foreach (var kvp in indexRemapping)
{
int oldIndex = kvp.Key;
int newIndex = kvp.Value;
// 复制顶点
newVertices[newIndex] = originalVertices[oldIndex];
// 复制UV
if (originalUVs != null && originalUVs.Length > oldIndex)
{
newUVs[newIndex] = originalUVs[oldIndex];
}
else if (originalUVs != null && originalUVs.Length > 0)
{
newUVs[newIndex] = originalUVs[0];
}
else
{
newUVs[newIndex] = Vector2.zero;
}
// 复制法线
if (newNormals != null && originalNormals.Length > oldIndex)
{
newNormals[newIndex] = originalNormals[oldIndex];
}
// 复制切线
if (newTangents != null && originalTangents.Length > oldIndex)
{
newTangents[newIndex] = originalTangents[oldIndex];
}
// 复制颜色
if (newColors != null && originalColors.Length > oldIndex)
{
newColors[newIndex] = originalColors[oldIndex];
}
}
// 重映射三角形
int[] newTriangles = new int[triangles.Length];
for (int i = 0; i < triangles.Length; i++)
{
newTriangles[i] = indexRemapping[triangles[i]];
}
// 创建新网格
Mesh newMesh = new Mesh();
newMesh.name = originalMesh.name + "_CleanedMesh";
newMesh.vertices = newVertices;
newMesh.uv = newUVs;
if (newNormals != null) newMesh.normals = newNormals;
if (newTangents != null) newMesh.tangents = newTangents;
if (newColors != null) newMesh.colors = newColors;
newMesh.triangles = newTriangles;
// 更新网格属性
if (newNormals == null) newMesh.RecalculateNormals();
newMesh.RecalculateBounds();
// 应用新网格
meshFilter.sharedMesh = newMesh;
}
catch (Exception ex)
{
Debug.LogError($"重构网格时出错: {ex.Message}");
}
}
}
/// <summary>
/// 确保物体及其子物体只有单一材质
/// </summary>
private void EnsureSingleMaterial(GameObject gameObject, Material originalMaterial)
{
Renderer[] renderers = gameObject.GetComponentsInChildren<Renderer>(true);
foreach (var renderer in renderers)
{
if (renderer == null) continue;
Material[] materials = renderer.sharedMaterials;
if (materials == null || materials.Length == 0) continue;
// 选择要使用的材质
Material materialToUse = originalMaterial;
if (materialToUse == null && materials[0] != null)
{
materialToUse = materials[0];
}
if (materialToUse == null) continue;
// 设置单一材质
renderer.sharedMaterial = materialToUse;
// 强制处理多材质情况
if (renderer.sharedMaterials.Length > 1)
{
renderer.sharedMaterials = new Material[1] { materialToUse };
}
}
}
/// <summary>
/// 修复切割生成的网格UV坐标
/// </summary>
private void FixMeshUVs(GameObject gameObject)
{
MeshFilter[] meshFilters = gameObject.GetComponentsInChildren<MeshFilter>(true);
foreach (var meshFilter in meshFilters)
{
if (meshFilter == null || meshFilter.sharedMesh == null) continue;
Mesh mesh = meshFilter.sharedMesh;
Mesh uniqueMesh = UnityEngine.Object.Instantiate(mesh);
int vertexCount = uniqueMesh.vertices.Length;
Vector2[] uvs = new Vector2[vertexCount];
// 填充UV数据
if (uniqueMesh.uv != null && uniqueMesh.uv.Length > 0)
{
Vector2[] oldUVs = uniqueMesh.uv;
int copyLength = Mathf.Min(oldUVs.Length, vertexCount);
// 复制已有UV
for (int i = 0; i < copyLength; i++)
{
uvs[i] = oldUVs[i];
}
// 填充剩余UV
if (oldUVs.Length < vertexCount)
{
Vector2 lastUV = oldUVs[oldUVs.Length - 1];
for (int i = oldUVs.Length; i < vertexCount; i++)
{
uvs[i] = lastUV;
}
}
}
// 应用UV
uniqueMesh.uv = uvs;
// 更新其他属性
if (uniqueMesh.normals == null || uniqueMesh.normals.Length != vertexCount)
{
uniqueMesh.RecalculateNormals();
}
if (uniqueMesh.tangents == null || uniqueMesh.tangents.Length != vertexCount)
{
Vector4[] tangents = new Vector4[vertexCount];
for (int i = 0; i < vertexCount; i++)
{
tangents[i] = new Vector4(1, 0, 0, 1);
}
uniqueMesh.tangents = tangents;
}
// 应用修改后的网格
meshFilter.sharedMesh = uniqueMesh;
}
}
}