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;
///
/// 对模型进行切割处理
///
public async Task SliceModels(
List 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 originalToProcessedMap = new Dictionary();
Plane slicePlane = CreateSlicePlane(groundObj, planeDirection);
List sliceTasks = new List();
// 处理每个根节点
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);
}
///
/// 保存处理后的网格
///
private void SaveMeshes(Dictionary originalToProcessedMap)
{
int savedCount = 0;
foreach (var pair in originalToProcessedMap)
{
savedCount += SaveProcessedMeshes(pair.Value);
}
if (savedCount > 0)
{
Debug.Log($"已成功保存 {savedCount} 个切割后的网格到路径: Assets/{_meshSavePath}");
AssetDatabase.Refresh();
}
else
{
Debug.LogWarning($"没有网格被保存到路径: Assets/{_meshSavePath}");
}
}
///
/// 确保保存目录存在
///
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;
}
}
}
///
/// 保存处理后模型的所有网格
///
private int SaveProcessedMeshes(Transform processedRoot)
{
if (processedRoot == null) return 0;
int savedCount = 0;
MeshFilter[] meshFilters = processedRoot.GetComponentsInChildren(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;
}
///
/// 获取物体在层级中的路径
///
private string GetHierarchyPath(Transform transform, Transform stopAt)
{
if (transform == null) return "UnknownMesh";
List pathParts = new List();
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);
}
///
/// 净化文件名,删除不允许的字符
///
private string SanitizeFileName(string fileName)
{
char[] invalidChars = Path.GetInvalidFileNameChars();
foreach (char c in invalidChars)
{
fileName = fileName.Replace(c, '_');
}
return fileName;
}
///
/// 将网格保存为资源文件
///
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;
}
}
///
/// 创建切割平面
///
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;
}
///
/// 处理根对象,返回处理后的模型
///
private async Task ProcessRootObject(Transform root, Plane slicePlane, bool ignoreObjectBoundsCheck, bool asynchronously, List 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;
}
}
///
/// 替换原始模型
///
private void ReplaceOriginalModels(Dictionary 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}");
}
}
}
///
/// 递归切割对象层次结构中的所有对象
///
private async Task SliceObjectHierarchy(Transform root, Plane plane, bool ignoreObjectBoundsCheck, bool asynchronously, List 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() != null && rootObj.GetComponent().sharedMesh != null;
bool hasRenderer = rootObj.GetComponent() != 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}");
}
}
}
///
/// 对单个游戏对象执行切割
///
private async Task SliceGameObject(GameObject gameObject, Plane plane, bool ignoreObjectBoundsCheck, bool asynchronously, List 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();
if (renderer != null && renderer.sharedMaterials.Length > 0)
{
originalMaterial = renderer.sharedMaterials[0];
}
// 设置切割组件
BzSliceableObject sliceable = gameObject.GetComponent();
bool addedComponent = false;
if (sliceable == null)
{
sliceable = gameObject.AddComponent();
sliceable.defaultSliceMaterial = _defaultSliceMaterial ?? originalMaterial;
sliceable.asynchronously = false;
addedComponent = true;
}
try
{
// 检查是否需要切割
if (!ignoreObjectBoundsCheck)
{
MeshFilter meshFilter = gameObject.GetComponent();
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) { /* 忽略组件删除错误 */ }
}
}
}
///
/// 检查物体是否与平面相交
///
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;
}
///
/// 处理切割结果
///
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();
if (sliceable != null)
{
UnityEngine.Object.DestroyImmediate(sliceable);
}
}
else
{
// 销毁下半部分
UnityEngine.Object.DestroyImmediate(resultObj.gameObject);
}
}
catch (Exception ex)
{
Debug.LogError($"处理切割结果时出错: {ex.Message}");
}
}
}
///
/// 删除切割面生成的submesh,并重新构建网格
///
private void RemoveSliceFaceSubmesh(GameObject gameObject)
{
MeshFilter[] meshFilters = gameObject.GetComponentsInChildren(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 usedVertexIndices = new HashSet();
for (int i = 0; i < triangles.Length; i++)
{
usedVertexIndices.Add(triangles[i]);
}
// 建立索引映射
Dictionary indexRemapping = new Dictionary();
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}");
}
}
}
///
/// 确保物体及其子物体只有单一材质
///
private void EnsureSingleMaterial(GameObject gameObject, Material originalMaterial)
{
Renderer[] renderers = gameObject.GetComponentsInChildren(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 };
}
}
}
///
/// 修复切割生成的网格UV坐标
///
private void FixMeshUVs(GameObject gameObject)
{
MeshFilter[] meshFilters = gameObject.GetComponentsInChildren(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;
}
}
}