3023 lines
108 KiB
C#
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 |