2049 lines
81 KiB
C#
2049 lines
81 KiB
C#
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if (UNITY_EDITOR)
|
|
using UnityEditor;
|
|
#endif
|
|
using System.Linq;
|
|
namespace ECE
|
|
{
|
|
/// <summary>
|
|
/// Class to calculate and add colliders
|
|
/// </summary>
|
|
public class EasyColliderCreator
|
|
{
|
|
|
|
#if (UNITY_EDITOR)
|
|
|
|
/// <summary>
|
|
/// Used in ECEAutoSkinned when generating previews / depenetration.
|
|
/// </summary>
|
|
public bool UndoEnabled = true;
|
|
|
|
/// <summary>
|
|
/// Just an easy way to get an instance of preferences to create colliders with.
|
|
/// </summary>
|
|
/// <value></value>
|
|
private EasyColliderPreferences ECEPreferences
|
|
{
|
|
get { return EasyColliderPreferences.Preferences; }
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Data struct from calculating a best fit sphere
|
|
/// </summary>
|
|
private struct BestFitSphere
|
|
{
|
|
/// <summary>
|
|
/// Center of the sphere
|
|
/// </summary>
|
|
public Vector3 Center;
|
|
|
|
/// <summary>
|
|
/// Radius of the sphere
|
|
/// </summary>
|
|
public float Radius;
|
|
|
|
/// <summary>
|
|
/// Best Fit Sphere
|
|
/// </summary>
|
|
/// <param name="center">Center of the sphere</param>
|
|
/// <param name="radius">Radius of the sphere</param>
|
|
public BestFitSphere(Vector3 center, float radius)
|
|
{
|
|
this.Center = center;
|
|
this.Radius = radius;
|
|
}
|
|
}
|
|
|
|
|
|
// rotate and duplicate are editor-only
|
|
#if (UNITY_EDITOR)
|
|
|
|
/// <summary>
|
|
/// gets a rotation as a quaternion from a transformation matrix.
|
|
/// useful for matrix operations in earlier versions of unity
|
|
/// </summary>
|
|
/// <param name="matrix"></param>
|
|
/// <returns></returns>
|
|
private Quaternion RotationFromMatrix(Matrix4x4 matrix)
|
|
{
|
|
Vector3 forward = new Vector3(matrix[0, 2], matrix[1, 2], matrix[2, 2]);
|
|
Vector3 up = new Vector3(matrix[0, 1], matrix[1, 1], matrix[2, 1]);
|
|
return Quaternion.LookRotation(forward, up);
|
|
}
|
|
|
|
/// <summary>
|
|
/// gets a scale from a transformation matrix.
|
|
/// useful for matrix operations in earlier versions of unity
|
|
/// </summary>
|
|
/// <param name="matrix"></param>
|
|
/// <returns></returns>
|
|
private Vector3 ScaleFromMatrix(Matrix4x4 matrix)
|
|
{
|
|
Vector3 mScale = Vector3.zero;
|
|
mScale.x = new Vector4(matrix[0, 0], matrix[1, 0], matrix[2, 0], matrix[3, 0]).magnitude;
|
|
mScale.y = new Vector4(matrix[0, 1], matrix[1, 1], matrix[2, 1], matrix[3, 1]).magnitude;
|
|
mScale.z = new Vector4(matrix[0, 2], matrix[1, 2], matrix[2, 2], matrix[3, 2]).magnitude;
|
|
return mScale;
|
|
}
|
|
|
|
private Vector3 PositionFromMatrix(Matrix4x4 matrix)
|
|
{
|
|
Vector3 mScale = new Vector3(matrix[0, 3], matrix[1, 3], matrix[2, 3]);
|
|
return mScale;
|
|
}
|
|
|
|
public List<EasyColliderData> CalculateRotateAndDuplicate(EasyColliderPreferences preferences, EasyColliderEditor ece)
|
|
{
|
|
EasyColliderRotateDuplicate ecrd = preferences.rotatedDupeSettings;
|
|
ecrd.attachTo = ece.AttachToObject;
|
|
return CalculateRotateAndDuplicate(preferences.PreviewColliderType, ece.GetWorldVertices(true), ecrd);
|
|
}
|
|
|
|
private List<EasyColliderData> CalculateRotateAndDuplicate(CREATE_COLLIDER_TYPE result, List<Vector3> worldVertices, EasyColliderRotateDuplicate ecrd)
|
|
{
|
|
Transform attachTo = ecrd.attachTo.transform;
|
|
Transform pivot = ecrd.pivot.transform;
|
|
List<EasyColliderData> data = new List<EasyColliderData>();
|
|
// we want to create the normal colliders in the attachto's local space, but rotate them around the pivot point.
|
|
List<Vector3> localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
EasyColliderData baseData = null;
|
|
switch (result)
|
|
{
|
|
case CREATE_COLLIDER_TYPE.BOX:
|
|
// we want the normal colliders calculated in local space of the attach to object.
|
|
// baseData = CalculateBox(worldVertices, attachTo, false);
|
|
baseData = CalculateBoxLocal(localVertices);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
|
|
baseData = CalculateBox(worldVertices, pivot, true, true);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.SPHERE:
|
|
baseData = CalculateSphereMinMaxLocal(localVertices);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.CAPSULE:
|
|
baseData = CalculateCapsuleMinMaxLocal(localVertices, ECEPreferences.CapsuleColliderMethod);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
|
|
baseData = CalculateCapsuleMinMax(worldVertices, pivot, ECEPreferences.CapsuleColliderMethod, true, true);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
|
|
baseData = CalculateMeshColliderQuickHullLocal(localVertices);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.CYLINDER:
|
|
baseData = CalculateCylinderColliderLocal(localVertices, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
|
|
break;
|
|
}
|
|
|
|
|
|
if (result != CREATE_COLLIDER_TYPE.ROTATED_BOX && result != CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
|
|
{
|
|
// rotated colliders have thier own matrix.
|
|
baseData.Matrix = attachTo.localToWorldMatrix;
|
|
}
|
|
|
|
// just calculating the angle and axis to rotate
|
|
float rotationAngle = (ecrd.EndRotation - ecrd.StartRotation) / (ecrd.NumberOfDuplications);
|
|
float currentAngle = ecrd.StartRotation;
|
|
Vector3 axis = Vector3.zero;
|
|
axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.X ? pivot.right : axis;
|
|
axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.Y ? pivot.up : axis;
|
|
axis = ecrd.axis == EasyColliderRotateDuplicate.ROTATE_AXIS.Z ? pivot.forward : axis;
|
|
|
|
Quaternion baseRotation = RotationFromMatrix(baseData.Matrix);
|
|
// will just be the attachTo's position at this point.
|
|
Vector3 basePosition = PositionFromMatrix(baseData.Matrix);
|
|
// lossy scale essentially.
|
|
Vector3 baseScale = ScaleFromMatrix(baseData.Matrix);
|
|
//Debug.Log("Base Pos:" + basePosition + " Base Rotation:" + baseRotation + " Base Scale:" + baseScale);
|
|
// used in the longer version, leaving in here for future reference use.
|
|
// Matrix4x4 pivotTranslation = Matrix4x4.Translate(pivot.position);
|
|
|
|
for (int i = 0; i < ecrd.NumberOfDuplications; i++)
|
|
{
|
|
// rotation around pivot's axis.
|
|
Quaternion q = Quaternion.AngleAxis(currentAngle, axis);
|
|
// We're gonna leave this here for my future self, as matrix math still confuses me
|
|
// create rotation matrix
|
|
// Matrix4x4 rotationMatrix = Matrix4x4.TRS(Vector3.zero, q * baseRotation, mScale);
|
|
// // // calculate rotated forward from the pivot to the attach to object.
|
|
//Vector3 forward = q * (attachTo.position - pivot.position);
|
|
// // create a matrix for the rotated pivot to attach translation
|
|
// var pushTranslate = Matrix4x4.Translate(forward);
|
|
// // use the rotation matrix
|
|
// m = rotationMatrix;
|
|
// // translated it to the pivot point.
|
|
// m = pivotTranslation * m;
|
|
// // push out from pivot
|
|
// m = pushTranslate * m;
|
|
// Above: simplified
|
|
//m = Matrix4x4.TRS(basePosition + forward, q * baseRotation, baseScale);
|
|
// but the preview matrix isn't correclty factoring in non-uniform scale.
|
|
//}
|
|
Vector3 forward = q * (attachTo.position - pivot.position);
|
|
Matrix4x4 m; m = Matrix4x4.TRS(basePosition + forward, q * baseRotation, baseScale);
|
|
// preview not correctly using non-uniform scale.
|
|
switch (result)
|
|
{
|
|
case CREATE_COLLIDER_TYPE.CAPSULE:
|
|
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
|
|
{
|
|
CapsuleColliderData d1 = baseData as CapsuleColliderData;
|
|
CapsuleColliderData d2 = new CapsuleColliderData();
|
|
d2.Clone(d1);
|
|
d2.Matrix = m;
|
|
data.Add(d2);
|
|
break;
|
|
}
|
|
case CREATE_COLLIDER_TYPE.BOX:
|
|
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
|
|
{
|
|
BoxColliderData d1 = baseData as BoxColliderData;
|
|
BoxColliderData d2 = new BoxColliderData();
|
|
d2.Clone(d1);
|
|
d2.Matrix = m;
|
|
data.Add(d2);
|
|
break;
|
|
}
|
|
case CREATE_COLLIDER_TYPE.SPHERE:
|
|
{
|
|
SphereColliderData d1 = baseData as SphereColliderData;
|
|
SphereColliderData d2 = new SphereColliderData();
|
|
d2.Clone(d1);
|
|
d2.Matrix = m;
|
|
data.Add(d2);
|
|
break;
|
|
}
|
|
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
|
|
case CREATE_COLLIDER_TYPE.CYLINDER:
|
|
{
|
|
MeshColliderData d1 = baseData as MeshColliderData;
|
|
MeshColliderData d2 = new MeshColliderData();
|
|
d2.Clone(d1);
|
|
d2.Matrix = m;
|
|
data.Add(d2);
|
|
break;
|
|
}
|
|
}
|
|
currentAngle += rotationAngle;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
|
|
public List<Collider> CreateRotateAndDuplicateColliders(CREATE_COLLIDER_TYPE collider_type, List<Vector3> worldVertices, EasyColliderProperties properties)
|
|
{
|
|
// Debug.Log("ECC: Create Rotated and Duplicate Colliders");
|
|
List<Collider> CreatedColliders = new List<Collider>();
|
|
EasyColliderRotateDuplicate ecrd = ECEPreferences.rotatedDupeSettings;
|
|
float rotationPerCollider = (ecrd.EndRotation - ecrd.StartRotation) / ecrd.NumberOfDuplications;
|
|
float currentRotation = 0.0f;
|
|
Transform parent = properties.AttachTo.transform;
|
|
List<EasyColliderData> data = CalculateRotateAndDuplicate(collider_type, worldVertices, ecrd);
|
|
foreach (EasyColliderData d in data)
|
|
{
|
|
GameObject obj = new GameObject("Rotated Collider");
|
|
#if (UNITY_EDITOR)
|
|
Undo.RegisterCreatedObjectUndo(obj, " Create Rotated Collider");
|
|
#endif
|
|
obj.transform.rotation = RotationFromMatrix(d.Matrix);
|
|
obj.transform.SetParent(ecrd.pivot.transform);
|
|
obj.transform.position = PositionFromMatrix(d.Matrix);
|
|
obj.transform.localScale = Vector3.one;
|
|
obj.layer = properties.Layer;
|
|
properties.AttachTo = obj;
|
|
properties.Orientation = COLLIDER_ORIENTATION.ROTATED;
|
|
Collider collider = null;
|
|
switch (collider_type)
|
|
{
|
|
case CREATE_COLLIDER_TYPE.BOX:
|
|
obj.name = "Rotated Box Collider";
|
|
collider = CreateBoxCollider(d as BoxColliderData, properties, false);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
|
|
obj.name = "Rotated Box Collider";
|
|
BoxColliderData rbd = d as BoxColliderData;
|
|
collider = CreateBoxCollider(rbd, properties, false);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.SPHERE:
|
|
obj.name = "Rotated Sphere Collider";
|
|
collider = CreateSphereCollider(d as SphereColliderData, properties, false);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.CAPSULE:
|
|
obj.name = "Rotated Capsule Collider";
|
|
collider = CreateCapsuleCollider(d as CapsuleColliderData, properties, false);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
|
|
obj.name = "Rotated Capsule Collider";
|
|
CapsuleColliderData rcd = d as CapsuleColliderData;
|
|
collider = CreateCapsuleCollider(rcd, properties, false);
|
|
break;
|
|
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
|
|
case CREATE_COLLIDER_TYPE.CYLINDER:
|
|
MeshColliderData mcd = d as MeshColliderData;
|
|
if (ECEPreferences.SaveConvexHullAsAsset)
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(mcd.ConvexMesh, parent.gameObject);
|
|
}
|
|
collider = CreateConvexMeshCollider(mcd.ConvexMesh, properties.AttachTo, properties);
|
|
break;
|
|
}
|
|
CreatedColliders.Add(collider);
|
|
currentRotation += rotationPerCollider;
|
|
#if (UNITY_EDITOR)
|
|
Undo.SetTransformParent(obj.transform, parent, "Change Parent");
|
|
#else
|
|
obj.transform.SetParent(parent);
|
|
#endif
|
|
obj.transform.localScale = Vector3.one;
|
|
PostColliderCreationProcess(collider, properties);
|
|
}
|
|
return CreatedColliders;
|
|
}
|
|
|
|
#endif
|
|
|
|
// merge colliders are editor-only.
|
|
#if (UNITY_EDITOR)
|
|
|
|
#region MergeColliders
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Merges all colliders in the list to a single resultant collider and returns it.
|
|
/// </summary>
|
|
/// <param name="collidersToMerge">List of colliders to merge</param>
|
|
/// <param name="result">Type of collider we want the colliders merged into</param>
|
|
/// <param name="properties">Properties to set on the new collider</param>
|
|
/// <returns>Single merged collider.</returns>
|
|
public Collider MergeColliders(List<Collider> collidersToMerge, CREATE_COLLIDER_TYPE result, EasyColliderProperties properties)
|
|
{
|
|
if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
|
|
}
|
|
EasyColliderData data = MergeCollidersPreview(collidersToMerge, result, properties.AttachTo.transform);
|
|
if (result == CREATE_COLLIDER_TYPE.BOX || result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
|
|
{
|
|
if (result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
|
|
{
|
|
properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
|
|
}
|
|
return CreateBoxCollider(data as BoxColliderData, properties);
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.CAPSULE || result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
|
|
{
|
|
if (result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
|
|
{
|
|
properties.AttachTo = GetFirstNonNullTransform(collidersToMerge).gameObject;
|
|
}
|
|
return CreateCapsuleCollider(data as CapsuleColliderData, properties);
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.SPHERE)
|
|
{
|
|
return CreateSphereCollider(data as SphereColliderData, properties);
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.CONVEX_MESH || result == CREATE_COLLIDER_TYPE.CYLINDER)
|
|
{
|
|
MeshColliderData d = data as MeshColliderData;
|
|
if (ECEPreferences.SaveConvexHullAsAsset)
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(d.ConvexMesh, properties.AttachTo);
|
|
}
|
|
return CreateConvexMeshCollider(d.ConvexMesh, properties.AttachTo, properties);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the first transform of a non-null collider
|
|
/// </summary>
|
|
/// <param name="collidersToMerge">list of colliders</param>
|
|
/// <returns>first non-null collider's transform, null if no non-null colliders</returns>
|
|
private Transform GetFirstNonNullTransform(List<Collider> collidersList)
|
|
{
|
|
foreach (Collider c in collidersList)
|
|
{
|
|
if (c != null)
|
|
{
|
|
return c.transform;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the preview data for merged colliders.
|
|
/// </summary>
|
|
/// <param name="collidersToMerge"></param>
|
|
/// <param name="result"></param>
|
|
/// <param name="properties"></param>
|
|
/// <returns></returns>
|
|
public EasyColliderData MergeCollidersPreview(List<Collider> collidersToMerge, CREATE_COLLIDER_TYPE result, Transform attachTo)
|
|
{
|
|
List<Vector3> worldVertices = GetWorldVertsForColliders(collidersToMerge);
|
|
EasyColliderData d = new EasyColliderData();
|
|
if (result == CREATE_COLLIDER_TYPE.CONVEX_MESH)
|
|
{
|
|
return EasyColliderQuickHull.CalculateHullData(worldVertices, attachTo);
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.BOX || result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
|
|
{
|
|
if (result == CREATE_COLLIDER_TYPE.ROTATED_BOX)
|
|
{
|
|
attachTo = GetFirstNonNullTransform(collidersToMerge);
|
|
}
|
|
return CalculateBox(worldVertices, attachTo, false);
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.SPHERE)
|
|
{
|
|
// does it make sense to allow different sphere methods -> or just min-max method.
|
|
if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.MinMax)
|
|
{
|
|
return CalculateSphereMinMax(worldVertices, attachTo);
|
|
}
|
|
else if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.Distance)
|
|
{
|
|
return CalculateSphereDistance(worldVertices, attachTo);
|
|
}
|
|
else if (ECEPreferences.SphereColliderMethod == SPHERE_COLLIDER_METHOD.BestFit)
|
|
{
|
|
return CalculateSphereBestFit(worldVertices, attachTo);
|
|
}
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.CAPSULE || result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
|
|
{
|
|
if (result == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
|
|
{
|
|
attachTo = GetFirstNonNullTransform(collidersToMerge);
|
|
}
|
|
// does it make sense to allow capsule methods? -> can use various min max + dia, radius etc.
|
|
if (ECEPreferences.CapsuleColliderMethod == CAPSULE_COLLIDER_METHOD.BestFit)
|
|
{
|
|
return CalculateCapsuleBestFit(worldVertices, attachTo, false);
|
|
}
|
|
else
|
|
{
|
|
return CalculateCapsuleMinMax(worldVertices, attachTo, ECEPreferences.CapsuleColliderMethod, false);
|
|
}
|
|
}
|
|
else if (result == CREATE_COLLIDER_TYPE.CYLINDER)
|
|
{
|
|
return CalculateCylinderCollider(worldVertices, attachTo);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
public List<Vector3> GetWorldVertsForColliders(List<Collider> colliders)
|
|
{
|
|
List<Vector3> worldVertices = new List<Vector3>();
|
|
foreach (Collider col in colliders)
|
|
{
|
|
// Get world vertices for mesh collider.
|
|
MeshCollider mc = col as MeshCollider;
|
|
if (mc != null)
|
|
{
|
|
AddWorldVerts(mc, worldVertices);
|
|
continue;
|
|
}
|
|
BoxCollider box = col as BoxCollider;
|
|
if (box != null)
|
|
{
|
|
AddWorldVerts(box, worldVertices);
|
|
continue;
|
|
}
|
|
CapsuleCollider capsule = col as CapsuleCollider;
|
|
if (capsule != null)
|
|
{
|
|
AddWorldVerts(capsule, worldVertices);
|
|
continue;
|
|
}
|
|
SphereCollider sphere = col as SphereCollider;
|
|
if (sphere != null)
|
|
{
|
|
AddWorldVerts(sphere, worldVertices);
|
|
continue;
|
|
}
|
|
}
|
|
return worldVertices;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the vertices of a mesh collider to the world vertices list
|
|
/// </summary>
|
|
/// <param name="meshCollider">mesh collider</param>
|
|
/// <param name="worldVertices">world vertices list</param>
|
|
private void AddWorldVerts(MeshCollider meshCollider, List<Vector3> worldVertices)
|
|
{
|
|
if (meshCollider == null || meshCollider.sharedMesh == null) return;
|
|
Vector3[] vertices = meshCollider.sharedMesh.vertices;
|
|
Transform t = meshCollider.transform;
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i] = t.TransformPoint(vertices[i]);
|
|
}
|
|
worldVertices.AddRange(vertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the vertices of a box collider to the world vertices list
|
|
/// </summary>
|
|
/// <param name="boxCollider">box collider</param>
|
|
/// <param name="worldVertices">world vertices list</param>
|
|
private void AddWorldVerts(BoxCollider boxCollider, List<Vector3> worldVertices)
|
|
{
|
|
Vector3 halfSize = boxCollider.size / 2;
|
|
Vector3 center = boxCollider.center;
|
|
Vector3[] vertices = new Vector3[8]{
|
|
center + halfSize, //0
|
|
center + new Vector3(halfSize.x, halfSize.y, -halfSize.z), //1
|
|
center + new Vector3(halfSize.x, -halfSize.y, halfSize.z), //2
|
|
center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z), //3
|
|
center + new Vector3(-halfSize.x, halfSize.y, halfSize.z), //4
|
|
center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z), //5
|
|
center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z), //6
|
|
center - halfSize, //7
|
|
};
|
|
int triangleOffset = worldVertices.Count;
|
|
Transform t = boxCollider.transform;
|
|
for (int i = 0; i < vertices.Length; i++)
|
|
{
|
|
vertices[i] = t.TransformPoint(vertices[i]);
|
|
}
|
|
// add triangles and verts
|
|
worldVertices.AddRange(vertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the vertices of a sphere collider to the world vertices list
|
|
/// </summary>
|
|
/// <param name="sphereCollider">sphere collider</param>
|
|
/// <param name="worldVertices">world vertices list</param>
|
|
private void AddWorldVerts(SphereCollider sphereCollider, List<Vector3> worldVertices)
|
|
{
|
|
AddWorldVertsSphere(sphereCollider.transform, sphereCollider.center, sphereCollider.radius, worldVertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the vertices of a capsule collider to the world vertices list
|
|
/// </summary>
|
|
/// <param name="capsuleCollider">capsule collider</param>
|
|
/// <param name="worldVertices">world vertices list</param>
|
|
private void AddWorldVerts(CapsuleCollider capsuleCollider, List<Vector3> worldVertices)
|
|
{
|
|
Vector3 top = Vector3.zero;
|
|
Vector3 bottom = Vector3.zero;
|
|
if (capsuleCollider.direction == 0) //x
|
|
{
|
|
top = capsuleCollider.center + Vector3.right * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
bottom = capsuleCollider.center - Vector3.right * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
}
|
|
else if (capsuleCollider.direction == 1) //y
|
|
{
|
|
top = capsuleCollider.center + Vector3.up * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
bottom = capsuleCollider.center - Vector3.up * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
}
|
|
else if (capsuleCollider.direction == 2) //z
|
|
{
|
|
top = capsuleCollider.center + Vector3.forward * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
bottom = capsuleCollider.center - Vector3.forward * ((capsuleCollider.height - capsuleCollider.radius * 2) / 2);
|
|
}
|
|
|
|
//manually reselect collider points in an order that maintains rotation for rotated capsule colliders
|
|
// again the name method is brittle, but seems the easiest way to identify rotated colliders without cluttering tags.
|
|
if (capsuleCollider.gameObject.name.Contains("Rotated"))
|
|
{
|
|
Transform t = capsuleCollider.transform;
|
|
worldVertices.Add(t.TransformPoint(top));
|
|
worldVertices.Add(t.TransformPoint(bottom));
|
|
worldVertices.Add(t.TransformPoint(capsuleCollider.center + Vector3.Cross(top, bottom).normalized * capsuleCollider.radius));
|
|
}
|
|
|
|
// Easiest to just add the full top and bottom spheres as the result is the same for any collider.
|
|
// as they contain the min and max values of collider, and the middle section is all on the same plane as the halfsphere's base for convex meshes.
|
|
// we could write a seperate method to only add the half-spheres, but there's no need.
|
|
// top sphere
|
|
AddWorldVertsSphere(capsuleCollider.transform, top, capsuleCollider.radius, worldVertices);
|
|
// bottom sphere
|
|
AddWorldVertsSphere(capsuleCollider.transform, bottom, capsuleCollider.radius, worldVertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds world space points around a sphere
|
|
/// </summary>
|
|
/// <param name="t">transform of the collider</param>
|
|
/// <param name="center">center of the sphere</param>
|
|
/// <param name="baseRadius">radius of the sphere</param>
|
|
/// <param name="worldVertices"></param>
|
|
private void AddWorldVertsSphere(Transform t, Vector3 center, float radius, List<Vector3> worldVertices)
|
|
{
|
|
int accuracy = ECEPreferences.MergeCollidersRoundnessAccuracy;
|
|
// 360 degrees in radians.
|
|
float sin, cos = sin = 0.0f;
|
|
for (int i = 1; i < accuracy; i++)
|
|
{
|
|
// center shifted to the z-axis
|
|
float h = (i / (float)accuracy) * radius * 2;
|
|
Vector3 centerX = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.right;
|
|
Vector3 centerY = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.up;
|
|
Vector3 centerZ = center - (radius - (i / (float)accuracy) * radius * 2) * Vector3.forward;
|
|
float newRadius = Mathf.Sqrt(radius * 2 * h - Mathf.Pow(h, 2));
|
|
for (int j = 0; j <= accuracy; j++)
|
|
{
|
|
float angleStep = ((j / (float)accuracy) * 360f) * Mathf.Deg2Rad;
|
|
sin = Mathf.Sin(angleStep);
|
|
cos = Mathf.Cos(angleStep);
|
|
// constant z.
|
|
float xZ = centerZ.x + newRadius * sin;
|
|
float yZ = centerZ.y + newRadius * cos;
|
|
// constant x.
|
|
float yX = centerX.y + newRadius * sin;
|
|
float zX = centerX.z + newRadius * cos;
|
|
// constant y.
|
|
float zY = centerY.z + (newRadius * sin);
|
|
float xY = centerY.x + (newRadius * cos);
|
|
//
|
|
worldVertices.Add(t.TransformPoint(new Vector3(centerX.x, yX, zX)));
|
|
worldVertices.Add(t.TransformPoint(new Vector3(xY, centerY.y, zY)));
|
|
worldVertices.Add(t.TransformPoint(new Vector3(xZ, yZ, centerZ.z)));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endif
|
|
|
|
#region ColliderDataCalculation
|
|
|
|
/// <summary>
|
|
/// Calculates the best fit sphere for a series of points. Providing a larger list of points increases accuracy.
|
|
/// </summary>
|
|
/// <param name="localVertices">Local space vertices</param>
|
|
/// <returns>The best fit sphere</returns>
|
|
private BestFitSphere CalculateBestFitSphere(List<Vector3> localVertices)
|
|
{
|
|
// # of points.
|
|
int n = localVertices.Count;
|
|
// Calculate average x, y, and z value of vertices.
|
|
float xAvg, yAvg, zAvg = xAvg = yAvg = 0.0f;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
xAvg += vertex.x;
|
|
yAvg += vertex.y;
|
|
zAvg += vertex.z;
|
|
}
|
|
xAvg = xAvg * (1.0f / n);
|
|
yAvg = yAvg * (1.0f / n);
|
|
zAvg = zAvg * (1.0f / n);
|
|
// Do some fun math with matrices
|
|
// B Vector.
|
|
Vector3 B = Vector3.zero;
|
|
// Can use a 4x4 as a 3x3 with the 4x4 as 0,0,0,1 in the last row/column.
|
|
Matrix4x4 AM = Matrix4x4.zero;
|
|
AM.m33 = 1;
|
|
float x2, y2, z2 = x2 = y2 = 0.0f;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
AM[0, 0] += 2 * (vertex.x * (vertex.x - xAvg)) / n;
|
|
AM[0, 1] += 2 * (vertex.x * (vertex.y - yAvg)) / n;
|
|
AM[0, 2] += 2 * (vertex.x * (vertex.z - zAvg)) / n;
|
|
AM[1, 0] += 2 * (vertex.y * (vertex.x - xAvg)) / n;
|
|
AM[1, 1] += 2 * (vertex.y * (vertex.y - yAvg)) / n;
|
|
AM[1, 2] += 2 * (vertex.y * (vertex.z - zAvg)) / n;
|
|
AM[2, 0] += 2 * (vertex.z * (vertex.x - xAvg)) / n;
|
|
AM[2, 1] += 2 * (vertex.z * (vertex.y - yAvg)) / n;
|
|
AM[2, 2] += 2 * (vertex.z * (vertex.z - zAvg)) / n;
|
|
x2 = vertex.x * vertex.x;
|
|
y2 = vertex.y * vertex.y;
|
|
z2 = vertex.z * vertex.z;
|
|
B.x += ((x2 + y2 + z2) * (vertex.x - xAvg)) / n;
|
|
B.y += ((x2 + y2 + z2) * (vertex.y - yAvg)) / n;
|
|
B.z += ((x2 + y2 + z2) * (vertex.z - zAvg)) / n;
|
|
}
|
|
// Calculate the center of the best-fit sphere.
|
|
Vector3 center = (AM.transpose * AM).inverse * AM.transpose * B;
|
|
// Calculate radius.
|
|
float radius = 0.0f;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
radius += Mathf.Pow((vertex.x - center.x), 2) + Mathf.Pow(vertex.y - center.y, 2) + Mathf.Pow(vertex.z - center.z, 2);
|
|
}
|
|
radius = Mathf.Sqrt(radius / localVertices.Count);
|
|
BestFitSphere bfs = new BestFitSphere(center, radius);
|
|
return bfs;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a box's data from a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertices in world space</param>
|
|
/// <param name="attachTo">transform the box will be attached to</param>
|
|
/// <param name="isRotated">are we creating a rotated box?</param>
|
|
/// <param name="requiresMatrixCalc"> true when the attach to transform is not already correctly rotated for a rotated collider</param>
|
|
/// <returns>Data appropriate variables set for a box collider</returns>
|
|
public BoxColliderData CalculateBox(List<Vector3> worldVertices, Transform attachTo, bool isRotated = false, bool requiresMatrixCalc = false)
|
|
{
|
|
if (isRotated && worldVertices.Count < 3)
|
|
{
|
|
return new BoxColliderData();
|
|
}
|
|
else if (worldVertices.Count < 2)
|
|
{
|
|
return new BoxColliderData();
|
|
}
|
|
Quaternion q = Quaternion.identity;
|
|
Matrix4x4 m;
|
|
List<Vector3> localVertices = new List<Vector3>();
|
|
if (isRotated && worldVertices.Count >= 3 && requiresMatrixCalc)
|
|
{
|
|
Vector3 forward = worldVertices[1] - worldVertices[0];
|
|
Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
|
|
q = Quaternion.LookRotation(forward, up);
|
|
// world space matrix, pos doesn't matter here in preview.
|
|
m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
|
|
Matrix4x4 mi = m.inverse;
|
|
// dont actually have the object in the preview.
|
|
for (int i = 0; i < worldVertices.Count; i++)
|
|
{
|
|
localVertices.Add(mi.MultiplyPoint3x4(worldVertices[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
m = attachTo.localToWorldMatrix;
|
|
}
|
|
BoxColliderData data = CalculateBoxLocal(localVertices);
|
|
data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_BOX : CREATE_COLLIDER_TYPE.BOX;
|
|
data.Matrix = m;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates box collider data for a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="vertices">list of local space vertices</param>
|
|
/// <returns>box collider data with center and size set</returns>
|
|
public BoxColliderData CalculateBoxLocal(List<Vector3> vertices)
|
|
{
|
|
float xMin, yMin, zMin = xMin = yMin = Mathf.Infinity;
|
|
float xMax, yMax, zMax = xMax = yMax = -Mathf.Infinity;
|
|
foreach (Vector3 vertex in vertices)
|
|
{
|
|
//x min & max.
|
|
xMin = (vertex.x < xMin) ? vertex.x : xMin;
|
|
xMax = (vertex.x > xMax) ? vertex.x : xMax;
|
|
//y min & max
|
|
yMin = (vertex.y < yMin) ? vertex.y : yMin;
|
|
yMax = (vertex.y > yMax) ? vertex.y : yMax;
|
|
//z min & max
|
|
zMin = (vertex.z < zMin) ? vertex.z : zMin;
|
|
zMax = (vertex.z > zMax) ? vertex.z : zMax;
|
|
}
|
|
Vector3 max = new Vector3(xMax, yMax, zMax);
|
|
Vector3 min = new Vector3(xMin, yMin, zMin);
|
|
Vector3 size = max - min;
|
|
Vector3 center = (max + min) / 2;
|
|
// set data from calculated values
|
|
BoxColliderData data = new BoxColliderData();
|
|
data.Center = center;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.BOX;
|
|
data.IsValid = true;
|
|
data.Size = size;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a capsule's data from the given values using the best fit method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertices in world space</param>
|
|
/// <param name="attachTo">transform the capsule will be attached to</param>
|
|
/// <param name="isRotated">are we creating a rotated capsule?</param>
|
|
/// <param name="requiresMatrixCalc"> true when the attach to transform is not already correctly rotated for a rotated collider</param>
|
|
/// <returns>Data with appropriate variables set for a capsule collider</returns>
|
|
public CapsuleColliderData CalculateCapsuleBestFit(List<Vector3> worldVertices, Transform attachTo, bool isRotated, bool requiresMatrixCalc = false)
|
|
{
|
|
if (worldVertices.Count >= 3)
|
|
{
|
|
Quaternion q = Quaternion.identity;
|
|
Matrix4x4 m;
|
|
List<Vector3> localVertices = new List<Vector3>();
|
|
if (isRotated && requiresMatrixCalc)
|
|
{
|
|
// for rotated colliders we also re-calculate the to local even though the transform is changed.
|
|
// this better handles scale-shearing.
|
|
Vector3 forward = worldVertices[1] - worldVertices[0];
|
|
Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
|
|
q = Quaternion.LookRotation(forward, up);
|
|
m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
|
|
for (int i = 0; i < worldVertices.Count; i++)
|
|
{
|
|
localVertices.Add(m.inverse.MultiplyPoint3x4(worldVertices[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
m = attachTo.localToWorldMatrix;
|
|
}
|
|
CapsuleColliderData data = CalculateCapsuleBestFitLocal(localVertices);
|
|
data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_CAPSULE : CREATE_COLLIDER_TYPE.CAPSULE;
|
|
data.Matrix = m;
|
|
return data;
|
|
}
|
|
return new CapsuleColliderData();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a best-fit capsule collider from a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">local space vertices</param>
|
|
/// <returns>Capsule collider data with center, direction and height</returns>
|
|
public CapsuleColliderData CalculateCapsuleBestFitLocal(List<Vector3> localVertices)
|
|
{
|
|
if (localVertices.Count < 3)
|
|
{
|
|
Debug.LogWarning("EasyColliderCreator: Too few vertices passed to calculate a best fit capsule collider.");
|
|
return new CapsuleColliderData();
|
|
}
|
|
// height from first 2 verts selected.
|
|
Vector3 v0 = localVertices[0];
|
|
Vector3 v1 = localVertices[1];
|
|
float height = Vector3.Distance(v0, v1);
|
|
float dX = Mathf.Abs(v1.x - v0.x);
|
|
float dY = Mathf.Abs(v1.y - v0.y);
|
|
float dZ = Mathf.Abs(v1.z - v0.z);
|
|
localVertices.RemoveAt(1);
|
|
localVertices.RemoveAt(0);
|
|
BestFitSphere bfs = CalculateBestFitSphere(localVertices);
|
|
Vector3 center = bfs.Center;
|
|
int direction = 0;
|
|
if (dX > dY && dX > dZ)
|
|
{
|
|
direction = 0;
|
|
center.x = (v1.x + v0.x) / 2;
|
|
}
|
|
else if (dY > dX && dY > dZ)
|
|
{
|
|
direction = 1;
|
|
center.y = (v1.y + v0.y) / 2;
|
|
}
|
|
else
|
|
{
|
|
direction = 2;
|
|
center.z = (v1.z + v0.z) / 2;
|
|
}
|
|
CapsuleColliderData data = new CapsuleColliderData();
|
|
data.Center = center;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
|
|
data.Direction = direction;
|
|
data.Height = height;
|
|
data.IsValid = true;
|
|
data.Radius = bfs.Radius;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a capsule's data from the given values using the min max method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertices in world space</param>
|
|
/// <param name="attachTo">transform the capsule will be attached to</param>
|
|
/// <param name="method">method we are using to create the capsule (ie MinMaxPlusRadius)</param>
|
|
/// <param name="isRotated">are we creating a rotated capsule?</param>
|
|
/// <param name="requiresMatrixCalc"> true when the attach to transform is not already correctly rotated for a rotated collider</param>
|
|
/// <returns>Data with appropriate variables set for a capsule collider</returns>
|
|
public CapsuleColliderData CalculateCapsuleMinMax(List<Vector3> worldVertices, Transform attachTo, CAPSULE_COLLIDER_METHOD method, bool isRotated, bool requiresMatrixCalc = false)
|
|
{
|
|
if (isRotated && worldVertices.Count < 3)
|
|
{
|
|
return new CapsuleColliderData();
|
|
}
|
|
else if (worldVertices.Count < 2)
|
|
{
|
|
return new CapsuleColliderData();
|
|
}
|
|
List<Vector3> localVertices = new List<Vector3>();
|
|
Matrix4x4 m;
|
|
Quaternion q;
|
|
if (isRotated && worldVertices.Count >= 3 && requiresMatrixCalc)
|
|
{
|
|
Vector3 forward = worldVertices[1] - worldVertices[0];
|
|
Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
|
|
q = Quaternion.LookRotation(forward, up);
|
|
m = Matrix4x4.TRS(attachTo.position, q, attachTo.lossyScale);
|
|
for (int i = 0; i < worldVertices.Count; i++)
|
|
{
|
|
localVertices.Add(m.inverse.MultiplyPoint3x4(worldVertices[i]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
localVertices = ToLocalVerts(attachTo.transform, worldVertices);
|
|
m = attachTo.localToWorldMatrix;
|
|
}
|
|
CapsuleColliderData data = CalculateCapsuleMinMaxLocal(localVertices, method);
|
|
data.ColliderType = isRotated ? CREATE_COLLIDER_TYPE.ROTATED_CAPSULE : CREATE_COLLIDER_TYPE.CAPSULE;
|
|
data.Matrix = m;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a capsule collider from a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">List of local space vertices</param>
|
|
/// <param name="method">method to use when calculating (used to add radius or diameter to height of capsule)</param>
|
|
/// <returns>Capsule collider data with center, direction, and height</returns>
|
|
public CapsuleColliderData CalculateCapsuleMinMaxLocal(List<Vector3> localVertices, CAPSULE_COLLIDER_METHOD method)
|
|
{
|
|
// calculate min and max points from vertices.
|
|
Vector3 min = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
|
|
Vector3 max = new Vector3(-Mathf.Infinity, -Mathf.Infinity, -Mathf.Infinity);
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
// Calc minimums
|
|
min.x = vertex.x < min.x ? vertex.x : min.x;
|
|
min.y = vertex.y < min.y ? vertex.y : min.y;
|
|
min.z = vertex.z < min.z ? vertex.z : min.z;
|
|
// Calc maximums
|
|
max.x = vertex.x > max.x ? vertex.x : max.x;
|
|
max.y = vertex.y > max.y ? vertex.y : max.y;
|
|
max.z = vertex.z > max.z ? vertex.z : max.z;
|
|
}
|
|
// Deltas for max-min
|
|
float dX = max.x - min.x;
|
|
float dY = max.y - min.y;
|
|
float dZ = max.z - min.z;
|
|
// center is between min and max values.
|
|
Vector3 center = (max + min) / 2;
|
|
int direction = 0;
|
|
float height = 0;
|
|
// set direction and height.
|
|
if (dX > dY && dX > dZ) // direction is x
|
|
{
|
|
direction = 0;
|
|
// height is the max difference in x.
|
|
height = dX;
|
|
}
|
|
else if (dY > dX && dY > dZ) // direction is y
|
|
{
|
|
direction = 1;
|
|
height = dY;
|
|
}
|
|
else // direction is z.
|
|
{
|
|
direction = 2;
|
|
height = dZ;
|
|
}
|
|
// Calculate radius, makes sure that all vertices are within the radius.
|
|
// Esentially to points on plane defined by direction axis, and find the furthest distance.
|
|
float maxRadius = -Mathf.Infinity;
|
|
Vector3 current = Vector3.zero;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
current = vertex;
|
|
if (direction == 0)
|
|
{
|
|
current.x = center.x;
|
|
}
|
|
else if (direction == 1)
|
|
{
|
|
current.y = center.y;
|
|
}
|
|
else if (direction == 2)
|
|
{
|
|
current.z = center.z;
|
|
}
|
|
float d = Vector3.Distance(current, center);
|
|
if (d > maxRadius)
|
|
{
|
|
maxRadius = d;
|
|
}
|
|
}
|
|
// method add radius / diameter
|
|
if (method == CAPSULE_COLLIDER_METHOD.MinMaxPlusRadius)
|
|
{
|
|
height += maxRadius;
|
|
}
|
|
else if (method == CAPSULE_COLLIDER_METHOD.MinMaxPlusDiameter)
|
|
{
|
|
height += maxRadius * 2;
|
|
}
|
|
CapsuleColliderData data = new CapsuleColliderData();
|
|
data.Center = center;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
|
|
data.Direction = direction;
|
|
data.Height = height;
|
|
data.IsValid = true;
|
|
data.Radius = maxRadius;
|
|
return data;
|
|
}
|
|
|
|
|
|
//TODO: Do a local-only method for cylinders.
|
|
|
|
/// <summary>
|
|
/// Calculates the data needed to create a cylinder shaped convex mesh collider using a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of selected world space vertices</param>
|
|
/// <param name="attachTo">transform the collider will be attached to</param>
|
|
/// <param name="numberOfSides">number of sides on the cylinder</param>
|
|
/// <returns>Data to create a a cylinder collider with type, convex mesh, validity, and matrix set</returns>
|
|
public MeshColliderData CalculateCylinderCollider(List<Vector3> worldVertices, Transform attachTo, int numberOfSides = 12, CYLINDER_ORIENTATION orientation = CYLINDER_ORIENTATION.Automatic, float cylinderOffset = 0.0f)
|
|
{
|
|
MeshColliderData data = new MeshColliderData();
|
|
// convert to local
|
|
List<Vector3> localVerts = ToLocalVerts(attachTo, worldVertices);
|
|
#if (UNITY_EDITOR)
|
|
List<Vector3> cylinderLocalPoints = CalculateCylinderPointsLocal(localVerts, attachTo, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
|
|
#else
|
|
List<Vector3> cylinderLocalPoints = CalculateCylinderPointsLocal(localVerts, attachTo, numberOfSides, orientation, cylinderOffset);
|
|
#endif
|
|
// build the mesh using quickhull and the cylinder points.
|
|
EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(cylinderLocalPoints);
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
|
|
data.ConvexMesh = qh.Result;
|
|
if (qh.Result != null)
|
|
{
|
|
data.IsValid = true;
|
|
}
|
|
data.Matrix = attachTo.transform.localToWorldMatrix;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the mesh collider data for a cylinder shaped convex mesh collider using a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="vertices">list of local space vertices</param>
|
|
/// <param name="numberOfSides">number of sides on the cylinder</param>
|
|
/// <param name="orientation">Automatic: Height is along largest axis. X,Y,Z orient height along X, Y, or Z respectively.</param>
|
|
/// <param name="cylinderOffset">angle to offset cylinder rotation in degrees.</param>
|
|
/// <returns>Mesh collider data with convex mesh set</returns>>
|
|
public MeshColliderData CalculateCylinderColliderLocal(List<Vector3> vertices, int numberOfSides = 12, CYLINDER_ORIENTATION orientation = CYLINDER_ORIENTATION.Automatic, float cylinderOffset = 0.0f)
|
|
{
|
|
MeshColliderData data = new MeshColliderData();
|
|
#if (UNITY_EDITOR)
|
|
List<Vector3> cylinderLocalPoints = CalculateCylinderPointsLocal(vertices, null, ECEPreferences.CylinderNumberOfSides, ECEPreferences.CylinderOrientation, ECEPreferences.CylinderRotationOffset);
|
|
#else
|
|
List<Vector3> cylinderLocalPoints = CalculateCylinderPointsLocal(vertices, null, numberOfSides, orientation, cylinderOffset);
|
|
#endif
|
|
EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(cylinderLocalPoints);
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
|
|
data.ConvexMesh = qh.Result;
|
|
if (qh.Result != null)
|
|
{
|
|
data.IsValid = true;
|
|
}
|
|
data.Matrix = new Matrix4x4();
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates mesh collider data for a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">list of local space vertices</param>
|
|
/// <returns>Mesh collider data with convex mesh set</returns>
|
|
public MeshColliderData CalculateMeshColliderQuickHullLocal(List<Vector3> localVertices)
|
|
{
|
|
MeshColliderData data = new MeshColliderData();
|
|
EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(localVertices);
|
|
data.ConvexMesh = qh.Result;
|
|
if (qh.Result != null)
|
|
{
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.CONVEX_MESH;
|
|
data.IsValid = true;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a sphere using the best fit method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertex positions in world space</param>
|
|
/// <param name="attachTo">transform sphere would be attached to</param>
|
|
/// <returns>Data with appropriate variables set for a sphere collider</returns>
|
|
public SphereColliderData CalculateSphereBestFit(List<Vector3> worldVertices, Transform attachTo)
|
|
{
|
|
if (worldVertices.Count < 2)
|
|
{
|
|
return new SphereColliderData();
|
|
}
|
|
List<Vector3> localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
// set data from values
|
|
SphereColliderData data = CalculateSphereBestFitLocal(localVertices);
|
|
data.Matrix = attachTo.localToWorldMatrix;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a best fit sphere collider using a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">list of local space vertices</param>
|
|
/// <returns>Sphere collider data with center and radius set</returns>
|
|
public SphereColliderData CalculateSphereBestFitLocal(List<Vector3> localVertices)
|
|
{
|
|
BestFitSphere bfs = CalculateBestFitSphere(localVertices);
|
|
// set data from values
|
|
SphereColliderData data = new SphereColliderData();
|
|
data.Center = bfs.Center;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
|
|
data.IsValid = true;
|
|
data.Radius = bfs.Radius;
|
|
return data;
|
|
}
|
|
|
|
|
|
// distance sphere is editor-only for now
|
|
/// <summary>
|
|
/// Calculates a sphere using the distance method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertex positions in world space</param>
|
|
/// <param name="attachTo">transform sphere would be attached to</param>
|
|
/// <returns>Data with appropriate variables set for a sphere collider</returns>
|
|
public SphereColliderData CalculateSphereDistance(List<Vector3> worldVertices, Transform attachTo)
|
|
{
|
|
if (worldVertices.Count < 2)
|
|
{
|
|
return new SphereColliderData();
|
|
}
|
|
List<Vector3> localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
// set data from values
|
|
SphereColliderData data = CalculateSphereDistanceLocal(localVertices);
|
|
data.Matrix = attachTo.localToWorldMatrix;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a sphere collider using a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">list of local space vertices</param>
|
|
/// <returns>Sphere collider data with center and radius</returns>
|
|
public SphereColliderData CalculateSphereDistanceLocal(List<Vector3> localVertices)
|
|
{
|
|
// if calculations take to long, it switches to a faster less accurate algorithm using the mean.
|
|
bool switchToFasterAlgorithm = false;
|
|
#if (UNITY_EDITOR)
|
|
double startTime = EditorApplication.timeSinceStartup;
|
|
#else
|
|
double startTime = Time.realtimeSinceStartup;
|
|
#endif
|
|
double maxTime = 0.1f;
|
|
Vector3 distanceVert1 = Vector3.zero;
|
|
Vector3 distanceVert2 = Vector3.zero;
|
|
float maxDistance = -Mathf.Infinity;
|
|
float distance = 0;
|
|
for (int i = 0; i < localVertices.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < localVertices.Count; j++)
|
|
{
|
|
distance = Vector3.Distance(localVertices[i], localVertices[j]);
|
|
if (distance > maxDistance)
|
|
{
|
|
maxDistance = distance;
|
|
distanceVert1 = localVertices[i];
|
|
distanceVert2 = localVertices[j];
|
|
}
|
|
}
|
|
#if (UNITY_EDITOR)
|
|
if (EditorApplication.timeSinceStartup - startTime > maxTime)
|
|
{
|
|
switchToFasterAlgorithm = true;
|
|
break;
|
|
}
|
|
#else
|
|
if (Time.realtimeSinceStartup - startTime > maxTime)
|
|
{
|
|
switchToFasterAlgorithm = true;
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
if (switchToFasterAlgorithm)
|
|
{
|
|
// use a significantly faster algorithm that is less accurate for a large # of points.
|
|
Vector3 mean = Vector3.zero;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
mean += vertex;
|
|
}
|
|
mean = mean / localVertices.Count;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
distance = Vector3.Distance(vertex, mean);
|
|
if (distance > maxDistance)
|
|
{
|
|
distanceVert1 = vertex;
|
|
maxDistance = distance;
|
|
}
|
|
}
|
|
maxDistance = -Mathf.Infinity;
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
distance = Vector3.Distance(vertex, distanceVert1);
|
|
if (distance > maxDistance)
|
|
{
|
|
maxDistance = distance;
|
|
distanceVert2 = vertex;
|
|
}
|
|
}
|
|
}
|
|
// set data from values
|
|
SphereColliderData data = new SphereColliderData();
|
|
data.Center = (distanceVert1 + distanceVert2) / 2;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
|
|
data.IsValid = true;
|
|
data.Radius = maxDistance / 2;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a sphere using the min max method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">list of vertex positions in world space</param>
|
|
/// <param name="attachTo">transform sphere would be attached to</param>
|
|
/// <returns>Data with appropriate variables set for a sphere collider</returns>
|
|
public SphereColliderData CalculateSphereMinMax(List<Vector3> worldVertices, Transform attachTo)
|
|
{
|
|
if (worldVertices.Count < 2)
|
|
{
|
|
return new SphereColliderData();
|
|
}
|
|
// use local space verts.
|
|
List<Vector3> localVertices = ToLocalVerts(attachTo, worldVertices);
|
|
SphereColliderData data = CalculateSphereMinMaxLocal(localVertices);
|
|
data.Matrix = attachTo.localToWorldMatrix;
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates a sphere collider using a list of local space vertices
|
|
/// </summary>
|
|
/// <param name="localVertices">local space vertices</param>
|
|
/// <returns>Sphere collider data with center and radius set</returns>
|
|
public SphereColliderData CalculateSphereMinMaxLocal(List<Vector3> localVertices)
|
|
{
|
|
float xMin, yMin, zMin = xMin = yMin = Mathf.Infinity;
|
|
float xMax, yMax, zMax = xMax = yMax = -Mathf.Infinity;
|
|
for (int i = 0; i < localVertices.Count; i++)
|
|
{
|
|
//x min & max.
|
|
xMin = (localVertices[i].x < xMin) ? localVertices[i].x : xMin;
|
|
xMax = (localVertices[i].x > xMax) ? localVertices[i].x : xMax;
|
|
//y min & max
|
|
yMin = (localVertices[i].y < yMin) ? localVertices[i].y : yMin;
|
|
yMax = (localVertices[i].y > yMax) ? localVertices[i].y : yMax;
|
|
//z min & max
|
|
zMin = (localVertices[i].z < zMin) ? localVertices[i].z : zMin;
|
|
zMax = (localVertices[i].z > zMax) ? localVertices[i].z : zMax;
|
|
}
|
|
// calculate center
|
|
Vector3 center = (new Vector3(xMin, yMin, zMin) + new Vector3(xMax, yMax, zMax)) / 2;
|
|
// calculate radius to contain all points
|
|
float maxDistance = 0.0f;
|
|
float distance = 0.0f;
|
|
// this is what makes it slightly differnt than just converting a box into a sphere.
|
|
foreach (Vector3 vertex in localVertices)
|
|
{
|
|
distance = Vector3.Distance(vertex, center);
|
|
if (distance > maxDistance)
|
|
{
|
|
maxDistance = distance;
|
|
}
|
|
}
|
|
SphereColliderData data = new SphereColliderData();
|
|
data.Center = center;
|
|
data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
|
|
data.IsValid = true;
|
|
data.Radius = maxDistance;
|
|
return data;
|
|
}
|
|
|
|
#endregion
|
|
|
|
// editor only
|
|
#region CreateMeshColliders
|
|
#if (UNITY_EDITOR)
|
|
/// <summary>
|
|
/// Create and saves a mesh with a minimum number of triangles that includes all selected vertices. Editor only.
|
|
/// </summary>
|
|
/// <param name="SavePath">Full path to save location including a base object name ie: "C:/UnityProjects/ProjectName/Assets/ConvexHulls/SaveNameBase"</param>
|
|
/// <param name="worldSpaceVertices">Vertices to create the mesh with in world space</param>
|
|
/// <param name="attachTo">Gameobject the mesh will be attached to</param>
|
|
/// <returns>The created mesh</returns>
|
|
public Mesh CreateMesh_Messy(List<Vector3> worldSpaceVertices, GameObject attachTo, GameObject selected)
|
|
{
|
|
// use vertices to make a useable mesh that contains all the selected points.
|
|
// The mesh is only used to generate the convex hull
|
|
Mesh mesh = new Mesh();
|
|
// get all vertices in world space and convert to local space.
|
|
List<Vector3> localVertices = worldSpaceVertices.Select(vertex => attachTo.transform.InverseTransformPoint(vertex)).ToList();
|
|
while (localVertices.Count % 3 != 0)
|
|
{
|
|
localVertices.Add(localVertices[localVertices.Count % 3]);
|
|
}
|
|
// attempt to deal with degenerate triangles (so if user changes the mesh collider flags manually, no crashes will occur)
|
|
Vector3 p0, p1, p2 = p1 = p0 = Vector3.zero;
|
|
Vector3 s1, s2 = s1 = Vector3.zero;
|
|
List<Vector3> verts = new List<Vector3>();
|
|
int index = localVertices.Count - 1;
|
|
while (index >= 0) // need to make sure we include the last vertex.
|
|
{
|
|
p0 = localVertices[index];
|
|
p1 = localVertices[(index - 1 >= 0) ? index - 1 : localVertices.Count - 1];
|
|
p2 = localVertices[(index - 2 >= 0) ? index - 2 : localVertices.Count - 2];
|
|
s1 = (p0 - p1).normalized;
|
|
s2 = (p0 - p2).normalized;
|
|
int degenIndex = localVertices.Count; // so we can automatically re-use the last vertices if needed.
|
|
bool degenFixed = false;
|
|
while (s1 == s2 || -s1 == s2 || (s2 == Vector3.zero && s1 != Vector3.zero))
|
|
{
|
|
degenFixed = true;
|
|
degenIndex--;
|
|
if (degenIndex < 0)
|
|
{
|
|
Debug.LogError("Easy Collider Editor: Unable to generate a valid mesh collider from the selected points. This happens when all points are in a straight line.");
|
|
return null;
|
|
}
|
|
p2 = localVertices[degenIndex];
|
|
s2 = (p0 - p2).normalized;
|
|
}
|
|
// if we fixed a degenerate we still need to do the last vertex, so only move back 2 indexs' in that case.
|
|
index -= degenFixed ? 2 : 3;
|
|
verts.Add(p0);
|
|
verts.Add(p1);
|
|
verts.Add(p2);
|
|
}
|
|
|
|
int[] triangles = new int[verts.Count];
|
|
for (int i = 0; i < verts.Count; i++)
|
|
{
|
|
triangles[i] = i;
|
|
}
|
|
// mesh.vertices = vertices;
|
|
mesh.vertices = verts.ToArray();
|
|
mesh.triangles = triangles;
|
|
// the mesh has to be saved somewhere so it can actually be used (although this is still just optional)
|
|
try
|
|
{
|
|
if (selected == null)
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(mesh, attachTo);
|
|
}
|
|
else
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(mesh, selected);
|
|
}
|
|
return mesh;
|
|
}
|
|
catch
|
|
{
|
|
Debug.LogError("EasyColliderEditor: Error saving mesh at path:" + EasyColliderSaving.GetValidConvexHullPath(attachTo));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates and saves (if set in preferences) a convex mesh collider using QuickHull. Editor only.
|
|
/// </summary>
|
|
/// <param name="vertices">Local or world space vertices</param>
|
|
/// <param name="attachTo">Gameobject the collider will be attached to</param>
|
|
/// <param name="isLocal">are the vertices already in local space?</param>
|
|
/// <returns></returns>
|
|
public Mesh CreateMesh_QuickHull(List<Vector3> vertices, GameObject attachTo, bool isLocal = false, GameObject selected = null)
|
|
{
|
|
List<Vector3> localVerts = isLocal ? vertices : ToLocalVerts(attachTo.transform, vertices);
|
|
EasyColliderQuickHull qh = EasyColliderQuickHull.CalculateHull(localVerts);
|
|
if (ECEPreferences.SaveConvexHullAsAsset)
|
|
{
|
|
if (selected == null)
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(qh.Result, attachTo);
|
|
}
|
|
else
|
|
{
|
|
EasyColliderSaving.CreateAndSaveMeshAsset(qh.Result, selected);
|
|
}
|
|
}
|
|
return qh.Result;
|
|
}
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
// creating colliders uses undos, the data itself can be used during runtime.
|
|
#region CreatePrimitiveColliders
|
|
|
|
/// <summary>
|
|
/// Creates a Box collider
|
|
/// </summary>
|
|
/// <param name="data">data to create box from</param>
|
|
/// <param name="properties">properties to set on collider</param>
|
|
/// <returns>Created collider</returns>
|
|
private BoxCollider CreateBoxCollider(BoxColliderData data, EasyColliderProperties properties, bool postProcess = true)
|
|
{
|
|
#if (UNITY_EDITOR)
|
|
BoxCollider boxCollider;
|
|
if (UndoEnabled)
|
|
{
|
|
boxCollider = Undo.AddComponent<BoxCollider>(properties.AttachTo);
|
|
}
|
|
else
|
|
{
|
|
boxCollider = properties.AttachTo.AddComponent<BoxCollider>();
|
|
}
|
|
#else
|
|
BoxCollider boxCollider = properties.AttachTo.AddComponent<BoxCollider>();
|
|
#endif
|
|
boxCollider.size = data.Size;
|
|
boxCollider.center = data.Center;
|
|
PostColliderCreation(boxCollider, properties, postProcess);
|
|
return boxCollider;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Creates a box collider using a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="vertices">List of world space vertices</param>
|
|
/// <param name="properties">Properties of collider</param>
|
|
/// <returns>The created box collider</returns>
|
|
public BoxCollider CreateBoxCollider(List<Vector3> vertices, EasyColliderProperties properties, bool isLocal = false)
|
|
{
|
|
if (vertices.Count >= 2)
|
|
{
|
|
BoxColliderData data;
|
|
if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
if (vertices.Count >= 3)
|
|
{
|
|
GameObject obj = CreateGameObjectOrientation(vertices, properties.AttachTo, "Rotated Box Collider");
|
|
// still want to recalculate using the transform matrix, as it better handles uneven scale / shearing across multiple children
|
|
if (obj != null)
|
|
{
|
|
obj.layer = properties.Layer;
|
|
properties.AttachTo = obj;
|
|
}
|
|
data = CalculateBox(vertices, properties.AttachTo.transform, true);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Easy Collider Editor: Creating a Rotated Box Collider requires at least 3 points to be selected.");
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isLocal)
|
|
{
|
|
data = CalculateBox(vertices, properties.AttachTo.transform);
|
|
}
|
|
else
|
|
{
|
|
data = CalculateBoxLocal(vertices);
|
|
}
|
|
}
|
|
return CreateBoxCollider(data, properties);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a capsule collider (editor undoable)
|
|
/// </summary>
|
|
/// <param name="data">data to create capsule from</param>
|
|
/// <param name="properties">properties to set on collider</param>
|
|
/// <returns>created capsule collider</returns>
|
|
private CapsuleCollider CreateCapsuleCollider(CapsuleColliderData data, EasyColliderProperties properties, bool postProcess = true)
|
|
{
|
|
#if (UNITY_EDITOR)
|
|
CapsuleCollider capsuleCollider;
|
|
if (UndoEnabled)
|
|
{
|
|
capsuleCollider = Undo.AddComponent<CapsuleCollider>(properties.AttachTo);
|
|
}
|
|
else
|
|
{
|
|
capsuleCollider = properties.AttachTo.AddComponent<CapsuleCollider>();
|
|
}
|
|
#else
|
|
CapsuleCollider capsuleCollider = properties.AttachTo.AddComponent<CapsuleCollider>();
|
|
#endif
|
|
capsuleCollider.direction = data.Direction;
|
|
capsuleCollider.height = data.Height;
|
|
capsuleCollider.center = data.Center;
|
|
capsuleCollider.radius = data.Radius;
|
|
// set properties
|
|
PostColliderCreation(capsuleCollider, properties);
|
|
return capsuleCollider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a capsule collider usintg the best fit algorithm and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world vertices</param>
|
|
/// <param name="properties">Properties of collider</param>
|
|
/// <returns>The created capsule collider</returns>
|
|
public CapsuleCollider CreateCapsuleCollider_BestFit(List<Vector3> worldVertices, EasyColliderProperties properties)
|
|
{
|
|
if (worldVertices.Count >= 3)
|
|
{
|
|
CapsuleColliderData data = new CapsuleColliderData();
|
|
if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
GameObject obj = CreateGameObjectOrientation(worldVertices, properties.AttachTo, "Rotated Capsule Collider");
|
|
if (obj != null)
|
|
{
|
|
properties.AttachTo = obj;
|
|
obj.layer = properties.Layer;
|
|
}
|
|
data = CalculateCapsuleBestFit(worldVertices, properties.AttachTo.transform, true);
|
|
}
|
|
else
|
|
{
|
|
data = CalculateCapsuleBestFit(worldVertices, properties.AttachTo.transform, false);
|
|
}
|
|
return CreateCapsuleCollider(data, properties);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a capsule collider using the Min-Max method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world space vertices</param>
|
|
/// <param name="properties">Properties to set on collider</param>
|
|
/// <param name="method">Min-Max method to use to add radius' to height.</param>
|
|
/// <returns>The created capsule collider</returns>
|
|
public CapsuleCollider CreateCapsuleCollider_MinMax(List<Vector3> worldVertices, EasyColliderProperties properties, CAPSULE_COLLIDER_METHOD method, bool isLocal = false)
|
|
{
|
|
CapsuleColliderData data;
|
|
if (properties.Orientation == COLLIDER_ORIENTATION.ROTATED && worldVertices.Count >= 3)
|
|
{
|
|
GameObject obj = CreateGameObjectOrientation(worldVertices, properties.AttachTo, "Rotated Capsule Collider");
|
|
if (obj != null)
|
|
{
|
|
properties.AttachTo = obj;
|
|
obj.layer = properties.AttachTo.layer;
|
|
}
|
|
data = CalculateCapsuleMinMax(worldVertices, properties.AttachTo.transform, method, true);
|
|
}
|
|
else
|
|
{
|
|
if (!isLocal)
|
|
{
|
|
data = CalculateCapsuleMinMax(worldVertices, properties.AttachTo.transform, method, false);
|
|
}
|
|
else
|
|
{
|
|
data = CalculateCapsuleMinMaxLocal(worldVertices, method);
|
|
}
|
|
}
|
|
return CreateCapsuleCollider(data, properties);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a convex mesh collider component from the mesh using all cooking options, so mesh does not have to be "valid"
|
|
/// </summary>
|
|
/// <param name="mesh">Mesh to make a convex hull from</param>
|
|
/// <param name="attachToObject">Gameobject the convex hull will be attached to</param>
|
|
/// <param name="properties">Parameters to set on created collider</param>
|
|
public MeshCollider CreateConvexMeshCollider(Mesh mesh, GameObject attachToObject, EasyColliderProperties properties, bool postProcess = true)
|
|
{
|
|
// Create a mesh collider
|
|
#if (UNITY_EDITOR)
|
|
MeshCollider createdCollider = Undo.AddComponent<MeshCollider>(attachToObject);
|
|
#else
|
|
MeshCollider createdCollider = attachToObject.AddComponent<MeshCollider>();
|
|
#endif
|
|
createdCollider.sharedMesh = mesh;
|
|
//enable all cooking options.
|
|
#if UNITY_2018_3_OR_NEWER
|
|
createdCollider.cookingOptions = ~MeshColliderCookingOptions.None;
|
|
#elif UNITY_2017_3_OR_NEWER
|
|
createdCollider.cookingOptions = ~MeshColliderCookingOptions.None;
|
|
// Auto inflate mesh to the minimum amount
|
|
createdCollider.skinWidth = 0.000001f;
|
|
#else
|
|
// for very old unity versions that didn't have cooking options.
|
|
createdCollider.inflateMesh = true;
|
|
createdCollider.skinWidth = 0.000001f;
|
|
#endif
|
|
// Would be nice if we could do a try/catch on the baking to only inflate if we have to, but that doesn't work.
|
|
createdCollider.convex = true;
|
|
PostColliderCreation(createdCollider, properties, postProcess);
|
|
//disable read/write to save memory based on user preferences.
|
|
//for limitations see bottom of: https://docs.unity3d.com/Manual/class-MeshCollider.html
|
|
#if (UNITY_EDITOR)
|
|
if (!ECEPreferences.ConvexMeshReadWriteEnabled)
|
|
{
|
|
mesh.UploadMeshData(true);
|
|
// fixes issue where mesh read/write was not correctly saving.
|
|
EditorUtility.SetDirty(mesh);
|
|
}
|
|
#endif
|
|
return createdCollider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a sphere collider, editor undo-able
|
|
/// </summary>
|
|
/// <param name="data">data to create the sphere collider from</param>
|
|
/// <param name="properties">properties to set on the collider</param>
|
|
/// <returns>the created sphere collider</returns>
|
|
private SphereCollider CreateSphereCollider(SphereColliderData data, EasyColliderProperties properties, bool postProcess = true)
|
|
{
|
|
#if (UNITY_EDITOR)
|
|
SphereCollider sphereCollider;
|
|
if (UndoEnabled)
|
|
{
|
|
sphereCollider = Undo.AddComponent<SphereCollider>(properties.AttachTo);
|
|
}
|
|
else
|
|
{
|
|
sphereCollider = properties.AttachTo.AddComponent<SphereCollider>();
|
|
}
|
|
#else
|
|
SphereCollider sphereCollider = properties.AttachTo.AddComponent<SphereCollider>();
|
|
#endif
|
|
sphereCollider.radius = data.Radius;
|
|
sphereCollider.center = data.Center;
|
|
PostColliderCreation(sphereCollider, properties, postProcess);
|
|
return sphereCollider;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a sphere collider using the best fit sphere algorithm and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world space vertices</param>
|
|
/// <param name="properties">Properties of collider</param>
|
|
/// <returns></returns>
|
|
public SphereCollider CreateSphereCollider_BestFit(List<Vector3> worldVertices, EasyColliderProperties properties)
|
|
{
|
|
if (worldVertices.Count >= 2)
|
|
{
|
|
// Convert to local space.
|
|
SphereColliderData data = CalculateSphereBestFit(worldVertices, properties.AttachTo.transform);
|
|
return CreateSphereCollider(data, properties);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a Sphere Collider using the distance method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world space vertices</param>
|
|
/// <param name="properties">Properties of collider</param>
|
|
/// <returns></returns>
|
|
public SphereCollider CreateSphereCollider_Distance(List<Vector3> worldVertices, EasyColliderProperties properties)
|
|
{
|
|
if (worldVertices.Count >= 2)
|
|
{
|
|
SphereColliderData data = CalculateSphereDistance(worldVertices, properties.AttachTo.transform);
|
|
return CreateSphereCollider(data, properties);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a sphere collider using the min max method and a list of world space vertices
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world space vertices</param>
|
|
/// <param name="properties">Properties of collider</param>
|
|
/// <returns></returns>
|
|
public SphereCollider CreateSphereCollider_MinMax(List<Vector3> worldVertices, EasyColliderProperties properties, bool isLocal = false)
|
|
{
|
|
if (worldVertices.Count >= 2)
|
|
{
|
|
if (!isLocal)
|
|
{
|
|
SphereColliderData data = CalculateSphereMinMax(worldVertices, properties.AttachTo.transform);
|
|
return CreateSphereCollider(data, properties);
|
|
}
|
|
else
|
|
{
|
|
SphereColliderData data = CalculateSphereMinMaxLocal(worldVertices);
|
|
return CreateSphereCollider(data, properties);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PostCreationProcessing
|
|
|
|
/// <summary>
|
|
/// can be used at runtime to set a specific custom post processor if desired, otherwise defaults to EasyColliderPostProccessor
|
|
/// </summary>
|
|
public static IEasyColliderPostProcessor EasyColliderPostProcessor;
|
|
|
|
/// <summary>
|
|
/// Can add any custom processing to a manually created collider here.
|
|
/// Current this handles re-centering the pivot of colliders if specified in preferences. (IE: always make collider holders & pivot at center)
|
|
/// These should only be things that have show no visual effect on the collider preview.
|
|
/// IE: things like rotating and re-aligning a collider along the new axis'. Or recentering the position of a collider center to 0, and moving it's collider holder to compensate.
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <param name="properties"></param>
|
|
public void PostColliderCreationProcess(Collider createdCollider, EasyColliderProperties properties)
|
|
{
|
|
// set to default if null
|
|
if (EasyColliderPostProcessor == null) { EasyColliderPostProcessor = new EasyColliderPostProccessor(); }
|
|
|
|
if (createdCollider is BoxCollider)
|
|
{
|
|
// adjust pivot to the center of the collider if specified.
|
|
BoxCollider bc = (BoxCollider)createdCollider;
|
|
#if(UNITY_EDITOR)
|
|
if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
bc.transform.position = bc.transform.TransformPoint(bc.center);
|
|
bc.center = Vector3.zero;
|
|
// bc.transform.localPosition = bc.center;
|
|
// bc.center = Vector3.zero;
|
|
}
|
|
#endif
|
|
// handle other box related things in future here.
|
|
if (EasyColliderPostProcessor != null)
|
|
{
|
|
EasyColliderPostProcessor.PostProcessCollider(bc, properties);
|
|
}
|
|
|
|
}
|
|
else if (createdCollider is SphereCollider)
|
|
{
|
|
SphereCollider sc = (SphereCollider)createdCollider;
|
|
#if(UNITY_EDITOR)
|
|
if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
sc.transform.position = sc.transform.TransformPoint(sc.center);
|
|
sc.center = Vector3.zero;
|
|
}
|
|
#endif
|
|
//handle other sphere collider things here.
|
|
if (EasyColliderPostProcessor != null)
|
|
{
|
|
EasyColliderPostProcessor.PostProcessCollider(sc, properties);
|
|
}
|
|
}
|
|
else if (createdCollider is CapsuleCollider)
|
|
{
|
|
CapsuleCollider cc = (CapsuleCollider)createdCollider;
|
|
#if (UNITY_EDITOR)
|
|
Transform t = cc.transform;
|
|
if (ECEPreferences.RotatedColliderPivotAtCenter && properties.Orientation == COLLIDER_ORIENTATION.ROTATED)
|
|
{
|
|
cc.transform.position = cc.transform.TransformPoint(cc.center);
|
|
cc.center = Vector3.zero;
|
|
}
|
|
if (ECEPreferences.CylinderAsCapsuleOrientation)
|
|
{
|
|
// aligning to cylinder axis as well.
|
|
int dir = cc.direction;
|
|
int prefDir = (int)ECEPreferences.CylinderOrientation - 1;
|
|
// not set to automatic alignment, so user wants a specific alignment (above would make it -1)
|
|
if (prefDir >= 0 && prefDir != dir)
|
|
{
|
|
Vector3 forward = Vector3.zero;
|
|
Vector3 up = Vector3.zero;
|
|
if (dir == 0)
|
|
{
|
|
// currently aligned with x-axis (right)
|
|
if (prefDir == 1) // up
|
|
{
|
|
forward = t.transform.up;
|
|
up = t.transform.right;
|
|
}
|
|
else if (prefDir == 2) // forward
|
|
{
|
|
forward = t.transform.right;
|
|
up = t.transform.forward;
|
|
}
|
|
}
|
|
else if (dir == 1)
|
|
{
|
|
// current aligned with y-axis (up)
|
|
if (prefDir == 0) // right
|
|
{
|
|
forward = t.transform.right;
|
|
up = t.transform.forward;
|
|
}
|
|
else if (prefDir == 2) // forward
|
|
{
|
|
forward = t.transform.up;
|
|
up = t.transform.right;
|
|
}
|
|
}
|
|
else if (dir == 2)
|
|
{
|
|
if (prefDir == 0) // right
|
|
{
|
|
forward = t.transform.right;
|
|
up = -t.transform.up;
|
|
}
|
|
else if (prefDir == 1) // up
|
|
{
|
|
forward = t.transform.up;
|
|
up = t.transform.forward;
|
|
}
|
|
}
|
|
|
|
if (!cc.transform.name.Contains("Rotated Capsule Collider"))
|
|
{
|
|
// need to create a collider holder to align if it's not a rotated collider.
|
|
GameObject o = new GameObject("EasyColliderHolder");
|
|
Undo.RegisterCreatedObjectUndo(o, "Create Collider Holder");
|
|
o.transform.SetParent(cc.transform);
|
|
// move the capsule to the new object.
|
|
CapsuleCollider replacedCapsule = Undo.AddComponent<CapsuleCollider>(o);
|
|
replacedCapsule.center = cc.center;
|
|
replacedCapsule.radius = cc.radius;
|
|
replacedCapsule.direction = cc.direction;
|
|
replacedCapsule.height = cc.height;
|
|
GameObject.DestroyImmediate(cc);
|
|
// correct it's position.
|
|
cc = replacedCapsule;
|
|
cc.transform.localPosition = cc.center;
|
|
cc.center = Vector3.zero;
|
|
// correct it's rotation and direction to align.
|
|
cc.transform.rotation = Quaternion.LookRotation(forward, up);
|
|
cc.direction = prefDir;
|
|
}
|
|
else
|
|
{
|
|
if (!ECEPreferences.RotatedColliderPivotAtCenter)
|
|
{
|
|
// the pivot isn't at center, so we need to transform the point to world space, then back to local after rotating it's transform.
|
|
Vector3 wsCenter = cc.transform.TransformPoint(cc.center);
|
|
cc.transform.rotation = Quaternion.LookRotation(forward, up);
|
|
cc.center = cc.transform.InverseTransformPoint(wsCenter);
|
|
cc.direction = prefDir;
|
|
}
|
|
else
|
|
{
|
|
// the pivot is already at the center, so we're good, just align it.
|
|
cc.transform.rotation = Quaternion.LookRotation(forward, up);
|
|
cc.direction = prefDir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (EasyColliderPostProcessor != null)
|
|
{
|
|
EasyColliderPostProcessor.PostProcessCollider(cc, properties);
|
|
}
|
|
}
|
|
else if (createdCollider is MeshCollider)
|
|
{
|
|
MeshCollider mc = (MeshCollider)createdCollider;
|
|
if (EasyColliderPostProcessor != null)
|
|
{
|
|
EasyColliderPostProcessor.PostProcessCollider(mc, properties);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region OtherHelperMethods
|
|
|
|
public List<Vector3> CalculateCylinderPointsLocal(List<Vector3> vertices, Transform attachTo, int numberOfSides, CYLINDER_ORIENTATION orientation, float cylinderOffset)
|
|
{
|
|
BoxColliderData data = CalculateBoxLocal(vertices);
|
|
// calculate height and direction based on the height.
|
|
// height is the max value between the box's directions.
|
|
float height = 0.0f;
|
|
// direction is the height's axis
|
|
int direction = 0;
|
|
if (orientation == CYLINDER_ORIENTATION.Automatic)
|
|
{
|
|
// calculate height as max of each axis, and direction as whichever value it is.
|
|
height = Mathf.Max(Mathf.Max(data.Size.x, data.Size.y), data.Size.z);
|
|
direction = (height == data.Size.x) ? 0 : (height == data.Size.y) ? 1 : 2;
|
|
}
|
|
else
|
|
{
|
|
height = data.Size[(int)orientation - 1]; // x=1, y=2, z=3 to their vector3 indexs.
|
|
direction = (int)orientation - 1; // direction is also the same
|
|
}
|
|
// calculate max distance to the center of the box.
|
|
float distance = 0.0f;
|
|
// max distance is the radius
|
|
float maxDistance = 0.0f;
|
|
Vector3 current = Vector3.zero;
|
|
|
|
// calculate radius for the given dimensions by squasing along height axis and measuring distance.
|
|
foreach (Vector3 v in vertices)
|
|
{
|
|
current.x = (direction == 0) ? data.Center.x : v.x;
|
|
current.y = (direction == 1) ? data.Center.y : v.y;
|
|
current.z = (direction == 2) ? data.Center.z : v.z;
|
|
distance = Vector3.Distance(current, data.Center);
|
|
if (distance > maxDistance) maxDistance = distance;
|
|
}
|
|
|
|
// half height for offset of points from center.
|
|
float halfHeight = height / 2;
|
|
// amount to increment the angle when adding points.
|
|
float angleIncrement = 360f / numberOfSides;
|
|
// top and bottom points to build a circle around.
|
|
Vector3 top, bottom = top = data.Center;
|
|
// adjust the top / bottom x or y or z depending on direction by the half height.
|
|
top.x = (direction == 0) ? top.x + halfHeight : top.x;
|
|
top.y = (direction == 1) ? top.y + halfHeight : top.y;
|
|
top.z = (direction == 2) ? top.z + halfHeight : top.z;
|
|
bottom.x = (direction == 0) ? bottom.x - halfHeight : bottom.x;
|
|
bottom.y = (direction == 1) ? bottom.y - halfHeight : bottom.y;
|
|
bottom.z = (direction == 2) ? bottom.z - halfHeight : bottom.z;
|
|
// list of points
|
|
List<Vector3> points = new List<Vector3>();
|
|
for (float a = 0 + cylinderOffset; a < 360f + cylinderOffset; a += angleIncrement)
|
|
{
|
|
// calculate pair of offset to use for a circle
|
|
float b = maxDistance * Mathf.Sin(a * Mathf.Deg2Rad);
|
|
float c = maxDistance * Mathf.Cos(a * Mathf.Deg2Rad);
|
|
// only adjust the axis if it is not the height axis.
|
|
if (direction == 0)
|
|
{
|
|
top.y = b + data.Center.y;
|
|
top.z = c + data.Center.z;
|
|
bottom.y = b + data.Center.y;
|
|
bottom.z = c + data.Center.z;
|
|
}
|
|
else if (direction == 1)
|
|
{
|
|
top.x = b + data.Center.x;
|
|
top.z = c + data.Center.z;
|
|
bottom.x = b + data.Center.x;
|
|
bottom.z = c + data.Center.z;
|
|
}
|
|
else
|
|
{
|
|
top.y = b + data.Center.y;
|
|
top.x = c + data.Center.x;
|
|
bottom.y = b + data.Center.y;
|
|
bottom.x = c + data.Center.x;
|
|
}
|
|
points.Add(top);
|
|
points.Add(bottom);
|
|
}
|
|
return points;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a gameobject attach to parent with it's local position at zero, and it's up direction oriented in the direction of the first 2 world vertices.
|
|
/// </summary>
|
|
/// <param name="worldVertices">List of world space vertices</param>
|
|
/// <param name="parent">Parent to attach gameobject to</param>
|
|
/// <param name="name">Name of gameobject to create</param>
|
|
/// <returns></returns>
|
|
private GameObject CreateGameObjectOrientation(List<Vector3> worldVertices, GameObject parent, string name)
|
|
{
|
|
GameObject obj = new GameObject(name);
|
|
if (worldVertices.Count >= 3)
|
|
{
|
|
// calculate forward and up.
|
|
Vector3 forward = worldVertices[1] - worldVertices[0];
|
|
Vector3 up = Vector3.Cross(forward, worldVertices[2] - worldVertices[1]);
|
|
obj.transform.rotation = Quaternion.LookRotation(forward, up);
|
|
obj.transform.SetParent(parent.transform);
|
|
obj.transform.localPosition = Vector3.zero;
|
|
// we definitely want the local scale of a rotated object to be uniform 1,1,1
|
|
obj.transform.localScale = Vector3.one;
|
|
#if (UNITY_EDITOR) // Rotated collider pivot at center is editor-only,
|
|
if (ECEPreferences.RotatedColliderPivotAtCenter)
|
|
{
|
|
Vector3 min = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
|
|
Vector3 max = new Vector3(-Mathf.Infinity, -Mathf.Infinity, -Mathf.Infinity);
|
|
foreach (Vector3 v in worldVertices)
|
|
{
|
|
Vector3 localV = obj.transform.InverseTransformPoint(v);
|
|
min.x = localV.x < min.x ? localV.x : min.x;
|
|
min.y = localV.y < min.y ? localV.y : min.y;
|
|
min.z = localV.z < min.z ? localV.z : min.z;
|
|
|
|
max.x = localV.x > max.x ? localV.x : max.x;
|
|
max.y = localV.y > max.y ? localV.y : max.y;
|
|
max.z = localV.z > max.z ? localV.z : max.z;
|
|
}
|
|
Vector3 center = (max + min) / 2;
|
|
center = obj.transform.TransformPoint(center);
|
|
center = parent.transform.InverseTransformPoint(center);
|
|
obj.transform.localPosition = center;
|
|
}
|
|
Undo.RegisterCreatedObjectUndo(obj, "Create Rotated GameObject");
|
|
#endif
|
|
return obj;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Just a helper method to draw a point in world space
|
|
/// </summary>
|
|
/// <param name="worldLoc"></param>
|
|
/// <param name="color"></param>
|
|
private void DebugDrawPoint(Vector3 worldLoc, Color color, float dist = 0.01f)
|
|
{
|
|
Debug.DrawLine(worldLoc - Vector3.up * dist, worldLoc + Vector3.up * dist, color, 0.01f, false);
|
|
Debug.DrawLine(worldLoc - Vector3.left * dist, worldLoc + Vector3.left * dist, color, 0.01f, false);
|
|
Debug.DrawLine(worldLoc - Vector3.forward * dist, worldLoc + Vector3.forward * dist, color, 0.01f, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A method that can be ran on any collider for things like setting collider properties or post processing.
|
|
/// </summary>
|
|
/// <param name="collider">Collider that was created</param>
|
|
/// <param name="properties">Properties object with the properties to set</param>
|
|
private void PostColliderCreation(Collider collider, EasyColliderProperties properties, bool postProcess = true)
|
|
{
|
|
SetPropertiesOnCollider(collider, properties);
|
|
if (postProcess)
|
|
{
|
|
PostColliderCreationProcess(collider, properties);
|
|
}
|
|
}
|
|
|
|
private void SetPropertiesOnCollider(Collider collider, EasyColliderProperties properties)
|
|
{
|
|
if (collider != null)
|
|
{
|
|
collider.isTrigger = properties.IsTrigger;
|
|
collider.sharedMaterial = properties.PhysicMaterial;
|
|
|
|
#if (UNITY_2022_2_OR_NEWER)
|
|
collider.layerOverridePriority = properties.LayerOverridePriority;
|
|
collider.includeLayers = properties.IncludeLayers;
|
|
collider.excludeLayers = properties.ExcludeLayers;
|
|
collider.providesContacts = properties.ProvidesContacts;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the list of world vertices to local positions
|
|
/// </summary>
|
|
/// <param name="transform">Transform to use for local space</param>
|
|
/// <param name="worldVertices">World space position of vertices</param>
|
|
/// <returns>Localspace position w.r.t transform of worldVertices</returns>
|
|
private List<Vector3> ToLocalVerts(Transform transform, List<Vector3> worldVertices)
|
|
{
|
|
List<Vector3> localVerts = new List<Vector3>(worldVertices.Count);
|
|
foreach (Vector3 v in worldVertices)
|
|
{
|
|
localVerts.Add(transform.InverseTransformPoint(v));
|
|
}
|
|
return localVerts;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|