348 lines
12 KiB
C#
348 lines
12 KiB
C#
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<int, Mesh> _originalMeshCache = new Dictionary<int, Mesh>();
|
||
private static Dictionary<int, SkinnedMeshRenderer> _rendererCache = new Dictionary<int, SkinnedMeshRenderer>();
|
||
|
||
#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<int, float>();
|
||
|
||
foreach (int penetratingIndex in penetratingIndices)
|
||
{
|
||
Queue<(int index, int step)> queue = new Queue<(int, int)>();
|
||
queue.Enqueue((penetratingIndex, 0));
|
||
HashSet<int> visited = new HashSet<int> { 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<int>();
|
||
|
||
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<int>(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<int, List<int>> adjacencyMap,
|
||
int[] targetIndices, int iterations, float smoothingFactor)
|
||
{
|
||
var smoothingTargetIndices = new HashSet<int>(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<int, List<int>> BuildVertexAdjacencyMap(int[] triangles, int vertexCount)
|
||
{
|
||
var map = new Dictionary<int, List<int>>();
|
||
|
||
for (int i = 0; i < vertexCount; i++)
|
||
map[i] = new List<int>();
|
||
|
||
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
|
||
}
|
||
}
|