707 lines
26 KiB
C#
707 lines
26 KiB
C#
|
|
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;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|