Meshsync 스크립트 추가
This commit is contained in:
parent
f9a33e5972
commit
f0824f152b
8
Assets/External/_StudioKafka.meta
vendored
Normal file
8
Assets/External/_StudioKafka.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ce652977f23eee4fa89058799522039
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/External/_StudioKafka/MeshSyncPro.meta
vendored
Normal file
8
Assets/External/_StudioKafka/MeshSyncPro.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8f1ac812e3f7804aae29071532c5b3b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/External/_StudioKafka/MeshSyncPro/Editor.meta
vendored
Normal file
8
Assets/External/_StudioKafka/MeshSyncPro/Editor.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 289042708b12df64f989e3ea0c83a041
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
537
Assets/External/_StudioKafka/MeshSyncPro/Editor/MeshSyncProWindow.cs
vendored
Normal file
537
Assets/External/_StudioKafka/MeshSyncPro/Editor/MeshSyncProWindow.cs
vendored
Normal file
@ -0,0 +1,537 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using StudioKafka.MeshSyncPro.Runtime;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace StudioKafka.MeshSyncPro
|
||||
{
|
||||
public class MeshSyncProWindow : EditorWindow
|
||||
{
|
||||
private List<GameObject> bodyObjects = new List<GameObject>();
|
||||
private List<GameObject> clothingObjects = new List<GameObject>();
|
||||
|
||||
private float penetrationThreshold = 0.001f;
|
||||
private float pushOutDistance = 0.05f;
|
||||
private float dotProductThreshold = 0.8f;
|
||||
private float influenceRadiusSteps = 1f;
|
||||
private int smoothingIterations = 2;
|
||||
private float smoothingFactor = 0.1f;
|
||||
|
||||
private Dictionary<(GameObject, GameObject), int[]> penetratingIndicesResults = new Dictionary<(GameObject, GameObject), int[]>();
|
||||
private Dictionary<(GameObject, GameObject), Vector3[]> penetratingVerticesResults = new Dictionary<(GameObject, GameObject), Vector3[]>();
|
||||
|
||||
private bool visualizePenetratingVertices = true;
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private List<MeshSyncProManager.Zone> zones = new List<MeshSyncProManager.Zone>();
|
||||
private GameObject debugSphere;
|
||||
|
||||
[MenuItem("Tools/MeshSyncPro")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<MeshSyncProWindow>("MeshSyncPro 도구");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SceneView.duringSceneGui += OnSceneGUIDelegate;
|
||||
Undo.undoRedoPerformed += OnUndoRedo;
|
||||
|
||||
if (zones == null || zones.Count == 0)
|
||||
{
|
||||
zones = new List<MeshSyncProManager.Zone>();
|
||||
zones.Add(new MeshSyncProManager.Zone()
|
||||
{
|
||||
center = new Vector3(0f, 0.872f, -0.12f),
|
||||
size = new Vector3(0.2f, 0.07f, 0.2f),
|
||||
active = true,
|
||||
color = new Color(0f, 1f, 0f, 0.25f)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SceneView.duringSceneGui -= OnSceneGUIDelegate;
|
||||
Undo.undoRedoPerformed -= OnUndoRedo;
|
||||
|
||||
if (debugSphere != null)
|
||||
DestroyImmediate(debugSphere);
|
||||
}
|
||||
|
||||
private void OnUndoRedo()
|
||||
{
|
||||
penetratingIndicesResults.Clear();
|
||||
penetratingVerticesResults.Clear();
|
||||
Repaint();
|
||||
SceneView.RepaintAll();
|
||||
Debug.Log("작업이 취소되었습니다. 관통 검출 결과는 초기화되었습니다.");
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
GUILayout.BeginVertical();
|
||||
try
|
||||
{
|
||||
EditorGUILayout.LabelField("MeshSyncPro: 메시 관통 검출·수정 도구", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"이 도구는 아바타 등의 기본 메시와 의상 메시 간의 관통을 검출하고 자동 수정을 시도합니다.\n" +
|
||||
"기본 사용법:\n" +
|
||||
"1. '기본 오브젝트'와 '의상 오브젝트'에 대상 GameObject를 지정합니다.\n" +
|
||||
"2. 각 설정값을 조정합니다. 툴팁에 조정 힌트가 있습니다.\n" +
|
||||
"3. '관통 부분 검출' 버튼으로 관통 부분을 특정합니다.\n" +
|
||||
"4. '검출 부분 수정' 버튼으로 자동 수정을 실행합니다.\n" +
|
||||
"모든 작업은 Undo (Ctrl+Z / Cmd+Z)로 되돌릴 수 있습니다.", MessageType.Info);
|
||||
|
||||
GUILayout.Label("대상 오브젝트 설정", EditorStyles.boldLabel);
|
||||
EditorGUILayout.LabelField("기본 오브젝트 (Skinned Mesh Renderer)", EditorStyles.miniBoldLabel);
|
||||
DrawGameObjectList(bodyObjects);
|
||||
|
||||
EditorGUILayout.LabelField("의상 오브젝트 (Skinned Mesh Renderer 또는 MeshFilter)", EditorStyles.miniBoldLabel);
|
||||
DrawGameObjectList(clothingObjects);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.Label("검출 대상 영역 설정", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox("관통 검출의 대상으로 하고 싶은 범위를 설정합니다.\n영역 설정은, 광범위한 메시에서 특정 부분(예: 가슴, 겨드랑이 등)의 관통만을 중점적으로 체크하고 싶은 경우에 유효합니다.", MessageType.None);
|
||||
|
||||
for (int i = 0; i < zones.Count; i++)
|
||||
{
|
||||
MeshSyncProManager.Zone zone = zones[i];
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
EditorGUILayout.LabelField($"영역 {i}", EditorStyles.boldLabel);
|
||||
|
||||
Undo.RecordObject(this, "영역 파라미터 변경");
|
||||
|
||||
zone.center = EditorGUILayout.Vector3Field(new GUIContent("중심 좌표", "영역의 중심이 되는 월드 좌표입니다. 대상 오브젝트의 로컬 좌표가 아닙니다."), zone.center);
|
||||
zone.size = EditorGUILayout.Vector3Field(new GUIContent("범위 크기", "영역의 XYZ 각 방향의 크기(지름)입니다."), zone.size);
|
||||
zone.active = EditorGUILayout.Toggle(new GUIContent("이 영역을 활성화", "체크를 해제하면 이 영역은 검출 대상에서 제외됩니다."), zone.active);
|
||||
zone.color = EditorGUILayout.ColorField(new GUIContent("영역 표시 색상", "씬 뷰에서 이 영역을 와이어프레임 표시할 때의 색상입니다. 반투명하게 하면 보기 쉽습니다."), zone.color);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("이 영역 삭제", GUILayout.Width(120)))
|
||||
{
|
||||
Undo.RecordObject(this, "영역 삭제");
|
||||
zones.RemoveAt(i--);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("이 영역 초기화", GUILayout.Width(120)))
|
||||
{
|
||||
Undo.RecordObject(this, "영역 초기화");
|
||||
if (i == 0 && zones.Count > 0)
|
||||
{
|
||||
zone.center = new Vector3(0f, 0.872f, -0.12f);
|
||||
zone.size = new Vector3(0.2f, 0.07f, 0.2f);
|
||||
zone.color = new Color(0f, 1f, 0f, 0.25f);
|
||||
} else {
|
||||
zone.center = Vector3.zero;
|
||||
zone.size = new Vector3(0.2f, 0.2f, 0.2f);
|
||||
zone.color = new Color(0.3f, 1f, 0.3f, 0.25f);
|
||||
}
|
||||
zone.active = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("새 영역 추가"))
|
||||
{
|
||||
Undo.RecordObject(this, "영역 추가");
|
||||
zones.Add(new MeshSyncProManager.Zone() { center = Vector3.zero, size = new Vector3(0.2f, 0.2f, 0.2f), active = true, color = new Color(0.3f, 0.3f, 1f, 0.25f) });
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.Label("관통 검출의 기본 설정", EditorStyles.boldLabel);
|
||||
|
||||
penetrationThreshold = EditorGUILayout.Slider(
|
||||
new GUIContent("관통으로 간주하는 거리 임계값", "의상이 기본 메시에 이 값 이상으로 파고들어 있는 경우에 관통으로 판정합니다.\n작은 값일수록 경미한 파고듦도 검출하지만, 과검출의 가능성도 증가합니다.(단위:미터)"),
|
||||
penetrationThreshold, 0.0001f, 0.1f);
|
||||
|
||||
dotProductThreshold = EditorGUILayout.Slider(
|
||||
new GUIContent("법선 방향의 일치도 임계값", "기본 메시의 법선과 관통 방향 벡터의 일치도. 값이 클수록, 기본 메시 표면에서 거의 수직으로 뚫고 나가는 관통만을 대상으로 합니다.\n-1에 가까울수록 모든 방향의 접촉을 검출하고, 1에 가까울수록 엄격한 뚫고 나감만을 검출합니다. 보통은 0.5 이상을 권장합니다."),
|
||||
dotProductThreshold, -1f, 1f);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUILayout.Label("수정의 기본 설정", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox("관통 수정하는 정점의 이동량이나 스무딩을 설정합니다", MessageType.None);
|
||||
|
||||
pushOutDistance = EditorGUILayout.Slider(
|
||||
new GUIContent("수정 시 밀어내는 양", "관통 수정 시, 기본 메시를 밀어내는 거리의 기준입니다.\n크게 하면 수정이 강해지지만, 형태가 왜곡될 수 있습니다.(단위:미터)"),
|
||||
pushOutDistance, 0.0f, 0.1f);
|
||||
|
||||
influenceRadiusSteps = EditorGUILayout.Slider(
|
||||
new GUIContent("수정 영향 스텝 수", "관통 수정 시, 직접 수정되는 정점의 주변 몇 스텝분의 정점까지 영향을 미치는지.\n값을 크게 하면 광범위가 부드럽게 수정되지만, 의도하지 않은 부분까지 변형될 가능성이 있습니다. 0으로 하면 직접 수정만 합니다."),
|
||||
influenceRadiusSteps, 0f, 10f);
|
||||
|
||||
smoothingIterations = EditorGUILayout.IntSlider(
|
||||
new GUIContent("스무딩 반복 횟수", "수정 후의 메시를 부드럽게 하는 처리의 반복 횟수입니다.\n횟수를 늘리면 더 부드러워지지만, 처리 시간이 증가하고 디테일이 손실될 수 있습니다."),
|
||||
smoothingIterations, 0, 10);
|
||||
|
||||
smoothingFactor = EditorGUILayout.Slider(
|
||||
new GUIContent("스무딩 강도", "수정 후의 메시를 부드럽게 할 때의 각 반복의 강도입니다.(0.0으로 효과 없음, 1.0으로 최대 효과)\n강도가 높을수록, 1회의 반복으로 크게 부드러워집니다."),
|
||||
smoothingFactor, 0f, 1f);
|
||||
|
||||
visualizePenetratingVertices = EditorGUILayout.Toggle(
|
||||
new GUIContent("관통 정점을 씬에 표시", "검출된 관통 정점을 빨간 구체로 씬 뷰에 표시합니다. 디버그나 설정 조정에 도움이 됩니다."),
|
||||
visualizePenetratingVertices);
|
||||
|
||||
GUILayout.Space(20);
|
||||
|
||||
if (GUILayout.Button("관통 부분 검출", GUILayout.Height(35)))
|
||||
{
|
||||
MeshSyncProManager manager = GetOrCreateManager();
|
||||
ConfigureManager(manager);
|
||||
Undo.RecordObject(manager, "관통 검출 (매니저 상태)");
|
||||
manager.ApplyMeshCorrections();
|
||||
|
||||
penetratingIndicesResults = manager.GetPenetratingIndicesResults();
|
||||
penetratingVerticesResults = manager.GetPenetratingVerticesResults();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
GUI.enabled = penetratingIndicesResults != null && penetratingIndicesResults.Count > 0 && penetratingIndicesResults.Any(kvp => kvp.Value != null && kvp.Value.Length > 0);
|
||||
|
||||
if (GUILayout.Button("검출 부분 수정", GUILayout.Height(35)))
|
||||
{
|
||||
MeshSyncProManager manager = GetOrCreateManager();
|
||||
ConfigureManager(manager);
|
||||
manager.FixDetectedPenetrations(pushOutDistance);
|
||||
|
||||
penetratingIndicesResults.Clear();
|
||||
penetratingVerticesResults.Clear();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// 수정된 메시가 있는지 확인
|
||||
bool hasModifiedMeshes = false;
|
||||
foreach (var clothingObj in clothingObjects)
|
||||
{
|
||||
if (clothingObj != null && clothingObj.GetComponent<SkinnedMeshRenderer>() != null)
|
||||
{
|
||||
hasModifiedMeshes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = hasModifiedMeshes;
|
||||
if (GUILayout.Button("수정된 메시 저장", GUILayout.Height(35)))
|
||||
{
|
||||
SaveModifiedMeshes();
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
GUILayout.Label("검출 결과 개요", EditorStyles.boldLabel);
|
||||
|
||||
if (penetratingIndicesResults != null && penetratingIndicesResults.Count > 0 && penetratingIndicesResults.Any(kvp => kvp.Value != null && kvp.Value.Length > 0))
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
int totalPenetratingVertices = 0;
|
||||
|
||||
foreach (var pair in penetratingIndicesResults)
|
||||
{
|
||||
var key = pair.Key;
|
||||
GameObject bodyObj = key.Item1;
|
||||
GameObject clothingObj = key.Item2;
|
||||
|
||||
if (bodyObj == null || clothingObj == null)
|
||||
{
|
||||
Debug.LogWarning($"[MeshSyncPro] 결과 표시 스킵: GameObject가 무효합니다. 기본: {(bodyObj == null ? "null" : bodyObj.name)}, 의상: {(clothingObj == null ? "null" : clothingObj.name)}");
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] penetratingIndices = pair.Value;
|
||||
if (penetratingIndices != null && penetratingIndices.Length > 0)
|
||||
{
|
||||
totalPenetratingVertices += penetratingIndices.Length;
|
||||
string resultText = $"기본「{(bodyObj?.name ?? "N/A")}」과 의상「{(clothingObj?.name ?? "N/A")}」사이: {penetratingIndices.Length} 정점의 관통을 검출";
|
||||
EditorGUILayout.LabelField(resultText);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalPenetratingVertices > 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"총 {totalPenetratingVertices} 부분의 관통 정점을 검출했습니다.", MessageType.Info);
|
||||
} else {
|
||||
EditorGUILayout.LabelField("선택된 오브젝트 사이에는, 현재 설정으로 관통으로 판정되는 부분은 없었습니다.");
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("아직 관통 검출은 실행되지 않았거나, 관통 부분은 발견되지 않았습니다.\n위의 버튼으로 검출을 실행해 주세요.");
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
catch (ExitGUIException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private MeshSyncProManager GetOrCreateManager()
|
||||
{
|
||||
MeshSyncProManager manager = FindObjectOfType<MeshSyncProManager>();
|
||||
if (manager == null)
|
||||
{
|
||||
GameObject managerGo = new GameObject("MeshSyncPro_ManagerComponent");
|
||||
Undo.RegisterCreatedObjectUndo(managerGo, "MeshSyncPro 매니저 생성");
|
||||
manager = managerGo.AddComponent<MeshSyncProManager>();
|
||||
Debug.Log("씬에 MeshSyncPro의 매니저 컴포넌트를 찾을 수 없어서 새로 생성했습니다.", managerGo);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
private void ConfigureManager(MeshSyncProManager manager)
|
||||
{
|
||||
Undo.RecordObject(manager, "MeshSyncPro 매니저 설정 변경");
|
||||
|
||||
manager.bodyObjects = new List<GameObject>(bodyObjects.Where(go => go != null));
|
||||
manager.clothingObjects = new List<GameObject>(clothingObjects.Where(go => go != null));
|
||||
manager.penetrationThreshold = penetrationThreshold;
|
||||
manager.dotProductThreshold = dotProductThreshold;
|
||||
manager.influenceRadiusSteps = influenceRadiusSteps;
|
||||
manager.smoothingIterations = smoothingIterations;
|
||||
manager.smoothingFactor = smoothingFactor;
|
||||
manager.DetectionZones = zones.FindAll(z => z.active).ToArray();
|
||||
}
|
||||
|
||||
void DrawGameObjectList(List<GameObject> list)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
GameObject newObj = (GameObject)EditorGUILayout.ObjectField(list[i], typeof(GameObject), true);
|
||||
if (newObj != list[i])
|
||||
{
|
||||
Undo.RecordObject(this, "대상 오브젝트 리스트 변경");
|
||||
list[i] = newObj;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("-", GUILayout.Width(20)))
|
||||
{
|
||||
Undo.RecordObject(this, "대상 오브젝트 리스트에서 삭제");
|
||||
list.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
Rect dropArea = EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Height(50));
|
||||
GUI.Box(dropArea, "대상의 GameObject를 여기에 드래그&드롭하여 추가");
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
Event currentEvent = Event.current;
|
||||
if (dropArea.Contains(currentEvent.mousePosition))
|
||||
{
|
||||
switch (currentEvent.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
|
||||
if (currentEvent.type == EventType.DragPerform)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
|
||||
foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences)
|
||||
{
|
||||
if (draggedObject is GameObject go)
|
||||
{
|
||||
if (!list.Contains(go))
|
||||
{
|
||||
Undo.RecordObject(this, "드래그 작업으로 대상 오브젝트를 리스트에 추가");
|
||||
list.Add(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DragAndDrop.activeControlID = 0;
|
||||
currentEvent.Use();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("+ 선택 중인 액티브 오브젝트 추가"))
|
||||
{
|
||||
if (Selection.activeGameObject != null)
|
||||
{
|
||||
if (!list.Contains(Selection.activeGameObject))
|
||||
{
|
||||
Undo.RecordObject(this, "선택 중 오브젝트를 리스트에 추가");
|
||||
list.Add(Selection.activeGameObject);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("대상이 되는 GameObject가 Hierarchy에서 선택되어 있지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void OnSceneGUIDelegate(SceneView sceneView)
|
||||
{
|
||||
foreach (MeshSyncProManager.Zone zone in zones)
|
||||
{
|
||||
if (zone.active)
|
||||
{
|
||||
Handles.color = zone.color;
|
||||
Handles.DrawWireCube(zone.center, zone.size);
|
||||
}
|
||||
}
|
||||
|
||||
if (visualizePenetratingVertices && penetratingVerticesResults != null)
|
||||
{
|
||||
Handles.color = Color.red;
|
||||
|
||||
foreach (var pair in penetratingVerticesResults)
|
||||
{
|
||||
GameObject bodyObj = pair.Key.Item1;
|
||||
GameObject clothingObj = pair.Key.Item2;
|
||||
|
||||
if (bodyObj == null || clothingObj == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3[] vertices = pair.Value;
|
||||
if (vertices != null && vertices.Length > 0)
|
||||
{
|
||||
foreach (Vector3 vertex in vertices)
|
||||
{
|
||||
float handleSize = HandleUtility.GetHandleSize(vertex) * 0.03f;
|
||||
Handles.SphereHandleCap(0, vertex, Quaternion.identity, handleSize, EventType.Repaint);
|
||||
}
|
||||
|
||||
if (vertices.Length > 0 && vertices[0] != null)
|
||||
{
|
||||
Vector3 labelPos = vertices[0] + Vector3.up * HandleUtility.GetHandleSize(vertices[0]) * 0.2f;
|
||||
Handles.Label(labelPos, $"관통: {vertices.Length}정점");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sceneView.Repaint();
|
||||
}
|
||||
|
||||
public void VisualizePenetrationFix(Vector3 beforeFix, Vector3 afterFix)
|
||||
{
|
||||
if (debugSphere != null)
|
||||
{
|
||||
DestroyImmediate(debugSphere);
|
||||
}
|
||||
|
||||
debugSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
debugSphere.name = "MeshSyncPro_FixVisualizationSphere";
|
||||
debugSphere.transform.localScale = Vector3.one * 0.02f;
|
||||
debugSphere.transform.position = beforeFix;
|
||||
|
||||
Debug.DrawLine(beforeFix, afterFix, Color.green, 5.0f);
|
||||
Handles.color = Color.green;
|
||||
Handles.DrawLine(beforeFix, afterFix);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
private void SaveModifiedMeshes()
|
||||
{
|
||||
bool anyMeshSaved = false;
|
||||
foreach (var clothingObj in clothingObjects)
|
||||
{
|
||||
if (clothingObj == null) continue;
|
||||
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = clothingObj.GetComponent<SkinnedMeshRenderer>();
|
||||
if (skinnedMeshRenderer != null && skinnedMeshRenderer.sharedMesh != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
string originalPath = AssetDatabase.GetAssetPath(skinnedMeshRenderer.sharedMesh);
|
||||
string directory;
|
||||
string fileName;
|
||||
|
||||
// 에셋 경로가 없는 경우 (씬에서 생성된 메시)
|
||||
if (string.IsNullOrEmpty(originalPath))
|
||||
{
|
||||
// 기본 저장 경로 설정
|
||||
directory = "Assets/Meshes";
|
||||
fileName = $"{clothingObj.name}_Mesh";
|
||||
|
||||
// 디렉토리가 없으면 생성
|
||||
if (!System.IO.Directory.Exists(directory))
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
directory = System.IO.Path.GetDirectoryName(originalPath);
|
||||
fileName = System.IO.Path.GetFileNameWithoutExtension(originalPath);
|
||||
}
|
||||
|
||||
string newPath = $"{directory}/{fileName}_Fixed.asset";
|
||||
|
||||
// 메시 복사 및 저장
|
||||
Mesh newMesh = Instantiate(skinnedMeshRenderer.sharedMesh);
|
||||
AssetDatabase.CreateAsset(newMesh, newPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
// 새로운 메시 적용
|
||||
skinnedMeshRenderer.sharedMesh = newMesh;
|
||||
EditorUtility.SetDirty(clothingObj);
|
||||
anyMeshSaved = true;
|
||||
|
||||
Debug.Log($"메시가 저장되었습니다: {newPath}");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"메시 '{clothingObj.name}' 저장 중 오류 발생: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anyMeshSaved)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.DisplayDialog("저장 완료", "수정된 메시가 성공적으로 저장되었습니다.", "확인");
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("저장 실패", "저장할 수정된 메시가 없습니다.", "확인");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/External/_StudioKafka/MeshSyncPro/Editor/MeshSyncProWindow.cs.meta
vendored
Normal file
11
Assets/External/_StudioKafka/MeshSyncPro/Editor/MeshSyncProWindow.cs.meta
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9ecc9e1fac43634780d004760cd4f08
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/External/_StudioKafka/MeshSyncPro/Runtime.meta
vendored
Normal file
8
Assets/External/_StudioKafka/MeshSyncPro/Runtime.meta
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3e043bb9c7ef3446ab04fdc40ddb53b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
326
Assets/External/_StudioKafka/MeshSyncPro/Runtime/MeshSyncProManager.cs
vendored
Normal file
326
Assets/External/_StudioKafka/MeshSyncPro/Runtime/MeshSyncProManager.cs
vendored
Normal file
@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
|
||||
namespace StudioKafka.MeshSyncPro
|
||||
{
|
||||
public class MeshSyncProManager : MonoBehaviour
|
||||
{
|
||||
#region Zone型の定義
|
||||
|
||||
[Serializable]
|
||||
public class Zone
|
||||
{
|
||||
public Vector3 center;
|
||||
public Vector3 size;
|
||||
public bool active;
|
||||
public Color color;
|
||||
|
||||
public Zone()
|
||||
{
|
||||
center = Vector3.zero;
|
||||
size = Vector3.one;
|
||||
active = true;
|
||||
color = Color.green;
|
||||
}
|
||||
|
||||
public Zone(Vector3 zoneCenter, Vector3 zoneSize, bool isActive = true)
|
||||
{
|
||||
center = zoneCenter;
|
||||
size = zoneSize;
|
||||
active = isActive;
|
||||
color = Color.green;
|
||||
}
|
||||
|
||||
public bool ContainsPoint(Vector3 point)
|
||||
{
|
||||
Vector3 min = center - size * 0.5f;
|
||||
Vector3 max = center + size * 0.5f;
|
||||
return point.x >= min.x && point.x <= max.x &&
|
||||
point.y >= min.y && point.y <= max.y &&
|
||||
point.z >= min.z && point.z <= max.z;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region パブリックプロパティ(UIから設定される)
|
||||
|
||||
[Header("対象オブジェクト")]
|
||||
public List<GameObject> bodyObjects = new List<GameObject>();
|
||||
public List<GameObject> clothingObjects = new List<GameObject>();
|
||||
|
||||
[Header("検出設定")]
|
||||
[Range(0.0001f, 0.1f)]
|
||||
public float penetrationThreshold = 0.001f;
|
||||
|
||||
[Range(-1f, 1f)]
|
||||
public float dotProductThreshold = 0.8f;
|
||||
|
||||
[Header("修正設定")]
|
||||
[Range(0f, 10f)]
|
||||
public float influenceRadiusSteps = 1f;
|
||||
|
||||
[Range(0, 10)]
|
||||
public int smoothingIterations = 2;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float smoothingFactor = 0.1f;
|
||||
|
||||
[Header("検出ゾーン")]
|
||||
public Zone[] DetectionZones = new Zone[0];
|
||||
|
||||
#endregion
|
||||
|
||||
#region プライベートフィールド
|
||||
|
||||
private Dictionary<(GameObject, GameObject), int[]> penetratingIndicesResults =
|
||||
new Dictionary<(GameObject, GameObject), int[]>();
|
||||
private Dictionary<(GameObject, GameObject), Vector3[]> penetratingVerticesResults =
|
||||
new Dictionary<(GameObject, GameObject), Vector3[]>();
|
||||
private bool isInitialized = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity ライフサイクル
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (DetectionZones == null || DetectionZones.Length == 0)
|
||||
{
|
||||
CreateDefaultZone();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 初期化
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (isInitialized) return;
|
||||
|
||||
penetratingIndicesResults = new Dictionary<(GameObject, GameObject), int[]>();
|
||||
penetratingVerticesResults = new Dictionary<(GameObject, GameObject), Vector3[]>();
|
||||
isInitialized = true;
|
||||
|
||||
Debug.Log("[MeshSyncProManager] マネージャーが初期化されました");
|
||||
}
|
||||
|
||||
private void CreateDefaultZone()
|
||||
{
|
||||
DetectionZones = new Zone[]
|
||||
{
|
||||
new Zone(new Vector3(0f, 0.872f, -0.12f), new Vector3(0.2f, 0.07f, 0.2f))
|
||||
{
|
||||
color = new Color(0f, 1f, 0f, 0.25f)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region メイン機能(UIから呼び出される)
|
||||
|
||||
public void ApplyMeshCorrections()
|
||||
{
|
||||
Debug.Log("[MeshSyncProManager] 貫通検出を開始します");
|
||||
ClearResults();
|
||||
|
||||
if (!ValidateInputs())
|
||||
{
|
||||
Debug.LogWarning("[MeshSyncProManager] 入力が無効です。検出を中止します");
|
||||
return;
|
||||
}
|
||||
|
||||
int totalDetections = 0;
|
||||
|
||||
foreach (GameObject bodyObj in bodyObjects.Where(obj => obj != null))
|
||||
{
|
||||
SkinnedMeshRenderer bodyRenderer = bodyObj.GetComponent<SkinnedMeshRenderer>();
|
||||
if (bodyRenderer == null) continue;
|
||||
|
||||
foreach (GameObject clothingObj in clothingObjects.Where(obj => obj != null))
|
||||
{
|
||||
Mesh clothingMesh = GetMeshFromGameObject(clothingObj);
|
||||
if (clothingMesh == null) continue;
|
||||
|
||||
var detectionResult = Runtime.PenetrationDetectionCore.DetectPenetration(
|
||||
bodyRenderer,
|
||||
clothingMesh,
|
||||
clothingObj.transform,
|
||||
penetrationThreshold,
|
||||
dotProductThreshold
|
||||
);
|
||||
|
||||
if (detectionResult.IsSuccessful && detectionResult.PenetratingIndices.Length > 0)
|
||||
{
|
||||
var filteredResult = FilterByZones(detectionResult);
|
||||
var key = (bodyObj, clothingObj);
|
||||
penetratingIndicesResults[key] = filteredResult.PenetratingIndices;
|
||||
penetratingVerticesResults[key] = filteredResult.PenetratingVertices;
|
||||
totalDetections += filteredResult.PenetratingIndices.Length;
|
||||
|
||||
Debug.Log($"[MeshSyncProManager] {bodyObj.name} と {clothingObj.name} 間で {filteredResult.PenetratingIndices.Length} 個の貫通を検出");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[MeshSyncProManager] 検出完了: 合計 {totalDetections} 個の貫通頂点を検出しました");
|
||||
}
|
||||
|
||||
public void FixDetectedPenetrations(float pushOutDistance)
|
||||
{
|
||||
Debug.Log($"[MeshSyncProManager] 貫通修正を開始します(押し出し距離: {pushOutDistance:F4})");
|
||||
int totalFixed = 0;
|
||||
|
||||
foreach (var result in penetratingIndicesResults)
|
||||
{
|
||||
var key = result.Key;
|
||||
GameObject bodyObj = key.Item1;
|
||||
int[] penetratingIndices = result.Value;
|
||||
|
||||
if (bodyObj != null && penetratingIndices != null && penetratingIndices.Length > 0)
|
||||
{
|
||||
SkinnedMeshRenderer bodyRenderer = bodyObj.GetComponent<SkinnedMeshRenderer>();
|
||||
if (bodyRenderer != null)
|
||||
{
|
||||
Runtime.PenetrationFixEngine.FixPenetrationAdvanced(
|
||||
bodyRenderer,
|
||||
penetratingIndices,
|
||||
pushOutDistance,
|
||||
influenceRadiusSteps,
|
||||
smoothingIterations,
|
||||
smoothingFactor
|
||||
);
|
||||
|
||||
totalFixed += penetratingIndices.Length;
|
||||
Debug.Log($"[MeshSyncProManager] {bodyObj.name} の {penetratingIndices.Length} 個の頂点を修正しました");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClearResults();
|
||||
Debug.Log($"[MeshSyncProManager] 修正完了: 合計 {totalFixed} 個の頂点を修正しました");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 結果取得メソッド(UIから呼び出される)
|
||||
|
||||
public Dictionary<(GameObject, GameObject), int[]> GetPenetratingIndicesResults()
|
||||
{
|
||||
return new Dictionary<(GameObject, GameObject), int[]>(penetratingIndicesResults);
|
||||
}
|
||||
|
||||
public Dictionary<(GameObject, GameObject), Vector3[]> GetPenetratingVerticesResults()
|
||||
{
|
||||
return new Dictionary<(GameObject, GameObject), Vector3[]>(penetratingVerticesResults);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ヘルパーメソッド
|
||||
|
||||
private bool ValidateInputs()
|
||||
{
|
||||
if (bodyObjects == null || bodyObjects.Count == 0)
|
||||
{
|
||||
Debug.LogError("[MeshSyncProManager] ボディオブジェクトが設定されていません");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clothingObjects == null || clothingObjects.Count == 0)
|
||||
{
|
||||
Debug.LogError("[MeshSyncProManager] 衣装オブジェクトが設定されていません");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Mesh GetMeshFromGameObject(GameObject obj)
|
||||
{
|
||||
SkinnedMeshRenderer smr = obj.GetComponent<SkinnedMeshRenderer>();
|
||||
if (smr != null && smr.sharedMesh != null)
|
||||
{
|
||||
return smr.sharedMesh;
|
||||
}
|
||||
|
||||
MeshFilter mf = obj.GetComponent<MeshFilter>();
|
||||
if (mf != null && mf.sharedMesh != null)
|
||||
{
|
||||
return mf.sharedMesh;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Runtime.PenetrationDetectionCore.DetectionResult FilterByZones(Runtime.PenetrationDetectionCore.DetectionResult originalResult)
|
||||
{
|
||||
var activeZones = DetectionZones?.Where(z => z != null && z.active).ToArray();
|
||||
if (activeZones == null || activeZones.Length == 0)
|
||||
{
|
||||
return originalResult;
|
||||
}
|
||||
|
||||
var filteredIndices = new List<int>();
|
||||
var filteredVertices = new List<Vector3>();
|
||||
|
||||
for (int i = 0; i < originalResult.PenetratingIndices.Length; i++)
|
||||
{
|
||||
Vector3 vertex = originalResult.PenetratingVertices[i];
|
||||
bool isInAnyZone = activeZones.Any(zone => zone.ContainsPoint(vertex));
|
||||
|
||||
if (isInAnyZone)
|
||||
{
|
||||
filteredIndices.Add(originalResult.PenetratingIndices[i]);
|
||||
filteredVertices.Add(originalResult.PenetratingVertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new Runtime.PenetrationDetectionCore.DetectionResult
|
||||
{
|
||||
IsSuccessful = filteredIndices.Count > 0,
|
||||
PenetratingIndices = filteredIndices.ToArray(),
|
||||
PenetratingVertices = filteredVertices.ToArray(),
|
||||
RawPenetratingVertices = originalResult.RawPenetratingVertices,
|
||||
ErrorMessage = originalResult.ErrorMessage,
|
||||
DebugInfo = originalResult.DebugInfo
|
||||
};
|
||||
}
|
||||
|
||||
private void ClearResults()
|
||||
{
|
||||
penetratingIndicesResults.Clear();
|
||||
penetratingVerticesResults.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region デバッグ
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (DetectionZones != null)
|
||||
{
|
||||
foreach (var zone in DetectionZones)
|
||||
{
|
||||
if (zone != null && zone.active)
|
||||
{
|
||||
Gizmos.color = zone.color;
|
||||
Gizmos.DrawWireCube(zone.center, zone.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/MeshSyncProManager.cs.meta
vendored
Normal file
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/MeshSyncProManager.cs.meta
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1386b3748e6dae0418f782afeaac23d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
406
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationDetectionCore.cs
vendored
Normal file
406
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationDetectionCore.cs
vendored
Normal file
@ -0,0 +1,406 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace StudioKafka.MeshSyncPro.Runtime
|
||||
{
|
||||
public static class PenetrationDetectionCore
|
||||
{
|
||||
private const float VectorMagnitudeThreshold = 0.0001f;
|
||||
private const float SpatialHashCellSizeMultiplier = 2.0f;
|
||||
private const float LooseDotProductThreshold = -0.5f;
|
||||
private const float CloseProximityFactor = 0.5f;
|
||||
|
||||
public struct DetectionResult
|
||||
{
|
||||
public bool IsSuccessful;
|
||||
public int[] PenetratingIndices;
|
||||
public Vector3[] PenetratingVertices;
|
||||
public Vector3[] RawPenetratingVertices;
|
||||
public string ErrorMessage;
|
||||
public ScaleDebugInfo DebugInfo;
|
||||
}
|
||||
|
||||
public struct ScaleDebugInfo
|
||||
{
|
||||
public Vector3 BodyScale;
|
||||
public Vector3 ClothingScale;
|
||||
public string ScaleAnalysisLog;
|
||||
}
|
||||
|
||||
private struct VertexData
|
||||
{
|
||||
public Vector3[] WorldVertices;
|
||||
public Vector3[] WorldNormals;
|
||||
public Vector3[] RawWorldVertices;
|
||||
}
|
||||
|
||||
public static DetectionResult DetectPenetration(
|
||||
SkinnedMeshRenderer bodyRenderer,
|
||||
Mesh clothingMesh,
|
||||
Transform clothingTransform,
|
||||
float penetrationThreshold,
|
||||
float dotThreshold)
|
||||
{
|
||||
Debug.Log($"[PenetrationDetectionCore] 検出開始 - Body: {bodyRenderer?.name}, Clothing: {clothingMesh?.name}");
|
||||
Debug.Log($"[PenetrationDetectionCore] 閾値 - 貫通: {penetrationThreshold:F5}, ドット積: {dotThreshold:F3}");
|
||||
|
||||
if (!ValidateInput(bodyRenderer, clothingMesh, clothingTransform))
|
||||
{
|
||||
return ErrorResult("入力パラメータが無効です。");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var scaleDebugInfo = AnalyzeScaleHierarchy(bodyRenderer, clothingTransform);
|
||||
LogScaleAnalysis(scaleDebugInfo);
|
||||
|
||||
var bodyVertexData = GetBodyVertexData(bodyRenderer);
|
||||
if (bodyVertexData.WorldVertices == null || bodyVertexData.WorldVertices.Length == 0)
|
||||
{
|
||||
return ErrorResult("Bodyメッシュの頂点データ取得に失敗、または頂点数が0です。");
|
||||
}
|
||||
|
||||
var clothingVertexData = GetClothingVertexData(clothingMesh, clothingTransform);
|
||||
if (clothingVertexData.WorldVertices == null || clothingVertexData.WorldVertices.Length == 0)
|
||||
{
|
||||
return ErrorResult("Clothingメッシュの頂点データ取得に失敗、または頂点数が0です。");
|
||||
}
|
||||
|
||||
Debug.Log($"[PenetrationDetectionCore] 実処理頂点数 - Body: {bodyVertexData.WorldVertices.Length}, Clothing: {clothingVertexData.WorldVertices.Length}");
|
||||
|
||||
var (penetratingIndices, penetratingVertices, rawVertices) = PerformPenetrationDetectionLogic(
|
||||
bodyVertexData, clothingVertexData, penetrationThreshold, dotThreshold);
|
||||
|
||||
Debug.Log($"[PenetrationDetectionCore] 検出完了: {penetratingIndices.Count}個の貫通頂点");
|
||||
|
||||
return new DetectionResult
|
||||
{
|
||||
IsSuccessful = true,
|
||||
PenetratingIndices = penetratingIndices.ToArray(),
|
||||
PenetratingVertices = penetratingVertices.ToArray(),
|
||||
RawPenetratingVertices = rawVertices.ToArray(),
|
||||
DebugInfo = scaleDebugInfo
|
||||
};
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return ErrorResult($"検出処理で予期せぬエラー: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static ScaleDebugInfo AnalyzeScaleHierarchy(SkinnedMeshRenderer bodyRenderer, Transform clothingTransform)
|
||||
{
|
||||
return new ScaleDebugInfo
|
||||
{
|
||||
BodyScale = bodyRenderer.transform.lossyScale,
|
||||
ClothingScale = clothingTransform.lossyScale,
|
||||
ScaleAnalysisLog = "簡素化されたスケール処理を使用中"
|
||||
};
|
||||
}
|
||||
|
||||
private static void LogScaleAnalysis(ScaleDebugInfo debugInfo)
|
||||
{
|
||||
Debug.Log($"[PenetrationDetectionCore] {debugInfo.ScaleAnalysisLog}");
|
||||
}
|
||||
|
||||
private static VertexData GetBodyVertexData(SkinnedMeshRenderer bodyRenderer)
|
||||
{
|
||||
Mesh bakedBodyMesh = null;
|
||||
try
|
||||
{
|
||||
bakedBodyMesh = new Mesh();
|
||||
bodyRenderer.BakeMesh(bakedBodyMesh);
|
||||
|
||||
if (bakedBodyMesh.vertexCount == 0)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Body BakeMesh vertex count is 0.");
|
||||
return new VertexData();
|
||||
}
|
||||
|
||||
Vector3[] localVertices = bakedBodyMesh.vertices;
|
||||
Vector3[] localNormals = bakedBodyMesh.normals;
|
||||
Matrix4x4 localToWorld = bodyRenderer.transform.localToWorldMatrix;
|
||||
|
||||
var (worldVerts, worldNorms) = TransformToWorldSpace(localVertices, localNormals, localToWorld);
|
||||
|
||||
return new VertexData
|
||||
{
|
||||
WorldVertices = worldVerts,
|
||||
WorldNormals = worldNorms,
|
||||
RawWorldVertices = worldVerts
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bakedBodyMesh != null) Object.DestroyImmediate(bakedBodyMesh);
|
||||
}
|
||||
}
|
||||
|
||||
private static VertexData GetClothingVertexData(Mesh clothingMesh, Transform clothingTransform)
|
||||
{
|
||||
Vector3[] localVertices = null;
|
||||
Vector3[] localNormals = null;
|
||||
|
||||
if (!clothingMesh.isReadable)
|
||||
{
|
||||
Debug.LogWarning($"[PenetrationDetectionCore] Clothing mesh '{clothingMesh.name}' はRead/Write Enabledではありません。");
|
||||
Debug.LogWarning("[PenetrationDetectionCore] 解決策: インポート設定でRead/Write Enabledにチェックを入れてください。");
|
||||
Debug.LogWarning("[PenetrationDetectionCore] また、SkinnedMeshRendererコンポーネントがある場合はBakeMeshを試行します。");
|
||||
|
||||
var clothingSkinnedRenderer = clothingTransform.GetComponent<SkinnedMeshRenderer>();
|
||||
if (clothingSkinnedRenderer != null)
|
||||
{
|
||||
Debug.Log("[PenetrationDetectionCore] SkinnedMeshRendererが見つかりました。BakeMeshを使用します。");
|
||||
Mesh bakedClothingMesh = null;
|
||||
try
|
||||
{
|
||||
bakedClothingMesh = new Mesh();
|
||||
clothingSkinnedRenderer.BakeMesh(bakedClothingMesh);
|
||||
|
||||
if (bakedClothingMesh.vertexCount > 0)
|
||||
{
|
||||
localVertices = bakedClothingMesh.vertices;
|
||||
localNormals = bakedClothingMesh.normals;
|
||||
Debug.Log($"[PenetrationDetectionCore] BakeMeshで取得: {localVertices.Length}個の頂点");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] BakeMeshの結果が空でした。");
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogError($"[PenetrationDetectionCore] BakeMesh失敗: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bakedClothingMesh != null) Object.DestroyImmediate(bakedClothingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (localVertices == null)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Clothingメッシュからデータを取得できませんでした。");
|
||||
return new VertexData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (clothingMesh.vertexCount == 0)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Clothing mesh vertex count is 0.");
|
||||
return new VertexData();
|
||||
}
|
||||
|
||||
localVertices = clothingMesh.vertices;
|
||||
localNormals = clothingMesh.normals;
|
||||
Debug.Log($"[PenetrationDetectionCore] 通常の方法で取得: {localVertices.Length}個の頂点");
|
||||
}
|
||||
|
||||
Matrix4x4 localToWorld = clothingTransform.localToWorldMatrix;
|
||||
var (worldVerts, worldNorms) = TransformToWorldSpace(localVertices, localNormals, localToWorld);
|
||||
|
||||
return new VertexData
|
||||
{
|
||||
WorldVertices = worldVerts,
|
||||
WorldNormals = worldNorms
|
||||
};
|
||||
}
|
||||
|
||||
private static (Vector3[] worldVertices, Vector3[] worldNormals) TransformToWorldSpace(
|
||||
Vector3[] localVertices, Vector3[] localNormals, Matrix4x4 localToWorldMatrix)
|
||||
{
|
||||
int vertexCount = localVertices.Length;
|
||||
var worldVertices = new Vector3[vertexCount];
|
||||
var worldNormals = new Vector3[vertexCount];
|
||||
bool hasValidNormals = localNormals != null && localNormals.Length == vertexCount;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
{
|
||||
worldVertices[i] = localToWorldMatrix.MultiplyPoint3x4(localVertices[i]);
|
||||
|
||||
if (hasValidNormals && localNormals[i].sqrMagnitude > VectorMagnitudeThreshold * VectorMagnitudeThreshold)
|
||||
{
|
||||
Vector3 worldNormal = localToWorldMatrix.MultiplyVector(localNormals[i]);
|
||||
worldNormals[i] = worldNormal.sqrMagnitude > VectorMagnitudeThreshold * VectorMagnitudeThreshold
|
||||
? worldNormal.normalized : Vector3.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
worldNormals[i] = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
return (worldVertices, worldNormals);
|
||||
}
|
||||
|
||||
private static (List<int> penetratingIndices, List<Vector3> penetratingVertices, List<Vector3> rawVertices)
|
||||
PerformPenetrationDetectionLogic(
|
||||
VertexData bodyData, VertexData clothingData,
|
||||
float penetrationThreshold, float dotThreshold)
|
||||
{
|
||||
var penetratingIndices = new List<int>();
|
||||
var penetratingVertices = new List<Vector3>();
|
||||
var rawVertices = new List<Vector3>();
|
||||
|
||||
float cellSize = Mathf.Max(penetrationThreshold * SpatialHashCellSizeMultiplier, VectorMagnitudeThreshold * 10f);
|
||||
var clothingSpatialHash = BuildSpatialHash(clothingData.WorldVertices, cellSize);
|
||||
|
||||
Debug.Log($"[PenetrationDetectionCore] 空間ハッシュセルサイズ: {cellSize:F6}, ハッシュエントリ数: {clothingSpatialHash.Count}");
|
||||
|
||||
int totalChecked = 0;
|
||||
int totalNearbyFound = 0;
|
||||
|
||||
for (int i = 0; i < bodyData.WorldVertices.Length; i++)
|
||||
{
|
||||
Vector3 currentBodyVertex = bodyData.WorldVertices[i];
|
||||
Vector3 currentBodyNormal = bodyData.WorldNormals[i];
|
||||
|
||||
if (currentBodyNormal.sqrMagnitude < VectorMagnitudeThreshold * VectorMagnitudeThreshold) continue;
|
||||
|
||||
totalChecked++;
|
||||
|
||||
var nearbyClothingIndices = GetNearbyVerticesFromSpatialHash(clothingSpatialHash, currentBodyVertex, cellSize);
|
||||
if (nearbyClothingIndices.Count > 0)
|
||||
{
|
||||
totalNearbyFound++;
|
||||
}
|
||||
|
||||
bool isPenetratingThisVertex = false;
|
||||
foreach (int clothingIndex in nearbyClothingIndices)
|
||||
{
|
||||
Vector3 clothingVertex = clothingData.WorldVertices[clothingIndex];
|
||||
Vector3 vectorToClothing = clothingVertex - currentBodyVertex;
|
||||
float distanceSqr = vectorToClothing.sqrMagnitude;
|
||||
|
||||
if (distanceSqr < penetrationThreshold * penetrationThreshold)
|
||||
{
|
||||
float distance = Mathf.Sqrt(distanceSqr);
|
||||
Vector3 penetrationDirection = distance > VectorMagnitudeThreshold ?
|
||||
vectorToClothing.normalized : currentBodyNormal;
|
||||
|
||||
float dotProduct = Vector3.Dot(currentBodyNormal, penetrationDirection);
|
||||
|
||||
if (dotProduct > dotThreshold ||
|
||||
(dotProduct > LooseDotProductThreshold && distance < penetrationThreshold * CloseProximityFactor))
|
||||
{
|
||||
isPenetratingThisVertex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isPenetratingThisVertex)
|
||||
{
|
||||
penetratingIndices.Add(i);
|
||||
penetratingVertices.Add(currentBodyVertex);
|
||||
if (bodyData.RawWorldVertices != null && i < bodyData.RawWorldVertices.Length)
|
||||
{
|
||||
rawVertices.Add(bodyData.RawWorldVertices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
rawVertices.Add(currentBodyVertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[PenetrationDetectionCore] 詳細統計 - チェック対象頂点: {totalChecked}, 近傍頂点が見つかった数: {totalNearbyFound}");
|
||||
|
||||
return (penetratingIndices, penetratingVertices, rawVertices);
|
||||
}
|
||||
|
||||
private static Dictionary<Vector3Int, List<int>> BuildSpatialHash(Vector3[] vertices, float cellSize)
|
||||
{
|
||||
var map = new Dictionary<Vector3Int, List<int>>();
|
||||
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var cell = new Vector3Int(
|
||||
Mathf.FloorToInt(vertices[i].x / cellSize),
|
||||
Mathf.FloorToInt(vertices[i].y / cellSize),
|
||||
Mathf.FloorToInt(vertices[i].z / cellSize)
|
||||
);
|
||||
|
||||
if (!map.TryGetValue(cell, out var list))
|
||||
{
|
||||
list = new List<int>();
|
||||
map[cell] = list;
|
||||
}
|
||||
|
||||
list.Add(i);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<int> GetNearbyVerticesFromSpatialHash(
|
||||
Dictionary<Vector3Int, List<int>> spatialHash, Vector3 position, float cellSize)
|
||||
{
|
||||
var nearby = new List<int>();
|
||||
var centerCell = new Vector3Int(
|
||||
Mathf.FloorToInt(position.x / cellSize),
|
||||
Mathf.FloorToInt(position.y / cellSize),
|
||||
Mathf.FloorToInt(position.z / cellSize)
|
||||
);
|
||||
|
||||
for (int xOff = -1; xOff <= 1; xOff++)
|
||||
{
|
||||
for (int yOff = -1; yOff <= 1; yOff++)
|
||||
{
|
||||
for (int zOff = -1; zOff <= 1; zOff++)
|
||||
{
|
||||
if (spatialHash.TryGetValue(centerCell + new Vector3Int(xOff, yOff, zOff), out var list))
|
||||
{
|
||||
nearby.AddRange(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nearby;
|
||||
}
|
||||
|
||||
private static bool ValidateInput(SkinnedMeshRenderer body, Mesh clothing, Transform clothingT)
|
||||
{
|
||||
if (body == null || body.sharedMesh == null || !body.sharedMesh.isReadable)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Body SMR or its mesh is invalid/unreadable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clothing == null)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Clothing mesh is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clothingT == null)
|
||||
{
|
||||
Debug.LogError("[PenetrationDetectionCore] Clothing transform is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!clothing.isReadable)
|
||||
{
|
||||
Debug.LogWarning($"[PenetrationDetectionCore] Clothing mesh '{clothing.name}' のRead/Write Enabledが無効です。代替手段を試行します。");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static DetectionResult ErrorResult(string message)
|
||||
{
|
||||
return new DetectionResult
|
||||
{
|
||||
IsSuccessful = false,
|
||||
ErrorMessage = message,
|
||||
PenetratingIndices = System.Array.Empty<int>(),
|
||||
PenetratingVertices = System.Array.Empty<Vector3>(),
|
||||
RawPenetratingVertices = System.Array.Empty<Vector3>()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationDetectionCore.cs.meta
vendored
Normal file
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationDetectionCore.cs.meta
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ce010e4de1c8e24aa5825a79dd41d54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
347
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationFixEngine.cs
vendored
Normal file
347
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationFixEngine.cs
vendored
Normal file
@ -0,0 +1,347 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationFixEngine.cs.meta
vendored
Normal file
11
Assets/External/_StudioKafka/MeshSyncPro/Runtime/PenetrationFixEngine.cs.meta
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c579fa45e59c3e746a8c2fc6079cb557
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
x
Reference in New Issue
Block a user