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