using UnityEngine; using System.Collections.Generic; using System.Linq; #if UNITY_EDITOR using UnityEditor; #endif namespace StudioKafka.MeshSyncPro.Runtime { public static class PenetrationFixEngine { private static Dictionary _originalMeshCache = new Dictionary(); private static Dictionary _rendererCache = new Dictionary(); #region レガシーAPI(Undo対応版) public static void FixPenetration( SkinnedMeshRenderer targetRenderer, int[] penetratingIndices, float pushDistance, int smoothingIterations) { if (targetRenderer == null || targetRenderer.sharedMesh == null || penetratingIndices == null || penetratingIndices.Length == 0) { Debug.LogWarning("[PenetrationFixEngine] Basic fix に対する入力が無効です。"); return; } Debug.Log($"[PenetrationFixEngine] Basic Fix 開始: {targetRenderer.name}, 対象頂点数: {penetratingIndices.Length}"); var meshInstanceId = RegisterMeshForUndo(targetRenderer, "Fix Mesh Penetration (Basic)"); Mesh mesh = targetRenderer.sharedMesh; Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; if (normals.Length != vertices.Length) { mesh.RecalculateNormals(); normals = mesh.normals; } foreach (int index in penetratingIndices) { if (index >= 0 && index < vertices.Length && index < normals.Length) { Vector3 normal = normals[index]; if (normal.sqrMagnitude > 0.000001f) { vertices[index] -= normal.normalized * pushDistance; } } } if (smoothingIterations > 0) { ApplySmoothing(ref vertices, mesh.triangles, penetratingIndices, smoothingIterations); } ApplyMeshChanges(mesh, vertices); Debug.Log($"[PenetrationFixEngine] Basic Fix 完了: {penetratingIndices.Length} 個の頂点を修正しました。"); } public static void FixPenetrationAdvanced( SkinnedMeshRenderer targetRenderer, int[] penetratingIndices, float pushDistance, float influenceRadiusSteps, int smoothingIterations, float smoothingFactor) { if (targetRenderer == null || targetRenderer.sharedMesh == null || penetratingIndices == null || penetratingIndices.Length == 0) { Debug.LogWarning("[PenetrationFixEngine] Advanced fix に対する入力が無効です。"); return; } Debug.Log($"[PenetrationFixEngine] Advanced Fix 開始: {targetRenderer.name}, 対象頂点数: {penetratingIndices.Length}"); var meshInstanceId = RegisterMeshForUndo(targetRenderer, "Fix Mesh Penetration (Advanced)"); Mesh mesh = targetRenderer.sharedMesh; Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; if (normals.Length != vertices.Length) { mesh.RecalculateNormals(); normals = mesh.normals; } var adjacencyMap = BuildVertexAdjacencyMap(mesh.triangles, vertices.Length); var influencedVertices = new Dictionary(); foreach (int penetratingIndex in penetratingIndices) { Queue<(int index, int step)> queue = new Queue<(int, int)>(); queue.Enqueue((penetratingIndex, 0)); HashSet visited = new HashSet { penetratingIndex }; influencedVertices[penetratingIndex] = 1.0f; while (queue.Count > 0) { var current = queue.Dequeue(); if (current.step >= (int)influenceRadiusSteps) continue; if (adjacencyMap.TryGetValue(current.index, out var neighbors)) { foreach (int neighborIndex in neighbors) { if (visited.Add(neighborIndex)) { float influence = (influenceRadiusSteps <= 0) ? 1.0f : (1.0f - (float)(current.step + 1) / (influenceRadiusSteps + 1.0f)); if (!influencedVertices.ContainsKey(neighborIndex) || influence > influencedVertices[neighborIndex]) { influencedVertices[neighborIndex] = influence; } queue.Enqueue((neighborIndex, current.step + 1)); } } } } } foreach (var pair in influencedVertices) { int index = pair.Key; float influence = pair.Value; if (index >= 0 && index < vertices.Length && index < normals.Length) { Vector3 normal = normals[index]; if (normal.sqrMagnitude > 0.000001f) { vertices[index] -= normal.normalized * (pushDistance * influence); } } } if (smoothingIterations > 0) { ApplyAdvancedSmoothing(ref vertices, adjacencyMap, influencedVertices.Keys.ToArray(), smoothingIterations, smoothingFactor); } ApplyMeshChanges(mesh, vertices); Debug.Log($"[PenetrationFixEngine] Advanced Fix 完了。影響を受けた頂点数: {influencedVertices.Count}"); } #endregion #region Undo対応メッシュ管理システム private static int RegisterMeshForUndo(SkinnedMeshRenderer renderer, string undoName) { var originalMesh = renderer.sharedMesh; var meshInstanceId = originalMesh.GetInstanceID(); if (_originalMeshCache.ContainsKey(meshInstanceId)) { return meshInstanceId; } #if UNITY_EDITOR var meshBackup = Object.Instantiate(originalMesh); meshBackup.name = originalMesh.name + "_UndoBackup"; _originalMeshCache[meshInstanceId] = meshBackup; _rendererCache[meshInstanceId] = renderer; var workingMesh = Object.Instantiate(originalMesh); workingMesh.name = originalMesh.name + "_Working"; Undo.RecordObject(renderer, undoName); renderer.sharedMesh = workingMesh; Undo.undoRedoPerformed -= OnUndoRedoPerformed; Undo.undoRedoPerformed += OnUndoRedoPerformed; #endif return meshInstanceId; } private static void OnUndoRedoPerformed() { #if UNITY_EDITOR var keysToRemove = new List(); foreach (var kvp in _rendererCache) { var meshInstanceId = kvp.Key; var renderer = kvp.Value; if (renderer == null) { keysToRemove.Add(meshInstanceId); continue; } if (_originalMeshCache.TryGetValue(meshInstanceId, out var originalMesh)) { if (renderer.sharedMesh != null && renderer.sharedMesh.name.Contains("_Working")) { Object.DestroyImmediate(renderer.sharedMesh); } renderer.sharedMesh = originalMesh; keysToRemove.Add(meshInstanceId); Debug.Log($"[PenetrationFixEngine] Undo実行: {renderer.name} のメッシュを元の状態に復元しました"); } } foreach (var key in keysToRemove) { if (_originalMeshCache.ContainsKey(key)) { _originalMeshCache.Remove(key); } _rendererCache.Remove(key); } SceneView.RepaintAll(); #endif } private static void ApplyMeshChanges(Mesh mesh, Vector3[] vertices) { mesh.vertices = vertices; mesh.RecalculateNormals(); mesh.RecalculateBounds(); #if UNITY_EDITOR EditorUtility.SetDirty(mesh); #endif } #endregion #region ヘルパーメソッド private static void ApplySmoothing(ref Vector3[] vertices, int[] triangles, int[] targetIndices, int iterations) { var adjacencyMap = BuildVertexAdjacencyMap(triangles, vertices.Length); var targetVertexSet = new HashSet(targetIndices); for (int iter = 0; iter < iterations; iter++) { Vector3[] tempVertices = (Vector3[])vertices.Clone(); foreach (int index in targetVertexSet) { if (adjacencyMap.TryGetValue(index, out var neighbors) && neighbors.Count > 0) { Vector3 averagePos = vertices[index]; foreach (int neighborIdx in neighbors) averagePos += vertices[neighborIdx]; tempVertices[index] = averagePos / (neighbors.Count + 1); } } vertices = tempVertices; } } private static void ApplyAdvancedSmoothing(ref Vector3[] vertices, Dictionary> adjacencyMap, int[] targetIndices, int iterations, float smoothingFactor) { var smoothingTargetIndices = new HashSet(targetIndices); for (int iter = 0; iter < iterations; iter++) { Vector3[] tempVertices = (Vector3[])vertices.Clone(); foreach (int index in smoothingTargetIndices) { if (adjacencyMap.TryGetValue(index, out var neighbors) && neighbors.Count > 0) { Vector3 averagePos = Vector3.zero; foreach (int neighborIdx in neighbors) averagePos += vertices[neighborIdx]; averagePos /= neighbors.Count; tempVertices[index] = Vector3.Lerp(vertices[index], averagePos, smoothingFactor); } } vertices = tempVertices; } } private static Dictionary> BuildVertexAdjacencyMap(int[] triangles, int vertexCount) { var map = new Dictionary>(); for (int i = 0; i < vertexCount; i++) map[i] = new List(); for (int i = 0; i < triangles.Length; i += 3) { int v0 = triangles[i], v1 = triangles[i + 1], v2 = triangles[i + 2]; if (v0 < vertexCount && v1 < vertexCount && v2 < vertexCount) { if (!map[v0].Contains(v1)) map[v0].Add(v1); if (!map[v0].Contains(v2)) map[v0].Add(v2); if (!map[v1].Contains(v0)) map[v1].Add(v0); if (!map[v1].Contains(v2)) map[v1].Add(v2); if (!map[v2].Contains(v0)) map[v2].Add(v0); if (!map[v2].Contains(v1)) map[v2].Add(v1); } } return map; } public static void ClearCache() { #if UNITY_EDITOR foreach (var kvp in _rendererCache) { var renderer = kvp.Value; if (renderer != null && renderer.sharedMesh != null && renderer.sharedMesh.name.Contains("_Working")) { Object.DestroyImmediate(renderer.sharedMesh); } } foreach (var backupMesh in _originalMeshCache.Values) { if (backupMesh != null) { Object.DestroyImmediate(backupMesh); } } _originalMeshCache.Clear(); _rendererCache.Clear(); Undo.undoRedoPerformed -= OnUndoRedoPerformed; #endif Debug.Log("[PenetrationFixEngine] キャッシュとUndoシステムをクリアしました"); } #endregion } }