238 lines
7.8 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|