Files
Cielonos/Assets/OtherPlugins/EasyColliderEditor/Scripts/EasyColliderEditor.cs
SoulliesOfficial ef7b479712 initial
2025-11-25 08:19:33 -05:00

3023 lines
108 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>();
public List<EasyColliderData> _VHACDConvertedData = new List<EasyColliderData>();
/// <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;
}
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[]>();
_VHACDConvertedData = new List<EasyColliderData>();
}
/// <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
// clean before another run which re-initializes itself.
// fix for an issue with the newer version of vhacd (although it's not used yet)
if (_VHACD != null)
{
if (!_VHACD.IsComputeFinished())
{
return false;
}
_VHACD.Clean();
}
_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 && parameters.MeshFilters[parameters.CurrentMeshFilter] != null)
{
//TODO: figure out a smart wayt hat communicates that attach to objects are used with separate child meshes
// child object as the attach to with separate child meshes? as that functionality is then the same as the normal child object one?
// \nChild Object: Attach colliders to a child of Attach To field.\nIndividual Child Objects: Each collider is attached to its own child whos parent is a child of the Attach To field.
// so since attach to says it attachs to the thing int he attach to field, we should probably change the behaviour on the IndividualChildObjects thing.
// if (parameters.vhacdResultMethod == VHACD_RESULT_METHOD.AttachTo && parameters.SeparateChildMeshes && !parameters.PerMeshAttachOverride)
// {
// default to using the attach to object, only if it's null so we don't override anything that was already set through previous separated child meshes.
if (parameters.AttachTo == null)
{
parameters.AttachTo = AttachToObject;
}
// if we're overriding to per mesh, use the mesh filters gameobject as the base attach to object.
if (parameters.PerMeshAttachOverride)
{
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.
if (parameters.MeshFilters[parameters.CurrentMeshFilter] != null)
{
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 && parameters.ConvertTo == VHACD_CONVERSION.None)
{
_VHACDMeshes = EasyColliderSaving.CreateAndSaveMeshAssets(_VHACDMeshes, parameters.SavePath, parameters.SaveSuffix);
}
}
else if (parameters.IsCalculationForPreview)
{
// for the preview.
_VHACDMeshes = _VHACD.CreateConvexHullMeshes();
if (parameters.ConvertTo == VHACD_CONVERSION.None)
{
if (_VHACDPreviewResult.ContainsKey(parameters.AttachTo.transform))
{
_VHACDPreviewResult[parameters.AttachTo.transform] = _VHACDPreviewResult[parameters.AttachTo.transform].Concat(_VHACDMeshes).ToArray();
}
else
{
_VHACDPreviewResult.Add(parameters.AttachTo.transform, _VHACDMeshes);
}
}
else
{
EasyColliderCreator convert = new EasyColliderCreator();
foreach (Mesh m in _VHACDMeshes)
{
EasyColliderData ecd = null;
if (parameters.ConvertTo == VHACD_CONVERSION.Boxes)
{
ecd = convert.CalculateBoxLocal(m.vertices.ToList());
}
else if (parameters.ConvertTo == VHACD_CONVERSION.Spheres)
{
ecd = convert.CalculateSphereMinMaxLocal(m.vertices.ToList());
}
else if (parameters.ConvertTo == VHACD_CONVERSION.Capsules)
{
ecd = convert.CalculateCapsuleMinMaxLocal(m.vertices.ToList(), CAPSULE_COLLIDER_METHOD.MinMax);
}
ecd.Matrix = parameters.AttachTo.transform.localToWorldMatrix;
_VHACDConvertedData.Add(ecd);
}
}
}
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)
{
// prevents non-per mesh override duplication of "VHACDColliders" when using separate child meshes
// otherwise each mesh would create another "VHACDColliders" as a child of the original base VHACDColliders object.
if (parameters.AttachTo == null || (parameters.AttachTo != null && !parameters.AttachTo.name.Contains("VHACDColliders")))
{
// 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;
parent.transform.localScale = Vector3.one;
if (ECEPreferences.CopyParentObjectLayer)
{
parent.layer = parameters.AttachTo.layer;
}
else
{
parent.layer = GameObjectLayerOverride;
}
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;
child.transform.localScale = Vector3.one;
if (ECEPreferences.CopyParentObjectLayer)
{
child.layer = parameters.AttachTo.layer;
}
else
{
child.layer = GameObjectLayerOverride;
}
attachTo = child;
Undo.RegisterCreatedObjectUndo(child, "Create VHACD Collider");
}
if (parameters.ConvertTo == VHACD_CONVERSION.None)
{
// create a convex mesh collider.
MeshCollider mc = ecc.CreateConvexMeshCollider(_VHACDMeshes[i], attachTo, GetProperties());
CreatedColliders.Add(mc);
VHACDCreatedColliders.Add(mc);
AddedColliderIDs.Add(mc.GetInstanceID());
}
else if (parameters.ConvertTo == VHACD_CONVERSION.Boxes)
{
EasyColliderProperties p = GetProperties();
p.AttachTo = attachTo;
BoxCollider bc = ecc.CreateBoxCollider(_VHACDMeshes[i].vertices.ToList(), p, true);
CreatedColliders.Add(bc);
AddedColliderIDs.Add(bc.GetInstanceID());
}
else if (parameters.ConvertTo == VHACD_CONVERSION.Spheres)
{
EasyColliderProperties p = GetProperties();
p.AttachTo = attachTo;
SphereCollider sc = ecc.CreateSphereCollider_MinMax(_VHACDMeshes[i].vertices.ToList(), p, true);
CreatedColliders.Add(sc);
AddedColliderIDs.Add(sc.GetInstanceID());
}
else if (parameters.ConvertTo == VHACD_CONVERSION.Capsules)
{
EasyColliderProperties p = GetProperties();
p.AttachTo = attachTo;
CapsuleCollider cc = ecc.CreateCapsuleCollider_MinMax(_VHACDMeshes[i].vertices.ToList(), p, CAPSULE_COLLIDER_METHOD.MinMax, true);
CreatedColliders.Add(cc);
AddedColliderIDs.Add(cc.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 filter.
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];
Vector3[] normals = new Vector3[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)
{
if (mf == null) continue;
// 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;
normals = mf.sharedMesh.normals;
// 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))
{
// Debug.Log("Full triangle.");
// add verts in world-space to convert later.
Vector3 v0 = vertices[triangles[i]];
Vector3 v1 = vertices[triangles[i + 1]];
Vector3 v2 = vertices[triangles[i + 2]];
// if it's been used before.
if (ecvVertIndexDictionary.ContainsKey(ecv0))
{
// Debug.Log("Vertex already in dictionary");
// 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);
// int count = selectedVerticesSet.Count;
// remove unused vertices that are full triangles.
selectedVerticesSet.ExceptWith(usedVerticesSet);
// Debug.Log("Remaining vertices count:" + selectedVerticesSet.Count + " Total At Start:" + count);
// list of reamining verts (vertices that are selected that arent' a part of at least 1 full triangle.)
List<Vector3> remainingVertsLocal = 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)
{
remainingVertsLocal.Add(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 < 3 && remainingVertsLocal.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 < remainingVertsLocal.Count - 3; i++)
{
// d0 -> d1
float d01 = Vector3.Distance(remainingVertsLocal[i], remainingVertsLocal[i + 1]);
// d1 -> d2
float d12 = Vector3.Distance(remainingVertsLocal[i + 1], remainingVertsLocal[i + 2]);
// d2 -> d0
float d20 = Vector3.Distance(remainingVertsLocal[i + 2], remainingVertsLocal[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(remainingVertsLocal[vertIndexs[0]]);
verticesList.Add(remainingVertsLocal[vertIndexs[1]]);
verticesList.Add(remainingVertsLocal[vertIndexs[2]]);
// add the triangle
trianglesList.Add(verticesList.Count - 3);
trianglesList.Add(verticesList.Count - 2);
trianglesList.Add(verticesList.Count - 1);
}
// add the remaining left-over non-full triangle vertices.
float minDistance = Mathf.Infinity;
int vertIndex0, vertIndex1 = vertIndex0 = -1;
foreach (Vector3 pos in remainingVertsLocal)
{
// 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);
}
}
// extrude selected vertices.
if (parameters.NormalExtrudeMultiplier > 0.0f)
{
int vCount = verticesList.Count;
Dictionary<int, int> vertexIndexToTriangleIndexDictionary = new Dictionary<int, int>();
int count = trianglesList.Count;
for (int i = 0; i < count; i++)
{
if (vertexIndexToTriangleIndexDictionary.ContainsKey(trianglesList[i]))
{
trianglesList.Add(vertexIndexToTriangleIndexDictionary[trianglesList[i]]);
}
else
{
int nCount = 0;
Vector3 p = verticesList[trianglesList[i]];
Vector3 n = Vector3.zero;
for (int j = 0; j < vertices.Length; j++)
{
if (vertices[j] == p)
{
nCount++;
n += normals[j];
}
}
n.Normalize();
trianglesList.Add(verticesList.Count);
vertexIndexToTriangleIndexDictionary.Add(trianglesList[i], verticesList.Count);
verticesList.Add(p + n * parameters.NormalExtrudeMultiplier);
}
}
}
// covert from local mesh space to world then
// convert verts from world to attach to local space.
Transform attachTo = parameters.AttachTo.transform;
for (int i = 0; i < verticesList.Count; i++)
{
verticesList[i] = t.TransformPoint(verticesList[i]);
verticesList[i] = attachTo.InverseTransformPoint(verticesList[i]);
}
// create and return the mesh for use in vhacd.
Mesh m = new Mesh();
#if (UNITY_2017_3_OR_NEWER)
m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
#endif
m.vertices = verticesList.ToArray();
m.triangles = trianglesList.ToArray();
createdMeshList.Add(m);
}
Mesh result = new Mesh();
// use 32 bit index format for high # of verts.
#if (UNITY_2017_3_OR_NEWER)
result.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
#endif
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; }
}
/// <summary>
/// Are we automatically including child skinned meshes when include child meshes is enabled?
/// </summary>
public bool AutoIncludeChildSkinnedMeshes { get { return ECEPreferences.AutoIncludeChildSkinnedMeshes; } }
[SerializeField]
private bool _ColliderSelectEnabled;
/// <summary>
/// Is Collider Selection Enabled? Toggles colliders on and off when changed.
/// </summary>
public bool ColliderSelectEnabled
{
get { return _ColliderSelectEnabled; }
set
{
_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());
}
}
}
[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]
/// <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;
public bool DisplayMeshVertices
{
get { return ECEPreferences.DisplayAllVertices; }
}
private static EasyColliderPreferences _ECEPreferences;
public static EasyColliderPreferences ECEPreferences
{
get
{
if (_ECEPreferences == null)
{
_ECEPreferences = EasyColliderPreferences.Preferences;
}
return _ECEPreferences;
}
}
[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();
}
}
}
[SerializeField]
private bool _IncludeChildMeshes;
/// <summary>
/// Are we including child meshes for vertex selection?
/// </summary>
public bool IncludeChildMeshes
{
get { return _IncludeChildMeshes; }
set
{
if (_IncludeChildMeshes != value)
{
OnlyDeselectableColliders.Clear();
// lets store things.
// List<EasyColliderVertex> selectedVertsBeforeChange = new List<EasyColliderVertex>(SelectedVertices);
//HashSet<EasyColliderVertex> selectedNonVerts = new HashSet<EasyColliderVertex>(SelectedNonVerticesSet);
HashSet<Collider> selectedChildColliders = new HashSet<Collider>(SelectedColliders);
if (!value && SelectedGameObject != null)
{
selectedChildColliders.ExceptWith(SelectedGameObject.GetComponents<Collider>());
}
// why do we do this?
GameObject selected = SelectedGameObject;
GameObject attach = AttachToObject;
SelectedGameObject = null;
_IncludeChildMeshes = value;
//OnIncludeChildMeshesChanged(value);
SelectedGameObject = selected;
AttachToObject = attach;
/* foreach(var v in selectedVertsBeforeChange)
{
SelectVertex(v, true);
}*/
foreach (var c in selectedChildColliders)
{
SelectCollider(c);
OnlyDeselectableColliders.Add(c);
}
}
// _IncludeChildMeshes = value;
// SetupChildObjects(value);
// CalculateDensity();
if (value == false)
{
// CleanChildSelectedVertices();
}
}
}
public HashSet<Collider> OnlyDeselectableColliders = new HashSet<Collider>();
/* void OnIncludeChildMeshesChanged(bool value)
{
HashSet<MeshFilter> rootMeshFilters = new HashSet<MeshFilter>();
HashSet<MeshFilter> childMeshFilters = new HashSet<MeshFilter>();
rootMeshFilters.UnionWith(SelectedGameObject.GetComponents<MeshFilter>());
childMeshFilters.UnionWith(SelectedGameObject.GetComponentsInChildren<MeshFilter>());
childMeshFilters.ExceptWith
if (!value)
{
// CleanUpObject()
}
}*/
[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; }
}
[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;
}
}
#if (UNITY_6000_0_OR_NEWER)
[SerializeField]
private PhysicsMaterial _PhysicMaterial;
/// <summary>
/// Physic material to add to colliders upon creation.
/// </summary>
public PhysicsMaterial PhysicMaterial { get { return _PhysicMaterial; } set { _PhysicMaterial = value; } }
#else
[SerializeField]
private PhysicMaterial _PhysicMaterial;
/// <summary>
/// Physic material to add to colliders upon creation.
/// </summary>
public PhysicMaterial PhysicMaterial { get { return _PhysicMaterial; } set { _PhysicMaterial = value; } }
#endif
[SerializeField]
private List<Collider> _RaycastableColliders;
public List<Collider> RaycastableColliders
{
get
{
if (_RaycastableColliders == null)
{
_RaycastableColliders = new List<Collider>();
}
return _RaycastableColliders;
}
set { _RaycastableColliders = value; }
}
/// <summary>
/// Method we use to render points with. Either using a shader or gizmos.
/// </summary>
private RENDER_POINT_TYPE RenderPointType
{
get { return ECEPreferences.RenderPointType; }
}
public void ChangeRenderPointType(RENDER_POINT_TYPE value)
{
// add or remove one if the value is changing and it's already added
if (value != RENDER_POINT_TYPE.SHADER && Compute != null)
{
TryDestroyComponent(Compute);
}
if (value != RENDER_POINT_TYPE.GIZMOS && Gizmos != null)
{
TryDestroyComponent(Gizmos);
}
// add the new component if needed.
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());
}
}
[SerializeField]
private int _GameObjectLayerOverride;
/// <summary>
/// Layer to set on rotated collider's gameobject if not using selected gameobject's layer.
/// </summary>
public int GameObjectLayerOverride { get { return _GameObjectLayerOverride; } set { _GameObjectLayerOverride = 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);
// log a warning if there is a kinematic rigidbody on a parent, we dont track it because it can get lost more easily than child-rigidbodies.
// so just alert the user that vertices would not be able to be selected.
Rigidbody[] rbs = _SelectedGameObject.GetComponentsInParent<Rigidbody>();
foreach (Rigidbody rb in rbs)
{
if (rb.gameObject != _SelectedGameObject && !rb.isKinematic)
{
Debug.LogWarning("EasyColliderEditor: A parent (" + rb.gameObject.name + ") of the selected object has a non-kinematic rigidbody, you may not be able to select vertices while it is marked as non-kinematic.", rb.gameObject);
}
}
}
}
_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; }
}
public HashSet<EasyColliderVertex> SelectedNonVerticesSet = new HashSet<EasyColliderVertex>();
//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] private List<Vector3> _TransformLossyScales;
public List<Vector3> TransformLossyScales
{
get
{
if (_TransformLossyScales == null)
{
_TransformLossyScales = new List<Vector3>();
}
return _TransformLossyScales;
}
set { _TransformLossyScales = 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>
/// 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)
{
#if (UNITY_6000_3_OR_NEWER)
Object o = EditorUtility.EntityIdToObject(id);
#else
Object o = EditorUtility.InstanceIDToObject(id);
#endif
if (o != null)
{
if (intoPlayMode)
{
if (o is Component)
{
TryDestroyComponent((Component)o);
}
else
{
TryDestroyObject((GameObject)o);
}
}
else
{
if (o is Component)
{
TryDestroyComponent((Component)o);
}
else
{
TryDestroyObject((GameObject)o);
}
}
}
}
foreach (Rigidbody rb in NonKinematicRigidbodies)
{
if (rb != null)
{
rb.isKinematic = false;
}
}
// force cleanup gizmos and compute.
if (Gizmos != null)
{
TryDestroyComponent(Gizmos);
}
if (Compute != null)
{
TryDestroyComponent(Compute);
}
if (go != null)
{
// lets be safe and search for additional compute and gizmos.
EasyColliderGizmos[] extraGizmos = go.GetComponentsInChildren<EasyColliderGizmos>();
for (int i = 0; i < extraGizmos.Length; i++)
{
TryDestroyComponent(extraGizmos[i]);
}
EasyColliderCompute[] extraComputes = go.GetComponentsInChildren<EasyColliderCompute>();
for (int i = 0; i < extraComputes.Length; i++)
{
TryDestroyComponent(extraComputes[i]);
}
}
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>();
LastSelectedVertices = new List<EasyColliderVertex>();
MeshFilters = new List<MeshFilter>();
NonKinematicRigidbodies = new List<Rigidbody>();
OnlyDeselectableColliders = new HashSet<Collider>();
RaycastableColliders = new List<Collider>();
SelectedColliders = new List<Collider>();
SelectedVertices = new List<EasyColliderVertex>();
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
SelectedNonVerticesSet = new HashSet<EasyColliderVertex>();
_SkinnedMeshFilterPairs = new Dictionary<SkinnedMeshRenderer, MeshFilter>();
BoneTransforms = new List<Transform>();
_selectedBones = new List<Transform>();
_selectedBoneVertices = new List<SerializableBoneVertexList>();
LastSelectedBone = null;
#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>();
this.SelectedNonVerticesSet = 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(true), GetProperties(orientation));
if (createdCollider != null)
{
DisableCreatedCollider(createdCollider);
}
ClearSelectedVertices();
}
/// <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(true), GetProperties(orientation), method);
break;
case CAPSULE_COLLIDER_METHOD.BestFit:
createdCollider = ecc.CreateCapsuleCollider_BestFit(GetWorldVertices(true), GetProperties(orientation));
break;
}
if (createdCollider != null)
{
DisableCreatedCollider(createdCollider);
}
ClearSelectedVertices();
}
public void CreateCylinderCollider()
{
EasyColliderCreator ecc = new EasyColliderCreator();
// convert the selected world points to local points that represent a cylinder.
MeshColliderData data = ecc.CalculateCylinderCollider(GetWorldVertices(true), AttachToObject.transform);
if (ECEPreferences.SaveConvexHullAsAsset)
{
EasyColliderSaving.CreateAndSaveMeshAsset(data.ConvexMesh, SelectedGameObject);
}
// generate the hull from the cylinder points.
MeshCollider createdCollider = ecc.CreateConvexMeshCollider(data.ConvexMesh, AttachToObject, GetProperties());
if (createdCollider != null)
{
DisableCreatedCollider(createdCollider);
}
ClearSelectedVertices();
}
/// <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);
if (mesh == null)
{
Debug.LogWarning("EasyColliderEditor: Unable to create a valid convex mesh collider from the selected vertices, likely because all selected vertices are coplanar.");
}
else
{
EasyColliderCreator ecc = new EasyColliderCreator();
MeshCollider createdCollider = ecc.CreateConvexMeshCollider(mesh, AttachToObject, GetProperties());
if (createdCollider != null)
{
DisableCreatedCollider(createdCollider);
}
ClearSelectedVertices();
}
}
/// <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(true), AttachToObject, false, SelectedGameObject);
}
else
{
return new EasyColliderCreator().CreateMesh_Messy(GetWorldVertices(true), AttachToObject, SelectedGameObject);
}
}
public void CreateRotatedAndDuplicatedColliders(CREATE_COLLIDER_TYPE type)
{
EasyColliderCreator ecc = new EasyColliderCreator();
List<Collider> createdColliders = ecc.CreateRotateAndDuplicateColliders(type, GetWorldVertices(true), GetProperties());
foreach (Collider c in createdColliders)
{
if (c != null)
{
DisableCreatedCollider(c);
}
}
ClearSelectedVertices();
}
/// <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(true), GetProperties());
break;
case SPHERE_COLLIDER_METHOD.Distance:
createdCollider = ecc.CreateSphereCollider_Distance(GetWorldVertices(true), GetProperties());
break;
case SPHERE_COLLIDER_METHOD.MinMax:
createdCollider = ecc.CreateSphereCollider_MinMax(GetWorldVertices(true), GetProperties());
break;
}
if (createdCollider != null)
{
DisableCreatedCollider(createdCollider);
}
ClearSelectedVertices();
}
/// <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());
}
/// <summary>
/// Gets all colliders on parent + children if including children.
/// </summary>
/// <returns>Array of all colliders</returns>
private Collider[] GetAllColliders()
{
if (SelectedGameObject != null)
{
HashSet<Collider> selectedColliders = new HashSet<Collider>();
if (IncludeChildMeshes)
{
selectedColliders.UnionWith(SelectedGameObject.GetComponentsInChildren<Collider>());
selectedColliders.UnionWith(AttachToObject.GetComponentsInChildren<Collider>());
}
else
{
selectedColliders.UnionWith(SelectedGameObject.GetComponents<Collider>());
selectedColliders.UnionWith(AttachToObject.GetComponents<Collider>());
// "child meshes" toggle would ennable this, but collider holders don't have meshes, but generally should be selectable.
for (int i = 0; i < SelectedGameObject.transform.childCount; i++)
{
Transform t = SelectedGameObject.transform.GetChild(i);
// special names are Rotated X (x = box, capsule), VHACDCollider, and EasyColliderHolder
if ((t.name.Contains("Rotated") && t.name.Contains("Collider")) || t.name.Contains("VHACDCollider") || t.name.Contains("EasyColliderHolder"))
{
// collider holders can also hold rotated colliders, so get children as well.
selectedColliders.UnionWith(t.gameObject.GetComponentsInChildren<Collider>());
}
}
}
selectedColliders.ExceptWith(RaycastableColliders);
// Allow selecting colliders from selected gameobject, and it's children, and attach to / it's children.
return selectedColliders.ToArray();
}
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));
HasSkinnedMeshRenderer = true;
}
}
}
return meshFilters;
}
#if (UNITY_2022_2_OR_NEWER)
public LayerMask IncludeLayers;
public LayerMask ExcludeLayers;
public int LayerOverridePriority;
public bool ProvidesContacts;
#endif
/// <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 = ECEPreferences.CopyParentObjectLayer ? AttachToObject.layer : GameObjectLayerOverride;
}
else
{
ecp.Layer = GameObjectLayerOverride;
}
ecp.AttachTo = AttachToObject;
ecp.Orientation = orientation;
//overrides added in 2022.2.
#if (UNITY_2022_2_OR_NEWER)
ecp.LayerOverridePriority = LayerOverridePriority;
ecp.IncludeLayers = IncludeLayers;
ecp.ExcludeLayers = ExcludeLayers;
ecp.ProvidesContacts = ProvidesContacts;
#endif
return ecp;
}
/// <summary>
/// Gets all colliders that can be converted for DOTS (ie anything but the mesh colliders added for selection process)
/// </summary>
/// <returns></returns>
public Collider[] GetConvertibleColliders()
{
Collider[] colliders = GetAllColliders();
for (int i = 0; i < colliders.Length; i++)
{
if (AddedInstanceIDs.Contains(colliders[i].GetInstanceID()))
{
colliders[i] = null;
}
}
return colliders;
}
/// <summary>
/// Gets a list of the selected vertices in world space positions.
/// </summary>
/// <returns>List of world space positions.</returns>
public List<Vector3> GetWorldVertices(bool extrude = false)
{
if (!extrude)
{
// just return the world verts.
List<Vector3> verts = new List<Vector3>(SelectedVertices.Count);
foreach (EasyColliderVertex ecv in SelectedVertices)
{
if (ecv.T == null) continue;
verts.Add(ecv.T.TransformPoint(ecv.LocalPosition));
}
return verts;
}
bool extrudeOut = (extrude && ECEPreferences.VertexNormalOffsetType != NORMAL_OFFSET.In && ECEPreferences.VertexNormalOffset != 0);
bool extrudeIn = (extrude && ECEPreferences.VertexNormalOffsetType != NORMAL_OFFSET.Out && ECEPreferences.VertexNormalInset != 0);
// create list with enough space for all the vertices
int count = ((extrudeIn && extrudeOut) ? SelectedVertices.Count * 3 : SelectedVertices.Count) + (extrudeOut ? SelectedVertices.Count : 0) + (extrudeIn ? SelectedVertices.Count : 0);
List<Vector3> worldVertices = new List<Vector3>(count);
// order of first 3 can matter for rotated colliders, so need to add the actual selected vertices first if needed.
if ((!extrudeOut && !extrudeIn) || ECEPreferences.VertexNormalOffsetType == NORMAL_OFFSET.Both)
{
// both? add selected vertices, otherwise only offset the
foreach (EasyColliderVertex ecv in SelectedVertices)
{
if (ecv.T == null) continue;
worldVertices.Add(ecv.T.TransformPoint(ecv.LocalPosition));
}
}
foreach (EasyColliderVertex ecv in SelectedVertices)
{
if (ecv.T == null) continue;
if (extrudeOut)
{
worldVertices.Add(ecv.T.TransformPoint(ecv.LocalPosition + ecv.Normal * ECEPreferences.VertexNormalOffset));
}
if (extrudeIn)
{
worldVertices.Add(ecv.T.TransformPoint(ecv.LocalPosition - ecv.Normal * ECEPreferences.VertexNormalInset));
}
}
return worldVertices;
}
public List<Vector3> GetNormals()
{
List<Vector3> normals = new List<Vector3>(SelectedVertices.Count);
foreach (EasyColliderVertex ecv in SelectedVertices)
{
if (ecv.T == null) continue;
normals.Add(ecv.Normal);
}
return normals;
}
/// <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.
HashSet<EasyColliderVertex> newVertices = new HashSet<EasyColliderVertex>(newSelectedVertices.Where(value => !SelectedVerticesSet.Contains(value)));
SelectVertices(newVertices);
}
/// <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)
|| !TransformLossyScales.Contains(t.lossyScale))
{
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);
TransformLossyScales.Add(t.lossyScale);
}
}
}
return hasMoved;
}
/// <summary>
/// Inverts the currently selected vertices
/// </summary>
public void InvertSelectedVertices()
{
// just a hash set to add all the vertices to.
HashSet<EasyColliderVertex> invs = new HashSet<EasyColliderVertex>();
// Variables to hold values
Vector3[] vertices;
Transform transform;
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++)
{
invs.Add(new EasyColliderVertex(transform, vertices[j]));
}
}
}
invs.UnionWith(SelectedVertices);
// select all the vertices (will deselect selected, and select unselected)
SelectVertices(invs);
}
/// <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 (OnlyDeselectableColliders.Contains(collider)) return true;
if (GetAllColliders().Contains(collider))
{
// we don't want to allow selecting mesh colliders that we've added for vertex selection.
// they wouldn't be actually removed, but it makes selecting colliders more difficult if people can.
if (AddedInstanceIDs.Contains(collider.GetInstanceID())) return false;
return true;
}
return false;
}
[SerializeField] private List<EasyColliderVertex> _SerializedSelectedNonVertexSet = new List<EasyColliderVertex>();
[SerializeField] private List<SkinnedMeshRenderer> _SerializedSkinnedMeshRenderers = new List<SkinnedMeshRenderer>();
[SerializeField] private List<MeshFilter> _SerializedSkinnedMeshMeshFilters = new List<MeshFilter>();
/// <summary>
/// Called after deserializing, used to deserilize our serializable list of selected points back into the hashset.
/// Otherwise saving scripts and stuff that reloads domain will break things.
/// </summary>
public void OnAfterDeserialize()
{
// Deserialize our hashsets.
if (_SerializedSelectedVertexSet.Count > 0)
{
SelectedVerticesSet = new HashSet<EasyColliderVertex>(_SerializedSelectedVertexSet);
}
else
{
SelectedVerticesSet = new HashSet<EasyColliderVertex>();
}
if (_SerializedSelectedNonVertexSet.Count > 0)
{
SelectedNonVerticesSet = new HashSet<EasyColliderVertex>(_SerializedSelectedNonVertexSet);
}
else
{
SelectedNonVerticesSet = new HashSet<EasyColliderVertex>();
}
if (_SerializedSkinnedMeshRenderers.Count > 0)
{
_SkinnedMeshFilterPairs = new Dictionary<SkinnedMeshRenderer, MeshFilter>();
for (int i = 0; i < _SerializedSkinnedMeshRenderers.Count; i++)
{
_SkinnedMeshFilterPairs.Add(_SerializedSkinnedMeshRenderers[i], _SerializedSkinnedMeshMeshFilters[i]);
}
}
}
/// <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();
if (_SerializedSelectedNonVertexSet == null)
{
_SerializedSelectedNonVertexSet = new List<EasyColliderVertex>();
}
_SerializedSelectedNonVertexSet = SelectedNonVerticesSet.ToList();
if (_SerializedSkinnedMeshMeshFilters == null)
{
_SerializedSkinnedMeshMeshFilters = new List<MeshFilter>();
}
if (_SerializedSkinnedMeshRenderers == null)
{
_SerializedSkinnedMeshRenderers = new List<SkinnedMeshRenderer>();
}
_SerializedSkinnedMeshMeshFilters.Clear();
_SerializedSkinnedMeshRenderers.Clear();
foreach (var kvp in _SkinnedMeshFilterPairs)
{
_SerializedSkinnedMeshRenderers.Add(kvp.Key);
_SerializedSkinnedMeshMeshFilters.Add(kvp.Value);
}
}
/// <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);
TryDestroyObject(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;
CreatedColliders.Remove(col);
if (col.transform.childCount == 0 && ((col.gameObject.name.Contains("Rotated") && col.gameObject.name.Contains("Collider")) || col.gameObject.name.Contains("VHACDCollider") || col.gameObject.name.Contains("EasyColliderHolder")))
{ // 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)
{
Transform parent = null;
if (col.transform.parent != null && col.transform.parent.name.Contains("EasyColliderHolder"))
{
parent = col.transform.parent;
}
Undo.RecordObject(col.gameObject, "Remove collider");
Component[] c = col.GetComponents<Component>();
if (c.Length == 2)
{ // don't destroy the collider holder that a user has attached other components to.
TryDestroyObject(col.gameObject);
}
// can remove components from prefab instances, but not the object itself.
TryDestroyComponent(col);
if (parent != null && parent.childCount == 0)
{
c = parent.GetComponents<Component>();
if (c.Length == 1)
{ // don't remove collider holder parent that users have attached their own components to.
// Undo.DestroyObjectImmediate(parent.gameObject);
TryDestroyObject(parent.gameObject);
}
}
}
else
{
// just remove the selected collider.
TryDestroyComponent(col);
}
}
else // has children, not a rotated / collider holder or vhacd collider.
{
Undo.RecordObject(col, "Remove collider");
TryDestroyComponent(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];
Vector3[] normals = new Vector3[0];
Transform t = SelectedVertices[SelectedVertices.Count - 1].T;
foreach (MeshFilter filter in MeshFilters)
{
if (filter == null) continue;
if (filter.transform == t)
{
vertices = filter.sharedMesh.vertices;
triangles = filter.sharedMesh.triangles;
normals = filter.sharedMesh.normals;
}
}
// 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);
}
}
// create a hash set from the verts as we need it for the select vertices method which also handles normal smoothing.
HashSet<EasyColliderVertex> newVerts = new HashSet<EasyColliderVertex>(newVerticesToAdd);
// except with the current selected vertices so they dont get deselected when doing a ring.
newVerts.ExceptWith(SelectedVerticesSet);
// select the vertices.
SelectVertices(newVerts);
}
/// <summary>
/// Selects or deselects a collider
/// </summary>
/// <param name="collider">collider to select or deselect.</param>
public void SelectCollider(Collider collider)
{
// dont try to select a null collider.
if (collider == null) return;
if (SelectedColliders.Contains(collider))
{
SelectedColliders.Remove(collider);
OnlyDeselectableColliders.Remove(collider);
}
else
{
SelectedColliders.Add(collider);
}
}
public void DeselectAllColliders()
{
SelectedColliders = new List<Collider>();
}
public void InvertSelection()
{
HashSet<Collider> set = new HashSet<Collider>();
set.UnionWith(GetAllColliders());
set.ExceptWith(SelectedColliders);
set.RemoveWhere(x => x.enabled == false || AddedInstanceIDs.Contains(x.GetInstanceID()));
SelectedColliders = set.ToList();
}
/// <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.
/// Also handles smoothing of the selected normals.
/// </summary>
/// <param name="vertices">Set of vertices</param>
public void SelectVertices(HashSet<EasyColliderVertex> vertices, bool calculateNormals = true)
{
SelectedNonVerticesSet.ExceptWith(vertices);
// removes selected vertices that are in the vertices hashset. (deselects already selected vertices)
List<EasyColliderVertex> stillSelectedVertices = SelectedVertices.Where((value, index) => !vertices.Contains(value)).ToList();
// adds vertices in the vertices set that aren't already selected (selects unselected vertices)
List<EasyColliderVertex> newlySelectedVertices = vertices.Where((value) => !SelectedVerticesSet.Contains(value)).ToList();
// Debug.Log("Select:" + vertices.Count + " Still selected:" + stillSelectedVertices.Count + " New:" + newlySelectedVertices.Count);
if (calculateNormals)
{
// could calculate smooth normals before removing vertices, as that would be more consistently slow.
// but then it's the same speed of inverting from all to none.
HashSet<EasyColliderVertex> newSelectedSet = new HashSet<EasyColliderVertex>();
newSelectedSet.UnionWith(newlySelectedVertices);
// calculates the smoothed normals for the selected vertices.
Vector3[] verts = new Vector3[0];
Vector3[] normals = new Vector3[0];
MeshFilter mf = null;
// dictionary of vert to list of normals found.
Dictionary<EasyColliderVertex, List<Vector3>> modified = new Dictionary<EasyColliderVertex, List<Vector3>>();
for (int i = 0; i < MeshFilters.Count; i++)
{
mf = MeshFilters[i];
if (mf == null || mf.sharedMesh == null) continue;
verts = MeshFilters[i].sharedMesh.vertices;
normals = MeshFilters[i].sharedMesh.normals;
Transform t = mf.transform;
for (int j = 0; j < verts.Length; j++)
{
EasyColliderVertex v = new EasyColliderVertex(t, verts[j]);
if (modified.ContainsKey(v))
{
// add the normals.
modified[v].Add(normals[j]);
}
else if (newSelectedSet.Contains(v))
{
// new vertex, create a list of normals.
modified.Add(v, new List<Vector3>() { normals[j] });
}
}
}
// to re-add all the vertices after calculating the normals.
newSelectedSet.Clear();
foreach (var item in modified)
{
Vector3 v = item.Value.Aggregate(new Vector3(0, 0, 0), (cur, val) => cur + val);
v.Normalize();
EasyColliderVertex ecv = new EasyColliderVertex(item.Key);
ecv.Normal = v;
newSelectedSet.Add(ecv);
}
newlySelectedVertices = newSelectedSet.ToList();
}
// last selected are the newly selected vertices.
LastSelectedVertices.Clear();
LastSelectedVertices = newlySelectedVertices;
// Combine the lists for the all currently selected vertices.
stillSelectedVertices.AddRange(newlySelectedVertices);
SelectedVertices = stillSelectedVertices;
// clear the selected vertices set
SelectedVerticesSet.Clear();
// add all the currently selected vertices to it with a union.
SelectedVerticesSet.UnionWith(SelectedVertices);
}
public void EditColliders(List<Collider> colliders, bool remove = true)
{
SelectVerticesFromColliders(colliders);
if (remove)
{
RemoveSelectedColliders();
}
ColliderSelectEnabled = false;
VertexSelectEnabled = true;
}
/// <summary>
/// Converts a list of colliders to EasyColliderVertex's and selects them.
/// </summary>
/// <param name="colliders"></param>
public void SelectVerticesFromColliders(List<Collider> colliders)
{
EasyColliderCreator ecc = new EasyColliderCreator();
List<Vector3> worldVertices = ecc.GetWorldVertsForColliders(colliders);
HashSet<Vector3> worldVerticesSet = new HashSet<Vector3>();
worldVerticesSet.UnionWith(worldVertices);
HashSet<EasyColliderVertex> newSelected = new HashSet<EasyColliderVertex>();
Vector3[] verts;
foreach (MeshFilter mf in _MeshFilters)
{
if (mf == null) continue;
verts = mf.sharedMesh.vertices;
Transform tra = mf.transform;
foreach (Vector3 p in verts)
{
Vector3 wp = tra.TransformPoint(p);
if (worldVerticesSet.Contains(wp))
{
worldVerticesSet.Remove(wp);
newSelected.Add(new EasyColliderVertex(tra, p));
}
}
}
Transform t = SelectedGameObject.transform;
foreach (Vector3 v in worldVerticesSet)
{
EasyColliderVertex ecv = new EasyColliderVertex(t, t.InverseTransformPoint(v));
newSelected.Add(ecv);
}
SelectVertices(newSelected, false);
}
/// <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, bool isVertex)
{
Vector3 normal = Vector3.zero;
int count = 0;
// calculate smoothed normal.
foreach (MeshFilter mf in MeshFilters)
{
if (mf == null || mf.transform != ecv.T || mf.sharedMesh == null) continue;
Vector3[] vertices = mf.sharedMesh.vertices;
Vector3[] normals = mf.sharedMesh.normals;
for (int i = 0; i < vertices.Length; i++)
{
if (vertices[i] == ecv.LocalPosition)
{
normal += normals[i];
count++;
}
}
}
ecv.Normal = normal.normalized;
if (SelectedVerticesSet.Remove(ecv))
{
// Debug.Log("Removed.");
if (!isVertex)
{
SelectedNonVerticesSet.Remove(ecv);
}
SelectedVertices.Remove(ecv);
return false;
}
else
{
// Debug.Log("Not removed.");
LastSelectedVertices = new List<EasyColliderVertex>();
if (!isVertex)
{
SelectedNonVerticesSet.Add(ecv);
}
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;
}
if (Gizmos != null)
{
Gizmos.DensityScale = Gizmos.UseFixedGizmoScale ? DensityScale * 7.5f : DensityScale;
}
}
/// <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;
// get either parent + children or just parent rigidbodies & colliders.
if (IncludeChildMeshes)
{
rigidbodies = parent.GetComponentsInChildren<Rigidbody>();
}
else
{
rigidbodies = parent.GetComponents<Rigidbody>();
}
// 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);
}
}
// 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);
}
}
}
void BakeSkinnedMesh(SkinnedMeshRenderer skinnedMesh, Mesh m)
{
// neither of these work properly 100% of the time, so need to fix skinned mesh baking manually at some point.
// #if (UNITY_2020_2_OR_NEWER)
// skinnedMesh.BakeMesh(m, true);
// #else
skinnedMesh.BakeMesh(m);
// #endif
}
/// <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>();
//prevents duplication of un-needed mesh filters.
if (smr.transform.childCount > 0)
{
for (int i = 0; i < smr.transform.childCount; i++)
{
if (smr.transform.GetChild(i).name == "Scaled Mesh Filter (Temporary)")
{
filter = smr.transform.GetChild(i).GetComponent<MeshFilter>();
if (filter != null)
{
break;
}
}
}
}
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)
{
if (!_SkinnedMeshFilterPairs.ContainsKey(smr))
{
_SkinnedMeshFilterPairs.Add(smr, filter);
AddSkinnedMeshBoneTransforms(smr);
}
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);
BakeSkinnedMesh(smr, 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;
}
private void TryDestroyObject(GameObject go, bool undo = true)
{
#if (UNITY_2018_3_OR_NEWER)
if (!PrefabUtility.IsPartOfAnyPrefab(go))
{
if (undo)
{
Undo.DestroyObjectImmediate(go);
}
else
{
DestroyImmediate(go);
}
}
#else
if (undo)
{
Undo.DestroyObjectImmediate(go);
}
else
{
DestroyImmediate(go);
}
#endif
}
private void TryDestroyComponent(Component component)
{
if (component != null)
{
Undo.DestroyObjectImmediate(component);
}
}
/// <summary>
/// Checks added instance IDs and checks if they are mesh filters, also checks if any meshfilters have been deleted
/// Re-adds lost meshfilters from Undo-Redos
/// </summary>
public void VerifyMeshFiltersOnUndoRedo()
{
// fixes bug on lost mesh filter with skinned mesh renderer
foreach (int id in AddedInstanceIDs)
{
#if (UNITY_6000_3_OR_NEWER)
Object o = EditorUtility.EntityIdToObject(id);
#else
Object o = EditorUtility.InstanceIDToObject(id);
#endif
if (o == null) { continue; }
else
{
if (o is MeshFilter)
{
MeshFilters.Add(o as MeshFilter);
}
}
}
// fix for losing a mesh filter when child meshes are enabled
// one is deleted, an operation is done, and undo-redo is done to the point of pre-deletion.
// (making sure the count is the same prevents the mesh filter from being permanently lost)
bool hasNullMeshFilter = false;
foreach (MeshFilter mf in MeshFilters)
{
if (mf == null)
{
hasNullMeshFilter = true;
}
}
if (hasNullMeshFilter)
{
List<MeshFilter> mfs = GetMeshFilters(SelectedGameObject);
if (mfs != null && mfs.Count == MeshFilters.Count)
{
MeshFilters = mfs;
}
}
}
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();
}
public bool SetAttachToOnBoneChange = true;
public bool CleanVerticesOnBoneChange = false;
public float SelectedBoneWeight = 0.5f;
public Transform LastSelectedBone;
public List<Transform> BoneTransforms = new List<Transform>();
[System.Serializable]
/// <summary>
/// lets us serialize the vertices selected per bone.
/// </summary>
private class SerializableBoneVertexList
{
public List<EasyColliderVertex> SelectedVertices = new List<EasyColliderVertex>();
}
[SerializeField] List<Transform> _selectedBones = new List<Transform>();
[SerializeField] List<SerializableBoneVertexList> _selectedBoneVertices = new List<SerializableBoneVertexList>();
Dictionary<SkinnedMeshRenderer, MeshFilter> _SkinnedMeshFilterPairs = new Dictionary<SkinnedMeshRenderer, MeshFilter>();
void AddSkinnedMeshBoneTransforms(SkinnedMeshRenderer renderer)
{
// fix issue if renderer does not actually have a mesh assigned.
if (renderer.sharedMesh == null) return;
HashSet<int> validBoneIndexes = new HashSet<int>();
#if UNITY_2019_1_OR_NEWER
Transform transform = renderer.transform;
Vector3[] vertices = renderer.sharedMesh.vertices;
Unity.Collections.NativeArray<BoneWeight1> weights = renderer.sharedMesh.GetAllBoneWeights();
Unity.Collections.NativeArray<byte> bonesPerVertex = renderer.sharedMesh.GetBonesPerVertex();
int boneWeightIndex = 0;
for (int vertIndex = 0; vertIndex < vertices.Length; vertIndex++)
{
if (vertIndex <= bonesPerVertex.Length)
{
int numBonesForVertex = bonesPerVertex[vertIndex];
for (int i = 0; i < numBonesForVertex; i++)
{
if (boneWeightIndex <= weights.Length)
{
BoneWeight1 item = weights[boneWeightIndex];
if (item.boneIndex >= 0 && item.weight >= 0)
{
validBoneIndexes.Add(item.boneIndex);
}
boneWeightIndex++;
}
else { break; }
}
}
else { break; }
}
#else
BoneWeight[] weights = renderer.sharedMesh.boneWeights;
for (int i = 0; i < weights.Length; i++)
{
BoneWeight item = weights[i];
if (item.boneIndex0 >= 0 && item.weight0 >= 0)
{
validBoneIndexes.Add(item.boneIndex0);
}
if (item.boneIndex1 >= 0 && item.weight1 >= 0)
{
validBoneIndexes.Add(item.boneIndex1);
}
if (item.boneIndex2 >= 0 && item.weight2 >= 0)
{
validBoneIndexes.Add(item.boneIndex2);
}
if (item.boneIndex3 >= 0 && item.weight3 >= 0)
{
validBoneIndexes.Add(item.boneIndex3);
}
}
#endif
List<Transform> bonesWithAtLeastOneWeightedVertex = new List<Transform>();
// add them in order.
List<int> validBoneIndexList = validBoneIndexes.ToList();
validBoneIndexList.Sort();
if (renderer.bones != null && renderer.bones.Length > 0)
{
foreach (var index in validBoneIndexList)
{
// occurs if a smr is optimized and bones are hidden.
if (index >= 0 && index < renderer.bones.Length)
{
bonesWithAtLeastOneWeightedVertex.Add(renderer.bones[index]);
}
}
}
BoneTransforms.AddRange(bonesWithAtLeastOneWeightedVertex);
}
public void ClearVerticesForBone(Transform bone)
{
int boneIndex = _selectedBones.IndexOf(bone);
if (boneIndex >= 0)
{
var selectedVertsForBone = _selectedBoneVertices[boneIndex].SelectedVertices;
HashSet<EasyColliderVertex> selectedVertSet = new HashSet<EasyColliderVertex>(selectedVertsForBone);
// because they can technically be cleared already, so clearing and invert would break things.
selectedVertSet.IntersectWith(_SelectedVerticesSet);
SelectVertices(selectedVertSet, true);
_selectedBones.RemoveAt(boneIndex);
_selectedBoneVertices.RemoveAt(boneIndex);
}
}
public void SelectVerticesForBone(Transform bone, float weightCutoff)
{
LastSelectedBone = bone;
SkinnedMeshRenderer[] smrs = SelectedGameObject.GetComponentsInChildren<SkinnedMeshRenderer>();
SkinnedMeshRenderer smr = null;
foreach (var item in smrs)
{
if (item.bones.Contains(bone))
{
smr = item;
break;
}
}
if (smr == null) return;
Transform[] bones = smr.bones;
int boneIndex = -1;
for (int i = 0; i < bones.Length; i++)
{
if (bones[i] == bone)
{
boneIndex = i;
break;
}
}
if (_SkinnedMeshFilterPairs.ContainsKey(smr))
{
Transform transform = _SkinnedMeshFilterPairs[smr].transform;
Vector3[] vertices = _SkinnedMeshFilterPairs[smr].sharedMesh.vertices;
HashSet<EasyColliderVertex> verts = new HashSet<EasyColliderVertex>();
#if UNITY_2019_1_OR_NEWER
Unity.Collections.NativeArray<BoneWeight1> weights = smr.sharedMesh.GetAllBoneWeights();
Unity.Collections.NativeArray<byte> bonesPerVertex = smr.sharedMesh.GetBonesPerVertex();
int boneWeightIndex = 0;
for (int vertIndex = 0; vertIndex < vertices.Length; vertIndex++)
{
int numBonesForVertex = bonesPerVertex[vertIndex];
for (int i = 0; i < numBonesForVertex; i++)
{
BoneWeight1 item = weights[boneWeightIndex];
if (item.boneIndex == boneIndex && item.weight >= weightCutoff)
{
verts.Add(new EasyColliderVertex(transform, vertices[vertIndex]));
}
boneWeightIndex++;
}
}
#else
BoneWeight[] weights = smr.sharedMesh.boneWeights;
for (int i = 0; i < weights.Length; i++)
{
BoneWeight item = weights[i];
if (item.boneIndex0 == boneIndex && item.weight0 >= weightCutoff)
{
verts.Add(new EasyColliderVertex(transform, vertices[i]));
}
if (item.boneIndex1 == boneIndex && item.weight1 >= weightCutoff)
{
verts.Add(new EasyColliderVertex(transform, vertices[i]));
}
if (item.boneIndex2 == boneIndex && item.weight2 >= weightCutoff)
{
verts.Add(new EasyColliderVertex(transform, vertices[i]));
}
if (item.boneIndex3 == boneIndex && item.weight3 >= weightCutoff)
{
verts.Add(new EasyColliderVertex(transform, vertices[i]));
}
}
#endif
// deselect any previously selected vertices for that bone, then reselect.
int selectedBoneIndex = _selectedBones.IndexOf(bone);
HashSet<EasyColliderVertex> vertsAlreadySelectedForBone = new HashSet<EasyColliderVertex>();
if (selectedBoneIndex >= 0)
{
// we've selected this bone already, so we want the vertices we selected for it
// and then we need only the ones that are still selected with the intersect
// then we select them to deselect the currently selected ones, so that
// the new selection only selects the appropriate weight
vertsAlreadySelectedForBone.UnionWith(_selectedBoneVertices[selectedBoneIndex].SelectedVertices);
_selectedBoneVertices[selectedBoneIndex].SelectedVertices = verts.ToList();
}
else
{
_selectedBones.Add(bone);
_selectedBoneVertices.Add(new SerializableBoneVertexList() { SelectedVertices = verts.ToList() });
// we dont want to end up deselecting vertices that are already selected, but we do want to store them as verts for this bone
// so we will be selecting the ones already selected to deselect them, so they can then be selected
vertsAlreadySelectedForBone.UnionWith(verts);
}
if (vertsAlreadySelectedForBone.Count > 0)
{
vertsAlreadySelectedForBone.IntersectWith(_SelectedVerticesSet);
SelectVertices(vertsAlreadySelectedForBone, true);
}
SelectVertices(verts, true);
// Debug.Log("Verts count:" + verts.Count + " Selected now:" + SelectedVerticesSet.Count);
}
}
}
}
#endif