2420 lines
86 KiB
C#
2420 lines
86 KiB
C#
#if (UNITY_EDITOR)
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Linq;
|
|
|
|
namespace ECE
|
|
{
|
|
[System.Serializable]
|
|
public class EasyColliderEditor : ScriptableObject, ISerializationCallbackReceiver
|
|
{
|
|
#region VHACD
|
|
// VHACD section
|
|
#if (!UNITY_EDITOR_LINUX)
|
|
/// <summary>
|
|
/// An instance of VHACD for use in each step when using RunVHACDStep
|
|
/// </summary>
|
|
private EasyColliderVHACD _VHACD;
|
|
|
|
/// <summary>
|
|
/// Array of meshes created from VHACDRunStep
|
|
/// </summary>
|
|
private Mesh[] _VHACDMeshes;
|
|
|
|
/// <summary>
|
|
/// Maximum number of vhacd recalculations
|
|
/// </summary>
|
|
private int _VHACDMaxCalculations = 3;
|
|
|
|
/// <summary>
|
|
/// Current vhacd calculation
|
|
/// </summary>
|
|
private int _VHACDCurrentCalculation = 0;
|
|
|
|
/// <summary>
|
|
/// List of colliders created by vhacd
|
|
/// </summary>
|
|
public List<MeshCollider> VHACDCreatedColliders = new List<MeshCollider>();
|
|
|
|
/// <summary>
|
|
/// Checks if the computation is finished and valid.
|
|
///
|
|
/// If force under 256 triangles is enabled, if the computation is finished but not valid,
|
|
/// will start a recomputation with a lower vertex limit.
|
|
/// Only recomputes a certain # of times before logging a warning and finishing.
|
|
///
|
|
/// If it's not enabled, just checks if the computation of convex hull data is complete
|
|
/// </summary>
|
|
/// <param name="parameters">Parameters of current vhacd calculation</param>
|
|
/// <returns>True if complete or complete and valid, false otherwise.</returns>
|
|
public bool VHACDCheckCompute(VHACDParameters parameters)
|
|
{
|
|
// check to see if we're forcing under 256 triangles and can still recalculate.
|
|
if (parameters.forceUnder256Triangles && _VHACDCurrentCalculation < _VHACDMaxCalculations)
|
|
{
|
|
// if the computation is finished
|
|
if (_VHACD.IsComputeFinished())
|
|
{
|
|
// and valid
|
|
if (_VHACD.IsValid())
|
|
{
|
|
// were done.
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// recompute the colliders (this changes the max number of vertices per hull so we get under 256)
|
|
_VHACD.RecomputeVHACD();
|
|
// increase the current calculation count.
|
|
_VHACDCurrentCalculation += 1;
|
|
return false;
|
|
}
|
|
}
|
|
// compute isn't finished.
|
|
else { return false; }
|
|
}
|
|
else
|
|
{
|
|
// if it's finished and we reached calculation max, tell user to reduce number of vertices per CH.
|
|
if (_VHACD.IsComputeFinished()
|
|
&& _VHACDCurrentCalculation == _VHACDMaxCalculations
|
|
&& _VHACD.IsValid() == false)
|
|
{
|
|
Debug.LogWarning("EasyColliderEditor: VHACD computation completed, but the final result had a number of triangles > 255. Try decreasing max vertices per hull, or increasing the number of hulls to prevent these errors.");
|
|
}
|
|
// return if the compute is finished.
|
|
return _VHACD.IsComputeFinished();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if VHACD is null
|
|
/// </summary>
|
|
/// <returns>false if null</returns>
|
|
public bool VHACDExists()
|
|
{
|
|
if (_VHACD == null) { return false; }
|
|
return true;
|
|
}
|
|
|
|
|
|
// /// <summary>
|
|
// /// Gets the current array of vhacd meshes.
|
|
// /// </summary>
|
|
// /// <returns></returns>
|
|
// public Mesh[] VHACDGetPreview()
|
|
// {
|
|
// if (_VHACDMeshes != null && _VHACDMeshes.Length > 0)
|
|
// {
|
|
// return _VHACDMeshes;
|
|
// }
|
|
// return null;
|
|
// }
|
|
|
|
private Dictionary<Transform, Mesh[]> _VHACDPreviewResult = new Dictionary<Transform, Mesh[]>();
|
|
public Dictionary<Transform, Mesh[]> VHACDGetPreview()
|
|
{
|
|
if (_VHACDPreviewResult != null && _VHACDPreviewResult.Count > 0)
|
|
{
|
|
return _VHACDPreviewResult;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void VHACDClearPreviewResult()
|
|
{
|
|
_VHACDPreviewResult = new Dictionary<Transform, Mesh[]>();
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Runs VHACD step by step
|
|
/// </summary>
|
|
/// <param name="step">Current step to run, 0-5</param>
|
|
/// <param name="parameters">Parameters to use</param>
|
|
/// <param name="savePath">Save path for meshes</param>
|
|
/// <param name="attachTo">Gameobject to attach mesh collider's to.</param>
|
|
/// <returns>true if step is valid and completes, false otherwise</returns>
|
|
public bool VHACDRunStep(int step, VHACDParameters parameters, bool saveAsAsset)
|
|
{
|
|
switch (step)
|
|
{
|
|
case 0: // setup
|
|
_VHACD = new EasyColliderVHACD();
|
|
_VHACDCurrentCalculation = 0;
|
|
_VHACD.Init(true);
|
|
VHACDCreatedColliders = new List<MeshCollider>();
|
|
if (parameters.SeparateChildMeshes && parameters.UseSelectedVertices)
|
|
{ // occasionally this can happen somehow even though it shouldn't, this should treat the issue but not the cause.
|
|
parameters.SeparateChildMeshes = false;
|
|
parameters.UseSelectedVertices = true;
|
|
}
|
|
return _VHACD.SetParameters(parameters);
|
|
case 1: // prepare mesh data.
|
|
// if we're not using just the selected vertices, ie we are using the whole mesh at once.
|
|
if (!parameters.UseSelectedVertices)
|
|
{
|
|
// for seperate child meshes, we attach each result to each mesh.
|
|
// this allows one to run it on multiple meshes all with the same parameters using a common (or temporary) parent object.
|
|
if (parameters.SeparateChildMeshes)
|
|
{
|
|
// each child mesh needs it's attach to object to be different.
|
|
parameters.AttachTo = parameters.MeshFilters[parameters.CurrentMeshFilter].gameObject;
|
|
// it's a temporary added component.
|
|
if (AddedInstanceIDs.Contains(parameters.AttachTo.GetInstanceID()))
|
|
{
|
|
if (parameters.AttachTo.transform.parent != null)
|
|
{
|
|
// update attach to so we dont lose created colliders, and adjust save name.
|
|
parameters.AttachTo = parameters.AttachTo.transform.parent.gameObject;
|
|
if (!parameters.IsCalculationForPreview)
|
|
{
|
|
parameters.SavePath = parameters.SavePath.Remove(parameters.SavePath.LastIndexOf("/") + 1) + parameters.AttachTo.name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if we're including child meshes, we need to prepare them differently,
|
|
// all the child meshes essentially get merged into 1 mesh and sent into VHACD.
|
|
if (IncludeChildMeshes && !parameters.SeparateChildMeshes)
|
|
{
|
|
// mesh filters need to be passed as the vertices need to be transformed to attach to's local space.
|
|
return _VHACD.PrepareMeshData(parameters.MeshFilters, parameters.AttachTo.transform);
|
|
}
|
|
else
|
|
{
|
|
// a single mesh, or each individual seperated child mesh gets prepared.
|
|
// attach to the attach to object.
|
|
bool prepared = _VHACD.PrepareMeshData(parameters.MeshFilters[parameters.CurrentMeshFilter], parameters.AttachTo.transform, parameters.MeshFilters[parameters.CurrentMeshFilter].sharedMesh);
|
|
if (!prepared)
|
|
{
|
|
Debug.LogWarning("EasyColliderEditor: Unable to run VHACD on: " + parameters.MeshFilters[parameters.CurrentMeshFilter].name + ". Likely due to missing a mesh in the mesh filter.", parameters.MeshFilters[parameters.CurrentMeshFilter].gameObject);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else // use selected verts.
|
|
{
|
|
// Use selected verts is enabled, so we need to create a mesh from the selected vertices.
|
|
Mesh m = CreateVHACDSelectedVerticesPreviewMesh(parameters);
|
|
// prepare the mesh
|
|
return _VHACD.PrepareMeshData(parameters.MeshFilters[parameters.CurrentMeshFilter], parameters.AttachTo.transform, m);
|
|
}
|
|
case 2: // calculate (run VHACD on the prepared-mesh)
|
|
if (_VHACDCurrentCalculation == 0)
|
|
{
|
|
_VHACD.RunVHACD();
|
|
_VHACDCurrentCalculation = 1;
|
|
return false;
|
|
}
|
|
else if (_VHACD.IsComputeFinished()) // check if compute is finished
|
|
{
|
|
// we recalculate if necessary, otherwise calculation is done.
|
|
return VHACDCheckCompute(parameters);
|
|
}
|
|
return false;
|
|
case 3: // save meshes as assets if needed / get the data for each mesh from VHACD and build a mesh.
|
|
if (!parameters.IsCalculationForPreview)
|
|
{
|
|
_VHACDMeshes = _VHACD.CreateConvexHullMeshes();
|
|
if (saveAsAsset)
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAssets(_VHACDMeshes, parameters.SavePath, parameters.SaveSuffix);
|
|
}
|
|
}
|
|
else if (parameters.IsCalculationForPreview)
|
|
{
|
|
// for the preview.
|
|
_VHACDMeshes = _VHACD.CreateConvexHullMeshes();
|
|
if (_VHACDPreviewResult.ContainsKey(parameters.AttachTo.transform))
|
|
{
|
|
_VHACDPreviewResult[parameters.AttachTo.transform] = _VHACDPreviewResult[parameters.AttachTo.transform].Concat(_VHACDMeshes).ToArray();
|
|
}
|
|
else
|
|
{
|
|
_VHACDPreviewResult.Add(parameters.AttachTo.transform, _VHACDMeshes);
|
|
}
|
|
}
|
|
return true;
|
|
case 4: // use the data from VHACD to generate convex mesh colliders.
|
|
if (parameters.IsCalculationForPreview) { return true; } // skip step 4 for previews.
|
|
if (parameters.vhacdResultMethod != VHACD_RESULT_METHOD.AttachTo)
|
|
{
|
|
// if the method isn't the default attach to, we create a parent to hold colliders
|
|
GameObject parent = new GameObject("VHACDColliders");
|
|
// since all verts were coverted to the attach to's location/position/rotation we use it's settings.
|
|
parent.transform.parent = parameters.AttachTo.transform;
|
|
parent.transform.position = parameters.AttachTo.transform.position;
|
|
parent.transform.rotation = parameters.AttachTo.transform.rotation;
|
|
Undo.RegisterCreatedObjectUndo(parent, "Create VHACD Collider Holder");
|
|
parameters.AttachTo = parent;
|
|
}
|
|
// keep the attach to object in case we need to create children.
|
|
GameObject attachTo = parameters.AttachTo;
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
for (int i = 0; i < _VHACDMeshes.Length; i++)
|
|
{
|
|
// for individual child objects.
|
|
if (parameters.vhacdResultMethod == VHACD_RESULT_METHOD.IndividualChildObjects)
|
|
{
|
|
// we create a child at the same position and rotation as the common parent (the attachto object)
|
|
GameObject child = new GameObject("VHACDCollider");
|
|
child.transform.parent = parameters.AttachTo.transform;
|
|
child.transform.position = parameters.AttachTo.transform.position;
|
|
child.transform.rotation = parameters.AttachTo.transform.rotation;
|
|
attachTo = child;
|
|
Undo.RegisterCreatedObjectUndo(child, "Create VHACD Collider");
|
|
}
|
|
// create a convex mesh collider.
|
|
MeshCollider mc = ecc.CreateConvexMeshCollider(_VHACDMeshes[i], attachTo, GetProperties());
|
|
CreatedColliders.Add(mc);
|
|
VHACDCreatedColliders.Add(mc);
|
|
AddedColliderIDs.Add(mc.GetInstanceID());
|
|
}
|
|
return true;
|
|
case 5: // clean up
|
|
if (parameters.IsCalculationForPreview) { return true; } // skip step 5 for previews.
|
|
if (parameters.UseSelectedVertices)
|
|
{
|
|
Undo.RecordObject(this, "Run VHACD");
|
|
ClearSelectedVertices();
|
|
}
|
|
// compute buffer gets all points set to origin when VHACD is run without use only selected vertices. So let's reupdate it.
|
|
if (Compute != null)
|
|
{
|
|
Compute.UpdateSelectedBuffer(GetWorldVertices());
|
|
}
|
|
_VHACD.Clean();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mesh from the current VHACD preview using the "use selected vertices" method
|
|
/// </summary>
|
|
/// <param name="parameters">Current VHACD parameters</param>
|
|
/// <returns>Mesh created from the current VHACD preview using full-triangles, and adding remaining vertices by closest distance</returns>
|
|
private Mesh CreateVHACDSelectedVerticesPreviewMesh(VHACDParameters parameters)
|
|
{
|
|
// list of created meshes, 1 for each mesh filtler.
|
|
List<Mesh> createdMeshList = new List<Mesh>();
|
|
// arrays to hold vertices of triangles of each mesh filter, and transform for each mesh filter.
|
|
Vector3[] vertices = new Vector3[0];
|
|
int[] triangles = new int[0];
|
|
Transform t = null;
|
|
// variables to hold easy collider vertices for each triangle of the mesh
|
|
EasyColliderVertex ecv0, ecv1, ecv2 = ecv1 = ecv0 = null;
|
|
foreach (MeshFilter mf in MeshFilters)
|
|
{
|
|
// hashset of used vertices for use after full triangle pass.
|
|
HashSet<EasyColliderVertex> usedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
// dictionary of vertex : vertex index to update.
|
|
Dictionary<EasyColliderVertex, int> ecvVertIndexDictionary = new Dictionary<EasyColliderVertex, int>();
|
|
// calculated mesh vertices and triangles to create a mesh with.
|
|
List<Vector3> verticesList = new List<Vector3>();
|
|
List<int> trianglesList = new List<int>();
|
|
// transform, verts, and tris of current mesh filter.
|
|
t = mf.transform;
|
|
vertices = mf.sharedMesh.vertices;
|
|
triangles = mf.sharedMesh.triangles;
|
|
// keep track of how many verts have been added just to make it a little easier.
|
|
int vertexCount = 0;
|
|
// go through triangles to see if the whole triangle is selected.
|
|
for (int i = 0; i < triangles.Length; i += 3)
|
|
{
|
|
// vertices of the triangle.
|
|
ecv0 = new EasyColliderVertex(t, vertices[triangles[i]]);
|
|
ecv1 = new EasyColliderVertex(t, vertices[triangles[i + 1]]);
|
|
ecv2 = new EasyColliderVertex(t, vertices[triangles[i + 2]]);
|
|
// if the full triangle is selected, add it.
|
|
if (SelectedVerticesSet.Contains(ecv0) && SelectedVerticesSet.Contains(ecv1) && SelectedVerticesSet.Contains(ecv2))
|
|
{
|
|
// add verts in world-space to convert later.
|
|
Vector3 v0 = t.TransformPoint(vertices[triangles[i]]);
|
|
Vector3 v1 = t.TransformPoint(vertices[triangles[i + 1]]);
|
|
Vector3 v2 = t.TransformPoint(vertices[triangles[i + 2]]);
|
|
// if it's been used before.
|
|
if (ecvVertIndexDictionary.ContainsKey(ecv0))
|
|
{
|
|
// it's in the dictionary, so add the value in the dict to the triangle list
|
|
trianglesList.Add(ecvVertIndexDictionary[ecv0]);
|
|
}
|
|
else
|
|
{
|
|
// we haven't used this vertex yet, so add it
|
|
verticesList.Add(v0);
|
|
vertexCount++;
|
|
trianglesList.Add(vertexCount - 1);
|
|
// remember to add the vertex to the dictionary with the appropriate index value.
|
|
ecvVertIndexDictionary.Add(ecv0, vertexCount - 1);
|
|
// and add them to the used vertices set
|
|
usedVerticesSet.Add(ecv0);
|
|
}
|
|
// ecv1 - repeat above.
|
|
if (ecvVertIndexDictionary.ContainsKey(ecv1))
|
|
{
|
|
trianglesList.Add(ecvVertIndexDictionary[ecv1]);
|
|
}
|
|
else
|
|
{
|
|
verticesList.Add(v1);
|
|
vertexCount++;
|
|
trianglesList.Add(vertexCount - 1);
|
|
ecvVertIndexDictionary.Add(ecv1, vertexCount - 1);
|
|
usedVerticesSet.Add(ecv1);
|
|
}
|
|
// ecv2 - repeat above
|
|
if (ecvVertIndexDictionary.ContainsKey(ecv2))
|
|
{
|
|
trianglesList.Add(ecvVertIndexDictionary[ecv2]);
|
|
}
|
|
else
|
|
{
|
|
verticesList.Add(v2);
|
|
vertexCount++;
|
|
trianglesList.Add(vertexCount - 1);
|
|
ecvVertIndexDictionary.Add(ecv2, vertexCount - 1);
|
|
usedVerticesSet.Add(ecv2);
|
|
}
|
|
}
|
|
}
|
|
// hashset of selected vertices.
|
|
HashSet<EasyColliderVertex> selectedVerticesSet = new HashSet<EasyColliderVertex>(SelectedVerticesSet);
|
|
// remove unused vertices that are full triangles.
|
|
selectedVerticesSet.ExceptWith(usedVerticesSet);
|
|
// list of reamining verts (vertices that are selected that arent' a part of at least 1 full triangle.)
|
|
List<Vector3> remainingVertsWorld = new List<Vector3>();
|
|
// transform remaining verts to world-space.
|
|
foreach (EasyColliderVertex ecv in selectedVerticesSet)
|
|
{
|
|
// make sure the transform is the same as the current mesh filter.
|
|
if (ecv.T == t)
|
|
{
|
|
remainingVertsWorld.Add(ecv.T.TransformPoint(ecv.LocalPosition));
|
|
}
|
|
}
|
|
// here we are checking to see if there was at least 1 full triangle to build from, if there wasn't then we make one.
|
|
// need at least 1 triangle to build from, but need at least 3 verts to do so.
|
|
if (trianglesList.Count == 0 && remainingVertsWorld.Count >= 3)
|
|
{
|
|
// find closest 3 in-order points (faster.. less accurate)
|
|
float totalMinDistance = Mathf.Infinity;
|
|
int[] vertIndexs = new int[3];
|
|
for (int i = 0; i < remainingVertsWorld.Count - 3; i++)
|
|
{
|
|
// d0 -> d1
|
|
float d01 = Vector3.Distance(remainingVertsWorld[i], remainingVertsWorld[i + 1]);
|
|
// d1 -> d2
|
|
float d12 = Vector3.Distance(remainingVertsWorld[i + 1], remainingVertsWorld[i + 2]);
|
|
// d2 -> d0
|
|
float d20 = Vector3.Distance(remainingVertsWorld[i + 2], remainingVertsWorld[i]);
|
|
// new "smallest" in-order triangle
|
|
if (d01 + d12 + d20 < totalMinDistance)
|
|
{
|
|
totalMinDistance = d01 + d12 + d20;
|
|
vertIndexs[0] = i;
|
|
vertIndexs[1] = i + 1;
|
|
vertIndexs[2] = i + 2;
|
|
}
|
|
}
|
|
// add the vertices
|
|
verticesList.Add(remainingVertsWorld[vertIndexs[0]]);
|
|
verticesList.Add(remainingVertsWorld[vertIndexs[1]]);
|
|
verticesList.Add(remainingVertsWorld[vertIndexs[2]]);
|
|
// add the triangle
|
|
trianglesList.Add(0);
|
|
trianglesList.Add(1);
|
|
trianglesList.Add(2);
|
|
}
|
|
// add the remaining left-over non-full triangle vertices.
|
|
float minDistance = Mathf.Infinity;
|
|
int vertIndex0, vertIndex1 = vertIndex0 = -1;
|
|
foreach (Vector3 pos in remainingVertsWorld)
|
|
{
|
|
// find "closest" triangle edge quickly.
|
|
minDistance = Mathf.Infinity;
|
|
for (int i = 0; i < trianglesList.Count; i += 3)
|
|
{
|
|
// calc distance from vertex to each triangle point.
|
|
float d0, d1, d2 = d1 = d0 = 0;
|
|
d0 = Vector3.Distance(pos, verticesList[trianglesList[i]]);
|
|
d1 = Vector3.Distance(pos, verticesList[trianglesList[i + 1]]);
|
|
d2 = Vector3.Distance(pos, verticesList[trianglesList[i + 2]]);
|
|
// find lowest distance from remaining vert to a triangle vertex
|
|
if (d0 < minDistance)
|
|
{
|
|
// set the min distance
|
|
minDistance = d0;
|
|
// update the i0 index
|
|
vertIndex0 = trianglesList[i];
|
|
// update i1 index based on which other vertex in the triangle is closer.
|
|
vertIndex1 = d1 < d2 ? trianglesList[i + 1] : trianglesList[i + 2];
|
|
}
|
|
// repeat for d1.
|
|
if (d1 < minDistance)
|
|
{
|
|
minDistance = d1;
|
|
vertIndex0 = trianglesList[i + 1];
|
|
vertIndex1 = d0 < d2 ? trianglesList[i] : trianglesList[i + 2];
|
|
}
|
|
// d2 not needed because d0 -> d1, d2 or d1 -> d0, d2 would give the same result if d2 was lowest.
|
|
}
|
|
// now we have the "closest" triangle..
|
|
// add the vertex
|
|
if (vertIndex0 >= 0 && vertIndex1 >= 0)
|
|
{
|
|
verticesList.Add(pos);
|
|
// add the the triangle.
|
|
trianglesList.Add(verticesList.Count - 1);
|
|
trianglesList.Add(vertIndex0);
|
|
trianglesList.Add(vertIndex1);
|
|
}
|
|
else
|
|
{
|
|
// we have a single lonely vertex that needs to be added.
|
|
// luckily, just adding it 3 times and setting is a triangle works for VHACD....
|
|
verticesList.Add(pos);
|
|
verticesList.Add(pos);
|
|
verticesList.Add(pos);
|
|
trianglesList.Add(verticesList.Count - 1);
|
|
trianglesList.Add(verticesList.Count - 2);
|
|
trianglesList.Add(verticesList.Count - 3);
|
|
}
|
|
|
|
}
|
|
// convert verts to attach to local space.
|
|
Transform attachTo = parameters.AttachTo.transform;
|
|
for (int i = 0; i < verticesList.Count; i++)
|
|
{
|
|
verticesList[i] = attachTo.InverseTransformPoint(verticesList[i]);
|
|
}
|
|
// create and return the mesh for use in vhacd.
|
|
Mesh m = new Mesh();
|
|
m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
|
m.vertices = verticesList.ToArray();
|
|
m.triangles = trianglesList.ToArray();
|
|
createdMeshList.Add(m);
|
|
}
|
|
Mesh result = new Mesh();
|
|
// use 32 bit index format for high # of verts.
|
|
result.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
|
List<CombineInstance> cis = new List<CombineInstance>();
|
|
// create combine instances for each created mesh.
|
|
for (int i = 0; i < createdMeshList.Count; i++)
|
|
{
|
|
if (createdMeshList[i] != null)
|
|
{
|
|
CombineInstance ci = new CombineInstance();
|
|
ci.mesh = createdMeshList[i];
|
|
cis.Add(ci);
|
|
}
|
|
}
|
|
//combine the mesh with the combine instances, NOT using the matrix.
|
|
result.CombineMeshes(cis.ToArray(), true, false);
|
|
return result;
|
|
}
|
|
|
|
#endif // end VHACD section
|
|
#endregion
|
|
[SerializeField]
|
|
private List<int> _AddedColliderIDs;
|
|
/// <summary>
|
|
/// List of added colliders through GetInstanceID()
|
|
/// </summary>
|
|
public List<int> AddedColliderIDs
|
|
{
|
|
get
|
|
{
|
|
if (_AddedColliderIDs == null)
|
|
{
|
|
_AddedColliderIDs = new List<int>();
|
|
}
|
|
return _AddedColliderIDs;
|
|
}
|
|
set { _AddedColliderIDs = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<int> _AddedInstanceIDs;
|
|
/// <summary>
|
|
/// List of all object's instance IDs that should be destroyed on cleanup. These are things like
|
|
/// MeshCollider used for vertex selection.
|
|
/// MeshFilter for skinned mesh renderers for mesh colliders.
|
|
/// Compute shader component.
|
|
/// Gizmo drawing component.
|
|
/// </summary>
|
|
public List<int> AddedInstanceIDs
|
|
{
|
|
get
|
|
{
|
|
if (_AddedInstanceIDs == null)
|
|
{
|
|
_AddedInstanceIDs = new List<int>();
|
|
}
|
|
return _AddedInstanceIDs;
|
|
}
|
|
set { _AddedInstanceIDs = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private GameObject _AttachToObject;
|
|
/// <summary>
|
|
/// If different from the selected gameobject, the attach to object is used to convert to local vertices / attach the collider to.
|
|
/// </summary>
|
|
public GameObject AttachToObject
|
|
{
|
|
get
|
|
{
|
|
if (_AttachToObject == null)
|
|
{
|
|
return SelectedGameObject;
|
|
}
|
|
return _AttachToObject;
|
|
}
|
|
set { _AttachToObject = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private bool _AutoIncludeChildSkinnedMeshes;
|
|
/// <summary>
|
|
/// Are we automatically including child skinned meshes when include child meshes is enabled?
|
|
/// </summary>
|
|
public bool AutoIncludeChildSkinnedMeshes { get { return _AutoIncludeChildSkinnedMeshes; } set { _AutoIncludeChildSkinnedMeshes = value; } }
|
|
|
|
[SerializeField]
|
|
private bool _ColliderSelectEnabled;
|
|
/// <summary>
|
|
/// Is Collider Selection Enabled? Toggles colliders on and off when changed.
|
|
/// </summary>
|
|
public bool ColliderSelectEnabled
|
|
{
|
|
get { return _ColliderSelectEnabled; }
|
|
set
|
|
{
|
|
ToggleCollidersEnabled(value);
|
|
_ColliderSelectEnabled = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private EasyColliderCompute _Compute;
|
|
/// <summary>
|
|
/// Compute shader script
|
|
/// </summary>
|
|
public EasyColliderCompute Compute
|
|
{
|
|
get
|
|
{
|
|
if (_Compute == null && SelectedGameObject != null)
|
|
{
|
|
_Compute = SelectedGameObject.GetComponent<EasyColliderCompute>();
|
|
}
|
|
return _Compute;
|
|
}
|
|
set
|
|
{
|
|
_Compute = value;
|
|
if (value != null && DisplayMeshVertices)
|
|
{
|
|
_Compute.SetDisplayAllBuffer(GetAllWorldMeshVertices());
|
|
_Compute.DisplayAllVertices = DisplayMeshVertices;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<Collider> _CreatedColliders;
|
|
/// <summary>
|
|
/// List of colliders we created
|
|
/// </summary>
|
|
public List<Collider> CreatedColliders
|
|
{
|
|
get
|
|
{
|
|
if (_CreatedColliders == null)
|
|
{
|
|
_CreatedColliders = new List<Collider>();
|
|
}
|
|
return _CreatedColliders;
|
|
}
|
|
set { _CreatedColliders = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private bool _CreatedCollidersDisabled;
|
|
/// <summary>
|
|
/// Are the colliders we create immediately marked as disabled until we're done with the current gameobject?
|
|
/// </summary>
|
|
public bool CreatedColliderDisabled { get { return _CreatedCollidersDisabled; } set { _CreatedCollidersDisabled = value; } }
|
|
|
|
[SerializeField]
|
|
/// <summary>
|
|
/// Volume per vertex density. Used in displaying vertices so less verts in more space displays larger and vice versa.
|
|
/// </summary>
|
|
public float DensityScale = 1.0f;
|
|
|
|
[SerializeField]
|
|
private List<Collider> _DisabledColliders;
|
|
/// <summary>
|
|
/// Colliders that we disabled during setup.
|
|
/// </summary>
|
|
public List<Collider> DisabledColliders
|
|
{
|
|
get
|
|
{
|
|
if (_DisabledColliders == null)
|
|
{
|
|
_DisabledColliders = new List<Collider>();
|
|
}
|
|
return _DisabledColliders;
|
|
}
|
|
set { _DisabledColliders = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private bool _DisplayMeshVertices;
|
|
public bool DisplayMeshVertices
|
|
{
|
|
get { return _DisplayMeshVertices; }
|
|
set
|
|
{
|
|
_DisplayMeshVertices = value;
|
|
if (Gizmos != null)
|
|
{
|
|
Undo.RegisterCompleteObjectUndo(Gizmos, "Change Display Mesh Gizmos");
|
|
int group = Undo.GetCurrentGroup();
|
|
Gizmos.DisplayAllVertices = value;
|
|
Gizmos.DisplayVertexPositions = value ? GetAllWorldMeshVertices() : new HashSet<Vector3>();
|
|
Undo.CollapseUndoOperations(group);
|
|
}
|
|
if (Compute != null)
|
|
{
|
|
Undo.RegisterCompleteObjectUndo(Compute, "Change Display Mesh Compute");
|
|
int group = Undo.GetCurrentGroup();
|
|
Compute.DisplayAllVertices = value;
|
|
Compute.SetDisplayAllBuffer(value ? GetAllWorldMeshVertices() : new HashSet<Vector3>());
|
|
Undo.CollapseUndoOperations(group);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
[SerializeField]
|
|
/// <summary>
|
|
/// Does the selected gameboject have a skinned mesh renderer as a child somewhere?
|
|
/// </summary>
|
|
public bool HasSkinnedMeshRenderer = false;
|
|
|
|
|
|
[SerializeField]
|
|
private EasyColliderGizmos _Gizmos;
|
|
/// <summary>
|
|
/// Gizmos component for drawing vertices and selections.
|
|
/// </summary>
|
|
public EasyColliderGizmos Gizmos
|
|
{
|
|
get
|
|
{
|
|
if (_Gizmos == null && _SelectedGameObject != null)
|
|
{
|
|
_Gizmos = SelectedGameObject.GetComponent<EasyColliderGizmos>();
|
|
}
|
|
return _Gizmos;
|
|
}
|
|
set
|
|
{
|
|
_Gizmos = value;
|
|
if (value != null && DisplayMeshVertices)
|
|
{
|
|
_Gizmos.DisplayVertexPositions = GetAllWorldMeshVertices();
|
|
_Gizmos.DisplayAllVertices = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private bool _IncludeChildMeshes;
|
|
/// <summary>
|
|
/// Are we including child meshes for vertex selection?
|
|
/// </summary>
|
|
public bool IncludeChildMeshes
|
|
{
|
|
get { return _IncludeChildMeshes; }
|
|
set
|
|
{
|
|
_IncludeChildMeshes = value;
|
|
SetupChildObjects(value);
|
|
CalculateDensity();
|
|
if (value == false)
|
|
{
|
|
CleanChildSelectedVertices();
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private bool _IsTrigger;
|
|
/// <summary>
|
|
/// Created colliders marked as trigger?
|
|
/// </summary>
|
|
/// <value></value>
|
|
public bool IsTrigger { get { return _IsTrigger; } set { _IsTrigger = value; } }
|
|
|
|
[SerializeField]
|
|
private List<EasyColliderVertex> _LastSelectedVertices;
|
|
/// <summary>
|
|
/// List of the vertices that were selected last
|
|
/// </summary>
|
|
public List<EasyColliderVertex> LastSelectedVertices
|
|
{
|
|
get
|
|
{
|
|
if (_LastSelectedVertices == null)
|
|
{
|
|
_LastSelectedVertices = new List<EasyColliderVertex>();
|
|
}
|
|
return _LastSelectedVertices;
|
|
}
|
|
set { _LastSelectedVertices = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<MeshFilter> _MeshFilters;
|
|
/// <summary>
|
|
/// List of mesh filters on SelectedGameobject + children (if IncludeChildMeshes)
|
|
/// </summary>
|
|
public List<MeshFilter> MeshFilters
|
|
{
|
|
get
|
|
{
|
|
if (_MeshFilters == null)
|
|
{
|
|
_MeshFilters = new List<MeshFilter>();
|
|
}
|
|
return _MeshFilters;
|
|
}
|
|
set { _MeshFilters = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bool to track entering/exiting play mode as this resets values from preferences
|
|
/// </summary>
|
|
public bool NeedsPreferencesUpdate = true;
|
|
|
|
[SerializeField]
|
|
private List<Rigidbody> _NonKinematicRigidbodies;
|
|
/// <summary>
|
|
/// Rigidbodies already on the objects that were marked as kinematic during setup.
|
|
/// </summary>
|
|
public List<Rigidbody> NonKinematicRigidbodies
|
|
{
|
|
get
|
|
{
|
|
if (_NonKinematicRigidbodies == null)
|
|
{
|
|
_NonKinematicRigidbodies = new List<Rigidbody>();
|
|
}
|
|
return _NonKinematicRigidbodies;
|
|
}
|
|
set
|
|
{
|
|
_NonKinematicRigidbodies = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private PhysicMaterial _PhysicMaterial;
|
|
/// <summary>
|
|
/// Physic material to add to colliders upon creation.
|
|
/// </summary>
|
|
public PhysicMaterial PhysicMaterial { get { return _PhysicMaterial; } set { _PhysicMaterial = value; } }
|
|
|
|
[SerializeField]
|
|
private List<Collider> _PreDisabledColliders;
|
|
/// <summary>
|
|
/// Colliders that were already disabled before setup.
|
|
/// </summary>
|
|
public List<Collider> PreDisabledColliders
|
|
{
|
|
get
|
|
{
|
|
if (_PreDisabledColliders == null)
|
|
{
|
|
_PreDisabledColliders = new List<Collider>();
|
|
}
|
|
return _PreDisabledColliders;
|
|
}
|
|
set { _PreDisabledColliders = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<Collider> _RaycastableColliders;
|
|
public List<Collider> RaycastableColliders
|
|
{
|
|
get
|
|
{
|
|
if (_RaycastableColliders == null)
|
|
{
|
|
_RaycastableColliders = new List<Collider>();
|
|
}
|
|
return _RaycastableColliders;
|
|
}
|
|
set { _RaycastableColliders = value; }
|
|
}
|
|
|
|
[SerializeField] private RENDER_POINT_TYPE _RenderPointType;
|
|
/// <summary>
|
|
/// Method we use to render points with. Either using a shader or gizmos.
|
|
/// </summary>
|
|
public RENDER_POINT_TYPE RenderPointType
|
|
{
|
|
get { return _RenderPointType; }
|
|
set
|
|
{
|
|
// add or remove one if the value is changing and it's already added
|
|
if (value != RENDER_POINT_TYPE.SHADER && Compute != null)
|
|
{
|
|
Undo.DestroyObjectImmediate(Compute);
|
|
}
|
|
if (value != RENDER_POINT_TYPE.GIZMOS && Gizmos != null)
|
|
{
|
|
Undo.DestroyObjectImmediate(Gizmos);
|
|
}
|
|
// add the new component.
|
|
if (value == RENDER_POINT_TYPE.GIZMOS && Gizmos == null && SelectedGameObject != null)
|
|
{
|
|
Gizmos = Undo.AddComponent<EasyColliderGizmos>(SelectedGameObject);
|
|
AddedInstanceIDs.Add(Gizmos.GetInstanceID());
|
|
}
|
|
if (value == RENDER_POINT_TYPE.SHADER && Compute == null && SelectedGameObject != null)
|
|
{
|
|
Compute = Undo.AddComponent<EasyColliderCompute>(SelectedGameObject);
|
|
AddedInstanceIDs.Add(Compute.GetInstanceID());
|
|
}
|
|
_RenderPointType = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private int _RotatedColliderLayer;
|
|
/// <summary>
|
|
/// Layer to set on rotated collider's gameobject if not using selected gameobject's layer.
|
|
/// </summary>
|
|
public int RotatedColliderLayer { get { return _RotatedColliderLayer; } set { _RotatedColliderLayer = value; } }
|
|
|
|
[SerializeField]
|
|
private bool _RotatedOnSelectedLayer;
|
|
/// <summary>
|
|
/// Should rotated collider's gameobject be on same layer as the selected gameobject?
|
|
/// </summary>
|
|
/// <value></value>
|
|
public bool RotatedOnSelectedLayer { get { return _RotatedOnSelectedLayer; } set { _RotatedOnSelectedLayer = value; } }
|
|
|
|
[SerializeField]
|
|
private List<Collider> _SelectedColliders;
|
|
/// <summary>
|
|
/// List of currently selected colliders
|
|
/// </summary>
|
|
public List<Collider> SelectedColliders
|
|
{
|
|
get
|
|
{
|
|
if (_SelectedColliders == null)
|
|
{
|
|
_SelectedColliders = new List<Collider>();
|
|
}
|
|
return _SelectedColliders;
|
|
}
|
|
set { _SelectedColliders = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private GameObject _SelectedGameObject;
|
|
/// <summary>
|
|
/// The currently selected gameobject. Changing this causes a cleanup of the previous selected object, and initialization of the object you are setting.
|
|
/// </summary>
|
|
public GameObject SelectedGameObject
|
|
{
|
|
get { return _SelectedGameObject; }
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
CleanUpObject(_SelectedGameObject, false);
|
|
_SelectedGameObject = value;
|
|
AttachToObject = value;
|
|
}
|
|
else if (!EditorUtility.IsPersistent(value))
|
|
{
|
|
// new selected object.
|
|
if (value != _SelectedGameObject)
|
|
{
|
|
// Had a selected object, clean it up.
|
|
if (_SelectedGameObject != null)
|
|
{
|
|
CleanUpObject(_SelectedGameObject, false);
|
|
}
|
|
// Value is an actual object, so set up everything that's needed.
|
|
if (value != null)
|
|
{
|
|
_SelectedGameObject = value;
|
|
AttachToObject = value;
|
|
SelectObject(value);
|
|
CalculateDensity();
|
|
ToggleCollidersEnabled(ColliderSelectEnabled);
|
|
}
|
|
}
|
|
_SelectedGameObject = value;
|
|
AttachToObject = value;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("Easy Collider Editor's Selected GameObject must be located in the scene. Select a gameobject from the scene hierarchy. If you wish to use a prefab, enter prefab isolation mode then select the object. For more information of editing prefabs, see the included documentation pdf.");
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<EasyColliderVertex> _SelectedVertices;
|
|
/// <summary>
|
|
/// Selected Vertices list (Needs to be a list, as hashsets are unordered, and some of the collider methods require specific order selection (like rotated ones))
|
|
/// </summary>
|
|
public List<EasyColliderVertex> SelectedVertices
|
|
{
|
|
get
|
|
{
|
|
if (_SelectedVertices == null)
|
|
{
|
|
_SelectedVertices = new List<EasyColliderVertex>();
|
|
}
|
|
return _SelectedVertices;
|
|
}
|
|
private set { _SelectedVertices = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private HashSet<EasyColliderVertex> _SelectedVerticesSet;
|
|
/// <summary>
|
|
/// HashSet of SelectedVertices. Used to make things a little faster to search through.
|
|
/// </summary>
|
|
public HashSet<EasyColliderVertex> SelectedVerticesSet
|
|
{
|
|
get
|
|
{
|
|
if (_SelectedVerticesSet == null)
|
|
{
|
|
_SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
return _SelectedVerticesSet;
|
|
}
|
|
set { _SelectedVerticesSet = value; }
|
|
}
|
|
|
|
//Serializing our hashsets.
|
|
[SerializeField]
|
|
private List<EasyColliderVertex> _SerializedSelectedVertexSet;
|
|
|
|
[SerializeField]
|
|
private List<Vector3> _TransformPositions;
|
|
/// <summary>
|
|
/// List of mesh filter world positions
|
|
/// </summary>
|
|
public List<Vector3> TransformPositions
|
|
{
|
|
get
|
|
{
|
|
if (_TransformPositions == null)
|
|
{
|
|
_TransformPositions = new List<Vector3>();
|
|
}
|
|
return _TransformPositions;
|
|
}
|
|
set { _TransformPositions = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<Quaternion> _TransformRotations;
|
|
/// <summary>
|
|
/// List of mesh filter rotations
|
|
/// </summary>
|
|
public List<Quaternion> TransformRotations
|
|
{
|
|
get
|
|
{
|
|
if (_TransformRotations == null)
|
|
{
|
|
_TransformRotations = new List<Quaternion>();
|
|
}
|
|
return _TransformRotations;
|
|
}
|
|
set { _TransformRotations = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
private List<Vector3> _TransformLocalScales;
|
|
/// <summary>
|
|
/// List of mesh filter local scales
|
|
/// </summary>
|
|
public List<Vector3> TransformLocalScales
|
|
{
|
|
get
|
|
{
|
|
if (_TransformLocalScales == null)
|
|
{
|
|
_TransformLocalScales = new List<Vector3>();
|
|
}
|
|
return _TransformLocalScales;
|
|
}
|
|
set { _TransformLocalScales = value; }
|
|
}
|
|
|
|
|
|
[SerializeField]
|
|
/// <summary>
|
|
/// Is vertex selection enabled?
|
|
/// </summary>
|
|
public bool VertexSelectEnabled;
|
|
|
|
HashSet<Vector3> _WorldMeshVertices;
|
|
/// <summary>
|
|
/// Set of all world space vertices for meshes that are able to have their vertices selected
|
|
/// </summary>
|
|
/// <value></value>
|
|
public HashSet<Vector3> WorldMeshVertices
|
|
{
|
|
get
|
|
{
|
|
if (_WorldMeshVertices == null)
|
|
{
|
|
_WorldMeshVertices = new HashSet<Vector3>();
|
|
}
|
|
return _WorldMeshVertices;
|
|
}
|
|
}
|
|
|
|
HashSet<Vector3> _WorldMeshPositions;
|
|
/// <summary>
|
|
/// Set of mesh filters positions, for update world mesh vertices.
|
|
/// </summary>
|
|
/// <value></value>
|
|
HashSet<Vector3> WorldMeshPositions
|
|
{
|
|
get
|
|
{
|
|
if (_WorldMeshPositions == null)
|
|
{
|
|
_WorldMeshPositions = new HashSet<Vector3>();
|
|
}
|
|
return _WorldMeshPositions;
|
|
}
|
|
}
|
|
|
|
HashSet<Quaternion> _WorldMeshRotations;
|
|
/// <summary>
|
|
/// Set of world mesh rotations, for updating world mesh vertices.
|
|
/// </summary>
|
|
HashSet<Quaternion> WorldMeshRotations
|
|
{
|
|
get
|
|
{
|
|
if (_WorldMeshRotations == null)
|
|
{
|
|
_WorldMeshRotations = new HashSet<Quaternion>();
|
|
}
|
|
return _WorldMeshRotations;
|
|
}
|
|
}
|
|
|
|
HashSet<Transform> _WorldMeshTransforms;
|
|
/// <summary>
|
|
/// Set of world mesh transforms, for updating world mesh vertices.
|
|
/// </summary>
|
|
HashSet<Transform> WorldMeshTransforms
|
|
{
|
|
get
|
|
{
|
|
if (_WorldMeshTransforms == null)
|
|
{
|
|
_WorldMeshTransforms = new HashSet<Transform>();
|
|
}
|
|
return _WorldMeshTransforms;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates density for displaying, note that this calculates density as volume / vertex instead of vertex/volume.true
|
|
/// This way more vertices means less scale when display vertices.
|
|
/// </summary>
|
|
private void CalculateDensity()
|
|
{
|
|
int totalNumVertices = 0;
|
|
float totalVolume = 0.0f;
|
|
foreach (MeshFilter meshFilter in MeshFilters)
|
|
{
|
|
if (meshFilter.sharedMesh == null) continue;
|
|
totalNumVertices += meshFilter.sharedMesh.vertexCount;
|
|
totalVolume += meshFilter.sharedMesh.bounds.size.x * meshFilter.sharedMesh.bounds.size.y * meshFilter.sharedMesh.bounds.size.z;
|
|
}
|
|
DensityScale = totalVolume / Mathf.Pow(totalNumVertices, 3);
|
|
DensityScale = Mathf.Max(0.01f, DensityScale);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all vertices that have index's greater than MeshFilter's count.
|
|
/// </summary>
|
|
private void CleanChildSelectedVertices()
|
|
{
|
|
// SelectedVertices.RemoveAll(vert => vert.MeshFilterIndex >= MeshFilters.Count);
|
|
SelectedVertices.RemoveAll(vert => vert.T != SelectedGameObject.transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleans up the object and children if required. Destroys components based on if going into play mode. Reenables/disables components to pre-selection values.
|
|
/// </summary>
|
|
/// <param name="go">Parent object to clean up</param>
|
|
/// <param name="intoPlayMode">Is the editor going into play mode?</param>
|
|
public void CleanUpObject(GameObject go, bool intoPlayMode)
|
|
{
|
|
foreach (int id in AddedInstanceIDs)
|
|
{
|
|
Object o = EditorUtility.InstanceIDToObject(id);
|
|
if (o != null)
|
|
{
|
|
if (intoPlayMode)
|
|
{
|
|
DestroyImmediate(o);
|
|
}
|
|
else
|
|
{
|
|
Undo.DestroyObjectImmediate(o);
|
|
}
|
|
}
|
|
}
|
|
foreach (Rigidbody rb in NonKinematicRigidbodies)
|
|
{
|
|
if (rb != null)
|
|
{
|
|
rb.isKinematic = false;
|
|
}
|
|
}
|
|
foreach (Collider col in DisabledColliders)
|
|
{
|
|
if (col != null)
|
|
{
|
|
col.enabled = true;
|
|
}
|
|
}
|
|
foreach (Collider col in PreDisabledColliders)
|
|
{
|
|
if (col != null)
|
|
{
|
|
col.enabled = false;
|
|
}
|
|
}
|
|
// force cleanup gizmos and compute.
|
|
if (Gizmos != null)
|
|
{
|
|
Undo.DestroyObjectImmediate(Gizmos);
|
|
}
|
|
if (Compute != null)
|
|
{
|
|
Undo.DestroyObjectImmediate(Compute);
|
|
}
|
|
if (go != null)
|
|
{
|
|
// Enable by added collider instance ids incase they were lost.
|
|
Collider[] cols = go.GetComponentsInChildren<Collider>();
|
|
foreach (Collider col in cols)
|
|
{
|
|
if (AddedColliderIDs.Contains(col.GetInstanceID()))
|
|
{
|
|
col.enabled = true;
|
|
}
|
|
}
|
|
}
|
|
HasSkinnedMeshRenderer = false;
|
|
ClearListsForNewObject();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates new lists for all the lists used.
|
|
/// </summary>
|
|
void ClearListsForNewObject()
|
|
{
|
|
AddedInstanceIDs = new List<int>();
|
|
CreatedColliders = new List<Collider>();
|
|
DisabledColliders = new List<Collider>();
|
|
LastSelectedVertices = new List<EasyColliderVertex>();
|
|
MeshFilters = new List<MeshFilter>();
|
|
NonKinematicRigidbodies = new List<Rigidbody>();
|
|
PreDisabledColliders = new List<Collider>();
|
|
RaycastableColliders = new List<Collider>();
|
|
SelectedColliders = new List<Collider>();
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
#if (!UNITY_EDITOR_LINUX)
|
|
_VHACDPreviewResult = new Dictionary<Transform, Mesh[]>();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deselects all currently selected vertices.
|
|
/// </summary>
|
|
public void ClearSelectedVertices()
|
|
{
|
|
this.SelectedVertices = new List<EasyColliderVertex>();
|
|
this.SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a box colider with a given orientation
|
|
/// </summary>
|
|
/// <param name="orientation">Orientation of box collider</param>
|
|
public void CreateBoxCollider(COLLIDER_ORIENTATION orientation = COLLIDER_ORIENTATION.NORMAL)
|
|
{
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
Collider createdCollider = ecc.CreateBoxCollider(GetWorldVertices(), GetProperties(orientation));
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a capsule collider using the method and orientation provided.
|
|
/// </summary>
|
|
/// <param name="method">Capsule collider algoirthm to use</param>
|
|
/// <param name="orientation">Orientation to use</param>
|
|
public void CreateCapsuleCollider(CAPSULE_COLLIDER_METHOD method, COLLIDER_ORIENTATION orientation = COLLIDER_ORIENTATION.NORMAL)
|
|
{
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
Collider createdCollider = null;
|
|
switch (method)
|
|
{
|
|
// use the same method for all min max' but pass the method in.
|
|
case CAPSULE_COLLIDER_METHOD.MinMax:
|
|
case CAPSULE_COLLIDER_METHOD.MinMaxPlusDiameter:
|
|
case CAPSULE_COLLIDER_METHOD.MinMaxPlusRadius:
|
|
createdCollider = ecc.CreateCapsuleCollider_MinMax(GetWorldVertices(), GetProperties(orientation), method);
|
|
break;
|
|
case CAPSULE_COLLIDER_METHOD.BestFit:
|
|
createdCollider = ecc.CreateCapsuleCollider_BestFit(GetWorldVertices(), GetProperties(orientation));
|
|
break;
|
|
}
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
|
|
public void CreateCylinderCollider()
|
|
{
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
// convert the selected world points to local points that represent a cylinder.
|
|
MeshColliderData data = ecc.CalculateCylinderCollider(GetWorldVertices(), AttachToObject.transform);
|
|
// generate the hull from the cylinder points.
|
|
MeshCollider createdCollider = ecc.CreateConvexMeshCollider(data.ConvexMesh, AttachToObject, GetProperties());
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a convex mesh collider from the currently selected points using the given method
|
|
/// </summary>
|
|
/// <param name="method"></param>
|
|
public void CreateConvexMeshCollider(MESH_COLLIDER_METHOD method)
|
|
{
|
|
Mesh mesh = CreateConvexMesh(method);
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
MeshCollider createdCollider = ecc.CreateConvexMeshCollider(mesh, AttachToObject, GetProperties());
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a convex mesh from the currently selected points using the given method
|
|
/// </summary>
|
|
/// <param name="method"></param>
|
|
/// <returns>Convex Mesh</returns>
|
|
private Mesh CreateConvexMesh(MESH_COLLIDER_METHOD method)
|
|
{
|
|
if (method == MESH_COLLIDER_METHOD.QuickHull)
|
|
{
|
|
return new EasyColliderCreator().CreateMesh_QuickHull(GetWorldVertices(), AttachToObject);
|
|
}
|
|
else
|
|
{
|
|
return new EasyColliderCreator().CreateMesh_Messy(GetWorldVertices(), AttachToObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a sphere collider
|
|
/// </summary>
|
|
/// <param name="method">Algorith to use to create the sphere collider.</param>
|
|
public void CreateSphereCollider(SPHERE_COLLIDER_METHOD method)
|
|
{
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
Collider createdCollider = null;
|
|
switch (method)
|
|
{
|
|
case SPHERE_COLLIDER_METHOD.BestFit:
|
|
createdCollider = ecc.CreateSphereCollider_BestFit(GetWorldVertices(), GetProperties());
|
|
break;
|
|
case SPHERE_COLLIDER_METHOD.Distance:
|
|
createdCollider = ecc.CreateSphereCollider_Distance(GetWorldVertices(), GetProperties());
|
|
break;
|
|
case SPHERE_COLLIDER_METHOD.MinMax:
|
|
createdCollider = ecc.CreateSphereCollider_MinMax(GetWorldVertices(), GetProperties());
|
|
break;
|
|
}
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disables a created collider based on preferences
|
|
/// </summary>
|
|
/// <param name="col">Collider to disable</param>
|
|
public void DisableCreatedCollider(Collider col)
|
|
{
|
|
// keep track fo the collider that was created.
|
|
CreatedColliders.Add(col);
|
|
AddedColliderIDs.Add(col.GetInstanceID());
|
|
if (CreatedColliderDisabled && !ColliderSelectEnabled)
|
|
{
|
|
col.enabled = false;
|
|
DisabledColliders.Add(col);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all colliders on parent + children if including children.
|
|
/// </summary>
|
|
/// <returns>Array of all colliders</returns>
|
|
private Collider[] GetAllColliders()
|
|
{
|
|
if (SelectedGameObject != null)
|
|
{
|
|
// Allow selecting colliders from selected gameobject, and it's children, and attach to / it's children.
|
|
Collider[] selectedColliders = SelectedGameObject.GetComponentsInChildren<Collider>();
|
|
Collider[] attachColliders = AttachToObject.GetComponentsInChildren<Collider>();
|
|
selectedColliders = selectedColliders.Concat(attachColliders).ToArray();
|
|
return selectedColliders;
|
|
}
|
|
return new Collider[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all the locations in world space of all MeshFilters vertices.
|
|
/// </summary>
|
|
/// <returns>World space locations of all mesh filters</returns>
|
|
public HashSet<Vector3> GetAllWorldMeshVertices()
|
|
{
|
|
bool hasChanged = false;
|
|
// if the pos, rot, or transform count is different than the mesh filter count we know we need to update.
|
|
if (WorldMeshPositions.Count != MeshFilters.Count || WorldMeshRotations.Count != MeshFilters.Count || WorldMeshTransforms.Count != MeshFilters.Count)
|
|
{
|
|
hasChanged = true;
|
|
}
|
|
if (!hasChanged)
|
|
{
|
|
// we need to update if any of the mesh transforms have moved, or translated,
|
|
// or the transform itself is different (ie. 2 different objects with same pos and rotation still means we need to update)
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (!WorldMeshPositions.Contains(filter.transform.position))
|
|
{
|
|
hasChanged = true;
|
|
break;
|
|
}
|
|
if (!WorldMeshRotations.Contains(filter.transform.rotation))
|
|
{
|
|
hasChanged = true;
|
|
break;
|
|
}
|
|
if (!WorldMeshTransforms.Contains(filter.transform))
|
|
{
|
|
hasChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// need to recalculate all the world locations.
|
|
if (hasChanged)
|
|
{
|
|
// Clear our lists to rebuild them.
|
|
WorldMeshVertices.Clear();
|
|
WorldMeshPositions.Clear();
|
|
WorldMeshRotations.Clear();
|
|
WorldMeshTransforms.Clear();
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (filter != null)
|
|
{
|
|
Transform t = filter.transform;
|
|
WorldMeshPositions.Add(t.position);
|
|
WorldMeshRotations.Add(t.rotation);
|
|
WorldMeshTransforms.Add(t);
|
|
Vector3[] vertices = filter.sharedMesh.vertices;
|
|
foreach (Vector3 vert in vertices)
|
|
{
|
|
WorldMeshVertices.Add(t.TransformPoint(vert));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// nothings changed? just return our hashset of world points.
|
|
return WorldMeshVertices;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all the mesh filters on the object. Gets the child meshes if include children is enabled, and creates mesh filters for any skinned mesh renderers if required.
|
|
/// </summary>
|
|
/// <param name="go">Parent object to get mesh filters from.</param>
|
|
/// <returns>List of mesh filters for the object, children, and skinned mesh renderers.</returns>
|
|
List<MeshFilter> GetMeshFilters(GameObject go)
|
|
{
|
|
if (go == null) return null;
|
|
List<MeshFilter> meshFilters = new List<MeshFilter>();
|
|
if (IncludeChildMeshes)
|
|
{
|
|
MeshFilter[] childMeshFilters = go.GetComponentsInChildren<MeshFilter>(false);
|
|
foreach (MeshFilter childMeshFilter in childMeshFilters)
|
|
{
|
|
if (childMeshFilter != null)
|
|
{
|
|
meshFilters.Add(childMeshFilter);
|
|
}
|
|
}
|
|
SkinnedMeshRenderer[] childSkinnedMeshRenderers = go.GetComponentsInChildren<SkinnedMeshRenderer>(false);
|
|
if (AutoIncludeChildSkinnedMeshes)
|
|
{
|
|
foreach (SkinnedMeshRenderer smr in childSkinnedMeshRenderers)
|
|
{
|
|
meshFilters.Add(SetupFilterForSkinnedMesh(smr));
|
|
}
|
|
}
|
|
if (childSkinnedMeshRenderers.Length > 0)
|
|
{
|
|
HasSkinnedMeshRenderer = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
|
if (meshFilter != null)
|
|
{
|
|
meshFilters.Add(meshFilter);
|
|
}
|
|
else
|
|
{
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if (smr != null)
|
|
{
|
|
meshFilters.Add(SetupFilterForSkinnedMesh(smr));
|
|
}
|
|
}
|
|
SkinnedMeshRenderer skinnedMesh = go.GetComponentInChildren<SkinnedMeshRenderer>(false);
|
|
if (skinnedMesh != null)
|
|
{
|
|
HasSkinnedMeshRenderer = true;
|
|
}
|
|
}
|
|
return meshFilters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an EasyColliderProperties based on the ECEditors values.
|
|
/// </summary>
|
|
/// <param name="orientation">Orientation property</param>
|
|
/// <returns>EasyColliderProperties to pass to collider creation methods</returns>
|
|
public EasyColliderProperties GetProperties(COLLIDER_ORIENTATION orientation = COLLIDER_ORIENTATION.NORMAL)
|
|
{
|
|
EasyColliderProperties ecp = new EasyColliderProperties();
|
|
ecp.IsTrigger = IsTrigger;
|
|
ecp.PhysicMaterial = PhysicMaterial;
|
|
if (SelectedGameObject != null)
|
|
{
|
|
ecp.Layer = RotatedOnSelectedLayer ? SelectedGameObject.layer : RotatedColliderLayer;
|
|
}
|
|
else
|
|
{
|
|
ecp.Layer = RotatedColliderLayer;
|
|
}
|
|
|
|
ecp.AttachTo = AttachToObject;
|
|
ecp.Orientation = orientation;
|
|
return ecp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a list of the selected vertices in world space positions.
|
|
/// </summary>
|
|
/// <returns>List of world space positions.</returns>
|
|
public List<Vector3> GetWorldVertices()
|
|
{
|
|
if (SelectedGameObject == null) { }
|
|
// since order can sometimes matter, we're using lists.
|
|
// create list with enough space for all the vertices.
|
|
List<Vector3> worldVertices = new List<Vector3>(SelectedVertices.Count);
|
|
foreach (EasyColliderVertex ecv in SelectedVertices)
|
|
{
|
|
if (ecv.T == null) continue;
|
|
worldVertices.Add(ecv.T.TransformPoint(ecv.LocalPosition));
|
|
}
|
|
return worldVertices;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows selected vertices out from all selected vertices
|
|
/// </summary>
|
|
public void GrowAllSelectedVertices()
|
|
{
|
|
GrowVertices(SelectedVerticesSet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows selected vertices out from all selected vertices until it can no longer grow.
|
|
/// </summary>
|
|
public void GrowAllSelectedVerticesMax()
|
|
{
|
|
int startCount = 0;
|
|
int currentCount = 0;
|
|
do
|
|
{
|
|
startCount = SelectedVerticesSet.Count;
|
|
GrowVertices(SelectedVerticesSet);
|
|
currentCount = SelectedVerticesSet.Count;
|
|
} while (startCount != currentCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows selected vertices out from the last selected vertex(s)
|
|
/// </summary>
|
|
public void GrowLastSelectedVertices()
|
|
{
|
|
HashSet<EasyColliderVertex> set = new HashSet<EasyColliderVertex>();
|
|
set.UnionWith(LastSelectedVertices);
|
|
GrowVertices(set);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows selected vertices from the last selected vertices unttil it can no longer be grown.
|
|
/// </summary>
|
|
public void GrowLastSelectedVerticesMax()
|
|
{
|
|
int startCount = 0;
|
|
int currentCount = 0;
|
|
do
|
|
{
|
|
startCount = SelectedVerticesSet.Count;
|
|
GrowLastSelectedVertices();
|
|
currentCount = SelectedVerticesSet.Count;
|
|
} while (startCount != currentCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows the list of vertices by shared triangles
|
|
/// </summary>
|
|
/// <param name="verticesToGrow">The list of vertices to expand out from</param>
|
|
public void GrowVertices(HashSet<EasyColliderVertex> verticesToGrow)
|
|
{
|
|
HashSet<EasyColliderVertex> newSelectedVertices = new HashSet<EasyColliderVertex>();
|
|
Transform t;
|
|
Vector3[] vertices;
|
|
int[] triangles;
|
|
// Go through every filter & triangle, seems the fastest way to do it without storing the vertices & triangles of every mesh.
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (filter == null || filter.sharedMesh == null) continue;
|
|
triangles = filter.sharedMesh.triangles;
|
|
vertices = filter.sharedMesh.vertices;
|
|
t = filter.transform;
|
|
for (int i = 0; i < triangles.Length; i += 3)
|
|
{
|
|
EasyColliderVertex ecv1 = new EasyColliderVertex(t, vertices[triangles[i]]);
|
|
EasyColliderVertex ecv2 = new EasyColliderVertex(t, vertices[triangles[i + 1]]);
|
|
EasyColliderVertex ecv3 = new EasyColliderVertex(t, vertices[triangles[i + 2]]);
|
|
if (verticesToGrow.Contains(ecv1) || verticesToGrow.Contains(ecv2) || verticesToGrow.Contains(ecv3))
|
|
{
|
|
newSelectedVertices.Add(ecv1);
|
|
newSelectedVertices.Add(ecv2);
|
|
newSelectedVertices.Add(ecv3);
|
|
}
|
|
}
|
|
}
|
|
// newly selected vertices are the ones where they are in the new set, but aren't currently in the selected set.
|
|
List<EasyColliderVertex> newSelected = newSelectedVertices.Where(value => !SelectedVerticesSet.Contains(value)).ToList();
|
|
// Add the new ones to the list.
|
|
SelectedVertices.AddRange(newSelected);
|
|
// clear the set, then union with the select vertices.
|
|
SelectedVerticesSet.Clear();
|
|
SelectedVerticesSet.UnionWith(SelectedVertices);
|
|
// these aren't really the "last selected" as it contains the previous grown set as well, but its close enough that it doesn't really matter much.
|
|
LastSelectedVertices = newSelected;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the transforms the mesh filters of the currently selected gameobject have moved or rotated
|
|
/// </summary>
|
|
/// <param name="update">Should the list of transforms be updated?</param>
|
|
/// <returns>True if any of the valid transform meshes are on have moved</returns>
|
|
public bool HasTransformMoved(bool update = false)
|
|
{
|
|
bool hasMoved = false;
|
|
Transform t = null;
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (filter == null) { continue; }
|
|
t = filter.transform;
|
|
if (filter != null
|
|
&& t != null
|
|
&& !TransformPositions.Contains(t.position)
|
|
|| !TransformRotations.Contains(t.rotation)
|
|
|| !TransformLocalScales.Contains(t.localScale))
|
|
{
|
|
hasMoved = true;
|
|
break;
|
|
}
|
|
}
|
|
if (hasMoved && update)
|
|
{
|
|
TransformPositions.Clear();
|
|
TransformRotations.Clear();
|
|
TransformLocalScales.Clear();
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (filter == null) { continue; }
|
|
t = filter.transform;
|
|
if (filter != null)
|
|
{
|
|
TransformRotations.Add(t.rotation);
|
|
TransformPositions.Add(t.position);
|
|
TransformLocalScales.Add(t.localScale);
|
|
}
|
|
}
|
|
}
|
|
return hasMoved;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inverts the currently selected vertices
|
|
/// </summary>
|
|
public void InvertSelectedVertices()
|
|
{
|
|
// list of new vertices (this doesn't have to be a list, since when inverting it'll mess up the order anyway)
|
|
// but we can just use a list, and then union with the selected set at the end, since we're not doing any checking on it.
|
|
List<EasyColliderVertex> inverted = new List<EasyColliderVertex>();
|
|
// world positions for selected & invert, as we don't want to duplicate vertices due to triangles sharing vertices.
|
|
HashSet<Vector3> selectedWorldPositions = new HashSet<Vector3>();
|
|
HashSet<Vector3> invertedWorldPositions = new HashSet<Vector3>();
|
|
// get selected vertices in worldspace
|
|
foreach (EasyColliderVertex vert in SelectedVertices)
|
|
{
|
|
selectedWorldPositions.Add(vert.T.TransformPoint(vert.LocalPosition));
|
|
}
|
|
// Variables to hold values
|
|
Vector3[] vertices;
|
|
Transform transform;
|
|
Vector3 transformedPosition;
|
|
for (int i = 0; i < MeshFilters.Count; i++)
|
|
{
|
|
if (MeshFilters[i] != null && MeshFilters[i].sharedMesh != null)
|
|
{
|
|
// we only assign vertices array once per filter.
|
|
transform = MeshFilters[i].transform;
|
|
vertices = MeshFilters[i].sharedMesh.vertices;
|
|
for (int j = 0; j < vertices.Length; j++)
|
|
{
|
|
transformedPosition = transform.TransformPoint(vertices[j]);
|
|
// if it's currently not selected, and isn't already in the inverted positions
|
|
// (multiple vertices share same world space because of triangles)
|
|
if (!selectedWorldPositions.Contains(transformedPosition) && !invertedWorldPositions.Contains(transformedPosition))
|
|
{
|
|
invertedWorldPositions.Add(transformedPosition);
|
|
inverted.Add(new EasyColliderVertex(transform, vertices[j]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SelectedVertices = inverted;
|
|
// clear and union the selected set with the new inverted list.
|
|
SelectedVerticesSet.Clear();
|
|
SelectedVerticesSet.UnionWith(inverted);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to see if a collider is already selected
|
|
/// </summary>
|
|
/// <param name="collider">Collider to check</param>
|
|
/// <returns>True if selected</returns>
|
|
public bool IsColliderSelected(Collider collider)
|
|
{
|
|
if (SelectedColliders.Contains(collider))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to make sure the collider is in the list of colliders that were added, or disabled.
|
|
/// </summary>
|
|
/// <param name="collider">Collider to check</param>
|
|
/// <returns>true if selectable</returns>
|
|
public bool IsSelectableCollider(Collider collider)
|
|
{
|
|
if (GetAllColliders().Contains(collider))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called after deserializing, used to deserilize our serializable list of selected points back into the hashset.
|
|
/// </summary>
|
|
public void OnAfterDeserialize()
|
|
{
|
|
// Deserialize our hashsets.
|
|
if (_SerializedSelectedVertexSet.Count > 0)
|
|
{
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>(_SerializedSelectedVertexSet);
|
|
}
|
|
else
|
|
{
|
|
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called before serialization, used to store our hashset of selected vertices into a serializable list.
|
|
/// </summary>
|
|
public void OnBeforeSerialize()
|
|
{
|
|
// Serialize ours hashsets.
|
|
if (_SerializedSelectedVertexSet == null)
|
|
{
|
|
_SerializedSelectedVertexSet = new List<EasyColliderVertex>();
|
|
}
|
|
_SerializedSelectedVertexSet = SelectedVerticesSet.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all colliders on the currently selected gameobject + attach to, and it's children.
|
|
/// </summary>
|
|
public void RemoveAllColliders()
|
|
{
|
|
// Get colliders from either selected or selected + children.
|
|
Collider[] colliders = GetAllColliders();
|
|
// set selcted colliders
|
|
SelectedColliders = colliders.ToList();
|
|
// remove them.
|
|
RemoveSelectedColliders();
|
|
// traverse children to remove vhacd colliders
|
|
List<GameObject> vhacdHolders = AttachToObject.GetComponentsInChildren<Transform>().Where(x => x.gameObject.name.Contains("VHACDColliders")).Select(a => a.gameObject).ToList();
|
|
foreach (GameObject obj in vhacdHolders)
|
|
{
|
|
Undo.DestroyObjectImmediate(obj);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the currently selected colliders.
|
|
/// </summary>
|
|
public void RemoveSelectedColliders()
|
|
{
|
|
foreach (Collider col in SelectedColliders)
|
|
{
|
|
// skip if null, or it's a collider we've added for functionality.
|
|
if (col == null || AddedInstanceIDs.Contains(col.GetInstanceID())) continue;
|
|
DisabledColliders.Remove(col);
|
|
CreatedColliders.Remove(col);
|
|
if (col.transform.childCount == 0 && ((col.gameObject.name.Contains("Rotated") && col.gameObject.name.Contains("Collider")) || col.gameObject.name.Contains("VHACDCollider")))
|
|
{ // is a rotated collider, or a vhacd collider.
|
|
Collider[] collidersOnRotatedGameObject = col.GetComponents<Collider>();
|
|
bool removeRotated = true;
|
|
foreach (Collider collider in collidersOnRotatedGameObject)
|
|
{
|
|
if (!SelectedColliders.Contains(collider))
|
|
{
|
|
removeRotated = false;
|
|
break;
|
|
}
|
|
}
|
|
if (removeRotated)
|
|
{
|
|
Undo.RecordObject(col.gameObject, "Remove collider");
|
|
Undo.DestroyObjectImmediate(col.gameObject);
|
|
}
|
|
else
|
|
{
|
|
// just remove the selected collider.
|
|
Undo.DestroyObjectImmediate(col);
|
|
}
|
|
}
|
|
else // has children, not a rotated collider holder or vhacd collider.
|
|
{
|
|
Undo.RecordObject(col, "Remove collider");
|
|
Undo.DestroyObjectImmediate(col);
|
|
}
|
|
}
|
|
SelectedColliders = new List<Collider>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rings around the last 2 selected vertices, selecting all the vertices in the ring.
|
|
/// </summary>
|
|
public void RingSelectVertices()
|
|
{
|
|
if (SelectedVertices.Count < 2)
|
|
{
|
|
Debug.LogWarning("Easy Collider Editor: Ring select requires 2 selected vertices.");
|
|
return;
|
|
}
|
|
// last 2 selected vertices must come from the same transform, otherwise you can't really ring around a mesh..
|
|
if (SelectedVertices[SelectedVertices.Count - 1].T != SelectedVertices[SelectedVertices.Count - 2].T)
|
|
{
|
|
Debug.LogWarning("Easy Collider Editor: Ring select from different transforms not allowed.");
|
|
return;
|
|
}
|
|
// list of all the vertice's were going to add at the end
|
|
List<EasyColliderVertex> newVerticesToAdd = new List<EasyColliderVertex>();
|
|
// add the last 2 vertices initially so we know where to end.
|
|
newVerticesToAdd.Add(SelectedVertices[SelectedVertices.Count - 1]);
|
|
newVerticesToAdd.Add(SelectedVertices[SelectedVertices.Count - 2]);
|
|
// get mesh's vertices & triangles.
|
|
Vector3[] vertices = new Vector3[0];
|
|
int[] triangles = new int[3];
|
|
Transform t = SelectedVertices[SelectedVertices.Count - 1].T;
|
|
foreach (MeshFilter filter in MeshFilters)
|
|
{
|
|
if (filter.transform == t)
|
|
{
|
|
vertices = filter.sharedMesh.vertices;
|
|
triangles = filter.sharedMesh.triangles;
|
|
}
|
|
}
|
|
// start vertex
|
|
Vector3 currentVertex = SelectedVertices[SelectedVertices.Count - 1].LocalPosition;
|
|
// previous vertex.
|
|
Vector3 prevVertex = SelectedVertices[SelectedVertices.Count - 2].LocalPosition;
|
|
// directon vector for first 2 points.
|
|
Vector3 currentDirection = (currentVertex - prevVertex).normalized;
|
|
// Directions for calculations
|
|
Vector3 directionA, directionB = directionA = Vector3.zero;
|
|
// points for calculations
|
|
Vector3 pointA, pointB = pointA = Vector3.zero;
|
|
// angle from calculations
|
|
float angleA, angleB = angleA = 0.0f;
|
|
|
|
// best point found in each iteration
|
|
Vector3 bestPoint = Vector3.zero;
|
|
// direction fot he best point (from current point)
|
|
Vector3 bestDirection = Vector3.zero;
|
|
// best angle from the best point (angle between current direction and best points direction from current point)
|
|
float bestAngle = Mathf.Infinity;
|
|
while (true)
|
|
{
|
|
// reset best angle for each iteration.
|
|
bestAngle = Mathf.Infinity;
|
|
// go through all the triangles.
|
|
for (int i = 0; i < triangles.Length; i += 3)
|
|
{
|
|
// if the triangle doesn't contain both the current and previous vertex.
|
|
// (as it's by the position, it allows cross edges that match position but not actual vertices' index)
|
|
if ((vertices[triangles[i]] == currentVertex || vertices[triangles[i + 1]] == currentVertex || vertices[triangles[i + 2]] == currentVertex)
|
|
&& (vertices[triangles[i]] != prevVertex && vertices[triangles[i + 1]] != prevVertex && vertices[triangles[i + 2]] != prevVertex))
|
|
{
|
|
// if it's the first vertex.
|
|
if (vertices[triangles[i]] == currentVertex)
|
|
{
|
|
// set the values for the pointA, pointB, directionA, and directionB to calculate.
|
|
pointA = vertices[triangles[i + 1]];
|
|
pointB = vertices[triangles[i + 2]];
|
|
directionA = pointA - currentVertex;
|
|
directionB = pointB - currentVertex;
|
|
}
|
|
else if (vertices[triangles[i + 1]] == currentVertex)
|
|
{
|
|
pointA = vertices[triangles[i]];
|
|
pointB = vertices[triangles[i + 2]];
|
|
directionA = pointA - currentVertex;
|
|
directionB = pointB - currentVertex;
|
|
}
|
|
else if (vertices[triangles[i + 2]] == currentVertex)
|
|
{
|
|
pointA = vertices[triangles[i]];
|
|
pointB = vertices[triangles[i + 1]];
|
|
directionA = pointA - currentVertex;
|
|
directionB = pointB - currentVertex;
|
|
}
|
|
// calculate angles between current direction and the direction to point A and point B.
|
|
angleA = Vector3.Angle(currentDirection, directionA);
|
|
angleB = Vector3.Angle(currentDirection, directionB);
|
|
// if the angle is less than our current best angle, and less than the other triangles angle
|
|
if (angleA < bestAngle && angleA < angleB)
|
|
{
|
|
// set our new best angle, best point, and best direction.
|
|
bestAngle = angleA;
|
|
bestPoint = pointA;
|
|
bestDirection = directionA;
|
|
}
|
|
else if (angleB < bestAngle && angleB < angleA)
|
|
{
|
|
bestAngle = angleB;
|
|
bestPoint = pointB;
|
|
bestDirection = directionB;
|
|
}
|
|
}
|
|
}
|
|
currentDirection = bestDirection;
|
|
prevVertex = currentVertex;
|
|
currentVertex = bestPoint;
|
|
EasyColliderVertex ecv = new EasyColliderVertex(t, bestPoint);
|
|
if (newVerticesToAdd.Contains(ecv))
|
|
{
|
|
// reach some kind of end (newest point is already to be added.)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
newVerticesToAdd.Add(ecv);
|
|
}
|
|
}
|
|
SelectedVertices.AddRange(newVerticesToAdd);
|
|
SelectedVerticesSet.UnionWith(newVerticesToAdd);
|
|
LastSelectedVertices.Clear();
|
|
LastSelectedVertices = newVerticesToAdd;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects or deselects a collider
|
|
/// </summary>
|
|
/// <param name="collider">collider to select or deselect.</param>
|
|
public void SelectCollider(Collider collider)
|
|
{
|
|
if (SelectedColliders.Contains(collider))
|
|
{
|
|
SelectedColliders.Remove(collider);
|
|
}
|
|
else
|
|
{
|
|
SelectedColliders.Add(collider);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the gameobject. Sets up the require components based for the object.
|
|
/// </summary>
|
|
/// <param name="obj">GameObject to select</param>
|
|
void SelectObject(GameObject obj)
|
|
{
|
|
// set up mesh filter list
|
|
MeshFilters = GetMeshFilters(obj);
|
|
// add / disable rigidbodies + colliders
|
|
SetRequiredComponentsFrom(obj, MeshFilters);
|
|
// add display vertices component.
|
|
if (RenderPointType == RENDER_POINT_TYPE.GIZMOS)
|
|
{
|
|
Gizmos = Undo.AddComponent<EasyColliderGizmos>(obj);
|
|
AddedInstanceIDs.Add(Gizmos.GetInstanceID());
|
|
}
|
|
else if (RenderPointType == RENDER_POINT_TYPE.SHADER)
|
|
{
|
|
Compute = Undo.AddComponent<EasyColliderCompute>(obj);
|
|
AddedInstanceIDs.Add(Compute.GetInstanceID());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects a bunch of vertices at once.
|
|
/// </summary>
|
|
/// <param name="vertices">Set of vertices</param>
|
|
public void SelectVertices(HashSet<EasyColliderVertex> vertices)
|
|
{
|
|
// removes selected vertices that are in the vertices hashset. (deselects already selected vertices)
|
|
List<EasyColliderVertex> prevSelected = SelectedVertices.Where((value, index) => !vertices.Contains(value)).ToList();
|
|
// adds vertices in the vertices set that aren't already selected (selects unselected vertices)
|
|
List<EasyColliderVertex> newSelected = vertices.Where((value) => !SelectedVerticesSet.Contains(value)).ToList();
|
|
// last selected are the newly selected vertices.
|
|
LastSelectedVertices.Clear();
|
|
LastSelectedVertices = newSelected;
|
|
// Combine the lists for the all currently selected vertices.
|
|
prevSelected.AddRange(newSelected);
|
|
SelectedVertices = prevSelected;
|
|
// clear the selected vertices set
|
|
SelectedVerticesSet.Clear();
|
|
// add all the currently selected vertices to it with a union.
|
|
SelectedVerticesSet.UnionWith(SelectedVertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects or deselects a vertex. Returns true if selected, false if deselected.
|
|
/// </summary>
|
|
/// <param name="ecv">Vertex to select</param>
|
|
/// <returns>True if selected, false if deselected.</returns>
|
|
public bool SelectVertex(EasyColliderVertex ecv)
|
|
{
|
|
|
|
if (SelectedVerticesSet.Remove(ecv))
|
|
{
|
|
SelectedVertices.Remove(ecv);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
LastSelectedVertices = new List<EasyColliderVertex>();
|
|
SelectedVerticesSet.Add(ecv);
|
|
SelectedVertices.Add(ecv);
|
|
LastSelectedVertices.Add(ecv);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the density values on gizmos and compute if needed.
|
|
/// </summary>
|
|
/// <param name="useDensityScale">Should density scaling be used?</param>
|
|
public void SetDensityOnDisplayers(bool useDensityScale)
|
|
{
|
|
if (Compute != null)
|
|
{
|
|
Compute.DensityScale = DensityScale;
|
|
Compute.UseDensityScale = useDensityScale;
|
|
}
|
|
if (Gizmos != null)
|
|
{
|
|
Gizmos.DensityScale = Gizmos.UseFixedGizmoScale ? DensityScale * 7.5f : DensityScale;
|
|
Gizmos.UseDensityScale = useDensityScale;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the required components from the parent to the children (if children are enabled)
|
|
/// This includes rigidbodies, colliders, and mesh colliders.
|
|
/// </summary>
|
|
/// <param name="parent"></param>
|
|
/// <param name="meshFilters"></param>
|
|
void SetRequiredComponentsFrom(GameObject parent, List<MeshFilter> meshFilters)
|
|
{
|
|
if (parent == null) return;
|
|
Rigidbody[] rigidbodies;
|
|
Collider[] colliders;
|
|
|
|
// get either parent + children or just parent rigidbodies & colliders.
|
|
if (IncludeChildMeshes)
|
|
{
|
|
rigidbodies = parent.GetComponentsInChildren<Rigidbody>();
|
|
colliders = parent.GetComponentsInChildren<Collider>();
|
|
}
|
|
else
|
|
{
|
|
rigidbodies = parent.GetComponents<Rigidbody>();
|
|
colliders = parent.GetComponents<Collider>();
|
|
}
|
|
|
|
// make sure rigidbodies are set to kinematic for raycasting
|
|
foreach (Rigidbody rb in rigidbodies)
|
|
{
|
|
if (!rb.isKinematic && !NonKinematicRigidbodies.Contains(rb))
|
|
{
|
|
Undo.RegisterCompleteObjectUndo(rb, "change isKinmatic");
|
|
rb.isKinematic = true;
|
|
NonKinematicRigidbodies.Add(rb);
|
|
}
|
|
}
|
|
|
|
// Disable currently enabled colliders, leave current disabled colliders disabled & keep track of which is which.
|
|
foreach (Collider col in colliders)
|
|
{
|
|
if (!DisabledColliders.Contains(col) && !PreDisabledColliders.Contains(col) && !AddedInstanceIDs.Contains(col.GetInstanceID()))
|
|
{
|
|
if (col.enabled)
|
|
{
|
|
if (!ColliderSelectEnabled)
|
|
{
|
|
Undo.RegisterCompleteObjectUndo(col, "Disable Collider");
|
|
col.enabled = false;
|
|
}
|
|
DisabledColliders.Add(col);
|
|
}
|
|
else { PreDisabledColliders.Add(col); }
|
|
}
|
|
}
|
|
|
|
// Add a mesh collider for every mesh filter.
|
|
foreach (MeshFilter filter in meshFilters)
|
|
{
|
|
if (filter != null)
|
|
{
|
|
MeshCollider mc = filter.GetComponent<MeshCollider>();
|
|
|
|
if (mc != null && mc.enabled && mc.sharedMesh == filter.sharedMesh)
|
|
{
|
|
RaycastableColliders.Add(mc);
|
|
continue;
|
|
} // don't add a mesh collider if it exists and is the correct mesh.
|
|
MeshCollider collider = Undo.AddComponent<MeshCollider>(filter.gameObject);
|
|
AddedInstanceIDs.Add(collider.GetInstanceID());
|
|
RaycastableColliders.Add(collider);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds / Removes / Enables / Disables required child components
|
|
/// </summary>
|
|
/// <param name="childrenEnabled">are children enabled?</param>
|
|
void SetupChildObjects(bool childrenEnabled)
|
|
{
|
|
MeshFilters = GetMeshFilters(SelectedGameObject);
|
|
if (childrenEnabled)
|
|
{
|
|
// Just essentially re-check all the components
|
|
SetRequiredComponentsFrom(SelectedGameObject, MeshFilters);
|
|
}
|
|
if (!childrenEnabled)
|
|
{
|
|
// Renable child components that were changed.
|
|
foreach (int id in AddedInstanceIDs)
|
|
{
|
|
Object o = EditorUtility.InstanceIDToObject(id);
|
|
if (o != null)
|
|
{
|
|
Component c = o as Component;
|
|
if (c != null)
|
|
{
|
|
if (c.gameObject != SelectedGameObject)
|
|
{
|
|
// remove mesh colliders from raycastable list.
|
|
if (c is MeshCollider)
|
|
{
|
|
MeshCollider mc = (MeshCollider)c;
|
|
RaycastableColliders.Remove(mc);
|
|
}
|
|
// bandaid to fix scaled mesh filter hanging around (even though they get cleaned up eventually)
|
|
if (c.gameObject.name.Contains("Scaled Mesh Filter"))
|
|
{
|
|
MeshCollider mc = c.GetComponent<MeshCollider>();
|
|
RaycastableColliders.Remove(mc);
|
|
Undo.DestroyObjectImmediate(c.gameObject);
|
|
}
|
|
else
|
|
{
|
|
Undo.DestroyObjectImmediate(o);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach (Rigidbody rb in NonKinematicRigidbodies)
|
|
{
|
|
if (rb != null & rb.gameObject != SelectedGameObject)
|
|
{
|
|
// without these the undo is still recored with a registercompleteobjectundo.
|
|
Undo.RecordObject(rb, "Set isKinematic");
|
|
rb.isKinematic = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mesh filter and bakes a mesh for a skinned mesh renderer.
|
|
/// </summary>
|
|
/// <param name="smr">Skinned mesh renderer to create the mesh filter for.</param>
|
|
/// <returns>The mesh filter that was created annd baked.</returns>
|
|
MeshFilter SetupFilterForSkinnedMesh(SkinnedMeshRenderer smr)
|
|
{
|
|
// Add a mesh filter and collider to the skinned mesh renderer while we select vertices.
|
|
MeshFilter filter = smr.GetComponent<MeshFilter>();
|
|
if (filter == null)
|
|
{
|
|
if (smr.transform.localScale != Vector3.one)
|
|
{
|
|
GameObject filterHolder = new GameObject("Scaled Mesh Filter (Temporary)");
|
|
filterHolder.transform.parent = smr.transform;
|
|
filterHolder.transform.localPosition = Vector3.zero;
|
|
filterHolder.transform.localRotation = Quaternion.identity;
|
|
AddedInstanceIDs.Add(filterHolder.GetInstanceID());
|
|
Undo.RegisterCreatedObjectUndo(filterHolder, "Create MeshFilter");
|
|
filter = Undo.AddComponent<MeshFilter>(filterHolder);
|
|
}
|
|
else
|
|
{
|
|
filter = Undo.AddComponent<MeshFilter>(smr.gameObject);
|
|
AddedInstanceIDs.Add(filter.GetInstanceID());
|
|
}
|
|
}
|
|
if (filter != null)
|
|
{
|
|
AddedInstanceIDs.Add(filter.GetInstanceID());
|
|
// Create a new mesh, so we prevent null refs by setting either the collider or filter's shared mesh.
|
|
Mesh mesh = new Mesh();
|
|
// Bake the skinned mesh to the mesh, otherwise you can have offset colliders/filters which aren't correctly located.
|
|
smr.BakeMesh(mesh);
|
|
|
|
// Set the shared mesh's to that mesh.
|
|
filter.sharedMesh = mesh;
|
|
|
|
// filter.sharedMesh = smr.sharedMesh;
|
|
// AddedComponentIDs.Add(filter.GetInstanceID());
|
|
// reset scale to what it was
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets values on this component from preferences.
|
|
/// </summary>
|
|
/// <param name="preferences"></param>
|
|
public void SetValuesFromPreferences(EasyColliderPreferences preferences)
|
|
{
|
|
NeedsPreferencesUpdate = false;
|
|
AutoIncludeChildSkinnedMeshes = preferences.AutoIncludeChildSkinnedMeshes;
|
|
RotatedOnSelectedLayer = preferences.RotatedOnSelectedLayer;
|
|
CreatedColliderDisabled = preferences.CreatedColliderDisabled;
|
|
RenderPointType = preferences.RenderPointType;
|
|
SetDensityOnDisplayers(preferences.UseDensityScale);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enabled and disables all colliders based on if collider selection is enabled.
|
|
/// </summary>
|
|
/// <param name="colliderSelectionEnabled">is collider selection enabled?</param>
|
|
private void ToggleCollidersEnabled(bool colliderSelectionEnabled)
|
|
{
|
|
// Get colliders from either selected or selected + children.
|
|
Collider[] colliders = GetAllColliders();
|
|
foreach (Collider col in colliders)
|
|
{
|
|
// Without this undo, the enable/disable is not recorded.
|
|
Undo.RecordObject(col, "Toggle Collider Enabled");
|
|
// we add the mesh collider, so we want to disable it during collider selection.
|
|
if (!CreatedColliders.Contains(col) && AddedInstanceIDs.Contains(col.GetInstanceID()))
|
|
{
|
|
col.enabled = !colliderSelectionEnabled;
|
|
}
|
|
else
|
|
{
|
|
col.enabled = colliderSelectionEnabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to match the given collider to any collider in the given list.
|
|
/// </summary>
|
|
/// <param name="collider">Collider to match</param>
|
|
/// <param name="colliderList">Collider list to find a match in.</param>
|
|
/// <returns>The matched collider, or null if none found.</returns>
|
|
private Collider TryGetMatchColliderInList(Collider collider, List<Collider> colliderList)
|
|
{
|
|
// dont match by checking physic materials, that causes issues.
|
|
foreach (Collider col in colliderList)
|
|
{
|
|
if (col == null) { continue; }
|
|
if (col.gameObject.name != collider.gameObject.name)
|
|
{
|
|
continue;
|
|
}
|
|
if (col is BoxCollider && collider is BoxCollider)
|
|
{
|
|
BoxCollider c1 = col as BoxCollider;
|
|
BoxCollider c2 = collider as BoxCollider;
|
|
if (c1.center == c2.center && c1.size == c2.size && c1.isTrigger == c2.isTrigger && c1.sharedMaterial == c2.sharedMaterial)
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
else if (col is CapsuleCollider && collider is CapsuleCollider)
|
|
{
|
|
CapsuleCollider c1 = col as CapsuleCollider;
|
|
CapsuleCollider c2 = collider as CapsuleCollider;
|
|
if (c1.center == c2.center && c1.height == c2.height && c1.direction == c2.direction && c1.height == c2.height && c1.isTrigger == c2.isTrigger && c1.sharedMaterial == c2.sharedMaterial)
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
else if (col is SphereCollider && collider is SphereCollider)
|
|
{
|
|
SphereCollider c1 = col as SphereCollider;
|
|
SphereCollider c2 = collider as SphereCollider;
|
|
if (c1.center == c2.center && c1.radius == c2.radius && c1.isTrigger == c2.isTrigger && c1.sharedMaterial == c2.sharedMaterial)
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
else if (col is MeshCollider && collider is MeshCollider)
|
|
{
|
|
if (col.GetInstanceID() == collider.GetInstanceID())
|
|
{
|
|
return col;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks added instance IDs and checks if they are mesh filters.
|
|
/// Re-adds lost meshfilters from Undo-Redos, fixes bug on lost meshfilters on skinned mesh renderers.
|
|
/// </summary>
|
|
public void VerifyMeshFiltersOnUndoRedo()
|
|
{
|
|
foreach (int id in AddedInstanceIDs)
|
|
{
|
|
Object o = EditorUtility.InstanceIDToObject(id);
|
|
if (o == null) { continue; }
|
|
else
|
|
{
|
|
if (o is MeshFilter)
|
|
{
|
|
MeshFilters.Add(o as MeshFilter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void MergeSelectedColliders(CREATE_COLLIDER_TYPE mergeTo, bool removeMergedColliders)
|
|
{
|
|
EasyColliderCreator ecc = new EasyColliderCreator();
|
|
Collider createdCollider = ecc.MergeColliders(SelectedColliders, mergeTo, GetProperties());
|
|
if (removeMergedColliders)
|
|
{
|
|
RemoveSelectedColliders();
|
|
}
|
|
if (createdCollider != null)
|
|
{
|
|
DisableCreatedCollider(createdCollider);
|
|
}
|
|
SelectedColliders.Clear();
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif |