Files
HauntedBloodlines/Assets/Editor/SmartColliderGenerator.cs
2025-05-29 22:31:40 +03:00

238 lines
7.8 KiB
C#

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class SmartColliderGeneratorMerged : EditorWindow
{
private float manualVoxelSize = 0.0f;
private int mergeClusterSize = 2;
private bool overwriteExisting = true;
private bool showPreview = true;
private bool forceSimpleBox = false;
private static List<(Vector3 center, Vector3 size, Transform transform)> previewBoxes = new();
[MenuItem("Tools/Smart Collider Generator (Merged)")]
public static void ShowWindow()
{
var window = GetWindow<SmartColliderGeneratorMerged>("Smart Collider Generator");
SceneView.duringSceneGui -= DrawPreview;
SceneView.duringSceneGui += DrawPreview;
window.Show();
}
void OnGUI()
{
GUILayout.Label("Collider Optimization Tool", EditorStyles.boldLabel);
manualVoxelSize = EditorGUILayout.FloatField("Voxel Size (0 = auto)", manualVoxelSize);
mergeClusterSize = EditorGUILayout.IntSlider("Merge Cluster Size", mergeClusterSize, 1, 5);
overwriteExisting = EditorGUILayout.Toggle("Overwrite Existing Colliders", overwriteExisting);
forceSimpleBox = EditorGUILayout.Toggle("Force Simple BoxCollider", forceSimpleBox);
showPreview = EditorGUILayout.Toggle("Show Collider Preview", showPreview);
if (GUILayout.Button("Generate Colliders for Selection"))
{
GenerateCollidersForSelection();
}
if (GUILayout.Button("Refresh Preview"))
{
RefreshPreview();
}
}
void GenerateCollidersForSelection()
{
foreach (var obj in Selection.objects)
{
if (obj is GameObject go)
{
string path = AssetDatabase.GetAssetPath(go);
if (!string.IsNullOrEmpty(path) && PrefabUtility.GetPrefabAssetType(go) != PrefabAssetType.NotAPrefab)
{
ProcessPrefabAsset(go, path);
}
else
{
// Scene object
var filters = go.GetComponentsInChildren<MeshFilter>();
foreach (var filter in filters)
{
if (filter.sharedMesh == null) continue;
if (overwriteExisting)
{
foreach (var col in filter.GetComponents<Collider>())
{
if (col != null)
DestroyImmediate(col);
}
}
else if (filter.GetComponent<Collider>() != null)
{
continue;
}
ProcessMesh(filter, applyToGameObject: true);
}
}
}
}
previewBoxes.Clear();
Debug.Log(" Collider generation complete.");
}
void ProcessPrefabAsset(GameObject prefabAsset, string assetPath)
{
GameObject prefabInstance = PrefabUtility.LoadPrefabContents(assetPath);
var filters = prefabInstance.GetComponentsInChildren<MeshFilter>();
foreach (var filter in filters)
{
if (filter.sharedMesh == null) continue;
if (overwriteExisting)
{
foreach (var col in filter.GetComponents<Collider>())
{
if (col != null)
DestroyImmediate(col);
}
}
else if (filter.GetComponent<Collider>() != null)
{
continue;
}
ProcessMesh(filter, applyToGameObject: true);
}
PrefabUtility.SaveAsPrefabAsset(prefabInstance, assetPath);
PrefabUtility.UnloadPrefabContents(prefabInstance);
Debug.Log($" Updated prefab: {assetPath}");
}
void RefreshPreview()
{
previewBoxes.Clear();
foreach (GameObject go in Selection.gameObjects)
{
var filters = go.GetComponentsInChildren<MeshFilter>();
foreach (var filter in filters)
{
if (filter.sharedMesh == null) continue;
ProcessMesh(filter, applyToGameObject: false);
}
}
SceneView.RepaintAll();
}
void ProcessMesh(MeshFilter filter, bool applyToGameObject)
{
var mesh = filter.sharedMesh;
var bounds = mesh.bounds;
var size = bounds.size;
float ratio = Mathf.Max(size.x, size.y, size.z) / Mathf.Max(0.0001f, Mathf.Min(size.x, size.y, size.z));
if (forceSimpleBox || (mesh.vertexCount < 100 && ratio < 3f))
{
if (applyToGameObject)
{
var box = filter.gameObject.AddComponent<BoxCollider>();
box.center = bounds.center;
box.size = size;
}
else
{
previewBoxes.Add((bounds.center, size, filter.transform));
}
return;
}
float voxelSize = manualVoxelSize > 0 ? manualVoxelSize : Mathf.Max(size.x, size.y, size.z) / 10f;
RunVoxelMerge(filter, mesh, voxelSize, applyToGameObject);
}
void RunVoxelMerge(MeshFilter filter, Mesh mesh, float voxelSize, bool applyToGameObject)
{
Vector3[] vertices = mesh.vertices;
Transform t = filter.transform;
HashSet<Vector3Int> filledVoxels = new();
foreach (var v in vertices)
{
Vector3 world = t.TransformPoint(v);
Vector3Int voxel = Vector3Int.FloorToInt(world / voxelSize);
filledVoxels.Add(voxel);
}
HashSet<Vector3Int> visited = new();
foreach (var voxel in filledVoxels)
{
if (visited.Contains(voxel)) continue;
List<Vector3> groupPoints = new();
for (int x = 0; x < mergeClusterSize; x++)
{
for (int y = 0; y < mergeClusterSize; y++)
{
for (int z = 0; z < mergeClusterSize; z++)
{
Vector3Int offset = voxel + new Vector3Int(x, y, z);
if (filledVoxels.Contains(offset) && !visited.Contains(offset))
{
visited.Add(offset);
groupPoints.Add((Vector3)offset * voxelSize);
}
}
}
}
if (groupPoints.Count == 0) continue;
Bounds mergedBounds = new Bounds(groupPoints[0], Vector3.zero);
foreach (var p in groupPoints)
mergedBounds.Encapsulate(p);
if (applyToGameObject)
{
var box = filter.gameObject.AddComponent<BoxCollider>();
box.center = t.InverseTransformPoint(mergedBounds.center);
box.size = Vector3.one * voxelSize * mergeClusterSize;
}
else
{
previewBoxes.Add((mergedBounds.center, Vector3.one * voxelSize * mergeClusterSize, filter.transform));
}
}
}
static void DrawPreview(SceneView sceneView)
{
if (!EditorWindow.HasOpenInstances<SmartColliderGeneratorMerged>())
return;
var window = (SmartColliderGeneratorMerged)EditorWindow.GetWindow(typeof(SmartColliderGeneratorMerged));
if (!window.showPreview) return;
Handles.color = new Color(0f, 1f, 0f, 0.3f);
foreach (var (center, size, transform) in previewBoxes)
{
var matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale);
using (new Handles.DrawingScope(matrix))
{
Handles.DrawWireCube(transform.InverseTransformPoint(center), size);
}
}
}
}