#if (UNITY_EDITOR)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace ECE
{
///
/// Scriptable object to preview colliders that would be created from the given points
///
[System.Serializable]
public class EasyColliderPreviewer : ScriptableObject
{
///
/// Current calculation method of the collider type we are using as an int
///
public int CurrentMethod = 0;
///
/// Current attach to transform used in the calculation
///
public Transform CurrentAttachTo;
///
/// Current vertex count for the last calculation we did for a preview
///
public int CurrentVertexCount = 0;
///
/// Current count of the nubmer of colliders selected for the last preview calculation.
///
public int CurrentColliderCount = 0;
///
/// Color to draw the preview lines with
///
public Color DrawColor = Color.cyan;
private EasyColliderCreator _ECC;
///
/// Creator that calculates the colliders we use to preview.
///
///
public EasyColliderCreator ECC
{
get
{
if (_ECC == null)
{
_ECC = new EasyColliderCreator();
}
return _ECC;
}
set { _ECC = value; }
}
///
/// Shader used to draw mesh colliders.
///
private Shader MeshColliderShader;
///
/// All the data from the current preview calculation
///
public EasyColliderData PreviewData;
///
/// Preview data hides that rotated colliders are different "collider types" so this keeps track of that for updating the preview.
///
public CREATE_COLLIDER_TYPE ActualColliderType;
#region DrawPreviews
///
/// Draws a preview from the current preview data.
///
private void DrawPreview()
{
// make sure we have data, and it's valid.
if (PreviewData != null && PreviewData.IsValid)
{
// Draw based on the preview type.
switch (PreviewData.ColliderType)
{
case CREATE_COLLIDER_TYPE.BOX:
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
DrawPreviewBox(PreviewData as BoxColliderData, DrawColor);
break;
case CREATE_COLLIDER_TYPE.SPHERE:
DrawPreviewSphere(PreviewData as SphereColliderData, DrawColor);
break;
case CREATE_COLLIDER_TYPE.CAPSULE:
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
DrawPreviewCapsule(PreviewData as CapsuleColliderData, DrawColor);
break;
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
case CREATE_COLLIDER_TYPE.CYLINDER:
DrawPreviewConvexMesh(PreviewData as MeshColliderData, DrawColor);
break;
}
}
}
///
/// Draws a mesh collider preview
///
/// Data from quickhull calculation
private void DrawPreviewConvexMesh(MeshColliderData data, Color color)
{
// try to find mesh shader
if (MeshColliderShader == null)
{
MeshColliderShader = Shader.Find("Custom/EasyColliderMeshColliderPreview");
}
// if we have the shader, draw it using the wireframe and the color
if (MeshColliderShader != null && data.ConvexMesh != null)
{
Material wireMat = new Material(MeshColliderShader);
wireMat.SetColor("_Color", color);
wireMat.SetPass(0);
GL.wireframe = true;
Graphics.DrawMeshNow(data.ConvexMesh, data.Matrix);
GL.wireframe = false;
Graphics.DrawMeshNow(data.ConvexMesh, data.Matrix);
}
}
///
/// Draws a box collider
///
/// Data from box calculation
private void DrawPreviewBox(BoxColliderData data, Color color)
{
// half size and center
Vector3 hs = data.Size / 2;
Vector3 c = data.Center;
// transform each point of the cube to world space with the transformation matrix
Vector3[] points = new Vector3[8]{
data.Matrix.MultiplyPoint3x4(c + hs),
data.Matrix.MultiplyPoint3x4(c + new Vector3(hs.x, hs.y, -hs.z)),
data.Matrix.MultiplyPoint3x4(c + new Vector3(hs.x, -hs.y, hs.z)),
data.Matrix.MultiplyPoint3x4(c + new Vector3(hs.x, -hs.y, -hs.z)),
data.Matrix.MultiplyPoint3x4(c + new Vector3(-hs.x, hs.y, hs.z)),
data.Matrix.MultiplyPoint3x4(c + new Vector3(-hs.x, hs.y, -hs.z)),
data.Matrix.MultiplyPoint3x4(c + new Vector3(-hs.x, -hs.y, hs.z)),
data.Matrix.MultiplyPoint3x4(c - hs)
};
// draw the lines connecting corners of the cube.
Handles.color = color;
Handles.DrawLine(points[0], points[1]);
Handles.DrawLine(points[0], points[2]);
Handles.DrawLine(points[0], points[4]);
Handles.DrawLine(points[7], points[5]);
Handles.DrawLine(points[7], points[6]);
Handles.DrawLine(points[7], points[3]);
Handles.DrawLine(points[1], points[5]);
Handles.DrawLine(points[1], points[3]);
Handles.DrawLine(points[2], points[6]);
Handles.DrawLine(points[2], points[3]);
Handles.DrawLine(points[4], points[5]);
Handles.DrawLine(points[4], points[6]);
}
public void DrawCapsuleCollider(CapsuleColliderData data, Color color)
{
DrawPreviewCapsule(data, color);
}
private void DrawPreviewCapsule(CapsuleColliderData data, Color color)
{
Handles.color = color;
// calculate top and bottom center sphere locations.
float offset = data.Height / 2;
Vector3 top, bottom = top = data.Center;
float radius = data.Radius;
Vector3 scale = data.Matrix.lossyScale;
switch (data.Direction)
{
case 0: //x axis
//adjust radius by the bigger scale.
radius *= scale.y > scale.z ? scale.y : scale.z;
// adjust the offset to top and bottom mid points for spheres based on radius / scale in that direction
offset -= radius / scale.x;
// offset top and bottom points.
top.x += offset;
bottom.x -= offset;
break;
case 1:
radius *= scale.x > scale.z ? scale.x : scale.z;
offset -= radius / scale.y;
top.y += offset;
bottom.y -= offset;
break;
case 2:
radius *= scale.x > scale.y ? scale.x : scale.y;
offset -= radius / scale.z;
top.z += offset;
bottom.z -= offset;
break;
}
if (data.Height < data.Radius * 2)
{
// draw just the sphere if the radius and the height will make a sphere.
Vector3 worldCenter = data.Matrix.MultiplyPoint(data.Center);
Handles.DrawWireDisc(worldCenter, Vector3.forward, radius);
Handles.DrawWireDisc(worldCenter, Vector3.right, radius);
Handles.DrawWireDisc(worldCenter, Vector3.up, radius);
return;
}
Vector3 worldTop = data.Matrix.MultiplyPoint3x4(top);
Vector3 worldBottom = data.Matrix.MultiplyPoint3x4(bottom);
Vector3 up = worldTop - worldBottom;
Vector3 cross1 = Vector3.up;
// dont want to cross if in same direction, forward works in this case as the first cross
if (up.normalized == cross1 || up.normalized == -cross1)
{
cross1 = Vector3.forward;
}
Vector3 right = Vector3.Cross(up, -cross1).normalized;
Vector3 forward = Vector3.Cross(up, -right).normalized;
// full circles at top and bottom
Handles.DrawWireDisc(worldTop, up, radius);
Handles.DrawWireDisc(worldBottom, up, radius);
// half arcs at top and bottom
Handles.DrawWireArc(worldTop, forward, right, 180f, radius);
Handles.DrawWireArc(worldTop, -right, forward, 180f, radius);
Handles.DrawWireArc(worldBottom, -forward, right, 180f, radius);
Handles.DrawWireArc(worldBottom, right, forward, 180f, radius);
// connect bottom and top side points
Handles.DrawLine(worldTop + right * radius, worldBottom + right * radius);
Handles.DrawLine(worldTop - right * radius, worldBottom - right * radius);
Handles.DrawLine(worldTop + forward * radius, worldBottom + forward * radius);
Handles.DrawLine(worldTop - forward * radius, worldBottom - forward * radius);
}
public void DrawSphereCollider(SphereColliderData data, Color color)
{
DrawPreviewSphere(data, color);
}
///
/// Draws a sphere collider
///
/// Data from sphere calculation
private void DrawPreviewSphere(SphereColliderData data, Color color)
{
Handles.color = color;
Vector3 worldCenter = data.Matrix.MultiplyPoint3x4(data.Center);
// Draw all normal axis' rings at the world center location for both perspective and isometric/orthographic
float radius = data.Radius;
Vector3 scale = data.Matrix.lossyScale;
float largestScale = Mathf.Max(Mathf.Max(scale.x, scale.y), scale.z);
radius *= largestScale;
Handles.DrawWireDisc(worldCenter, Vector3.forward, radius);
Handles.DrawWireDisc(worldCenter, Vector3.right, radius);
Handles.DrawWireDisc(worldCenter, Vector3.up, radius);
// orthographic camera
if (Camera.current != null)
{
if (Camera.current.orthographic)
{
// simple, use cameras forward in orthographic
Handles.DrawWireDisc(worldCenter, Camera.current.transform.forward, radius);
}
else
{
// draw a circle facing the camera covering all the radius in prespective mode
Vector3 normal = worldCenter - Handles.inverseMatrix.MultiplyPoint(Camera.current.transform.position);
float sqrMagnitude = normal.sqrMagnitude;
float r2 = radius * radius;
float r4m = r2 * r2 / sqrMagnitude;
float newRadius = Mathf.Sqrt(r2 - r4m);
Handles.DrawWireDisc(worldCenter - r2 * normal / sqrMagnitude, normal, newRadius);
}
}
}
#endregion
public void ClearPreview()
{
PreviewData = new EasyColliderData();
}
///
/// Gets the method for the current preview collider type
///
/// type of collider we are previewing
/// enum of method selected for collider type as an int
private int GetMethodForColliderPreviewType(EasyColliderPreferences preferences)
{
if (preferences.PreviewColliderType == CREATE_COLLIDER_TYPE.CAPSULE || preferences.PreviewColliderType == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE)
{
return (int)preferences.CapsuleColliderMethod;
}
else if (preferences.PreviewColliderType == CREATE_COLLIDER_TYPE.SPHERE)
{
return (int)preferences.SphereColliderMethod;
}
else if (preferences.PreviewColliderType == CREATE_COLLIDER_TYPE.CONVEX_MESH)
{
return (int)preferences.MeshColliderMethod;
}
else if (preferences.PreviewColliderType == CREATE_COLLIDER_TYPE.CYLINDER)
{
return (int)preferences.CylinderNumberOfSides;
}
else
{
return 0;
}
}
#region CalculatePreviews
///
/// Calculates data to display a collider
///
/// type of collider we want to preview
/// world vertices/points currently selected
/// object the collider would be attached to
/// method of calculation for the collider type as an int
private void CalculatePreviewCollider(CREATE_COLLIDER_TYPE type, List worldVertices, GameObject attachTo, int method)
{
CurrentMethod = method;
CurrentVertexCount = worldVertices.Count;
CurrentAttachTo = attachTo.transform;
if (PreviewData == null)
{
PreviewData = new EasyColliderData();
}
switch (type)
{
case CREATE_COLLIDER_TYPE.BOX:
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
PreviewData = ECC.CalculateBox(worldVertices, attachTo.transform, type == CREATE_COLLIDER_TYPE.ROTATED_BOX);
break;
case CREATE_COLLIDER_TYPE.SPHERE:
PreviewData = CalculatePreviewSphere(worldVertices, attachTo.transform, (SPHERE_COLLIDER_METHOD)method);
break;
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
case CREATE_COLLIDER_TYPE.CAPSULE:
PreviewData = CalculatePreviewCapsule(worldVertices, attachTo.transform, (CAPSULE_COLLIDER_METHOD)method, type == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE);
break;
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
PreviewData = CalculatePreviewMesh(worldVertices, attachTo.transform, (MESH_COLLIDER_METHOD)method);
break;
case CREATE_COLLIDER_TYPE.CYLINDER:
PreviewData = CalculatePreviewCylinder(worldVertices, attachTo.transform);
break;
}
PreviewData.ColliderType = type;
}
private void CalculatePreviewMerge(CREATE_COLLIDER_TYPE mergeTo, List selectedColliders, GameObject attachTo, int method)
{
CurrentMethod = method;
CurrentColliderCount = selectedColliders.Count;
CurrentAttachTo = attachTo.transform;
if (PreviewData == null)
{
PreviewData = new EasyColliderData();
}
if (selectedColliders.Count > 0)
{
EasyColliderCreator ecc = new EasyColliderCreator();
PreviewData = ecc.MergeCollidersPreview(selectedColliders, mergeTo, attachTo.transform);
}
else
{
PreviewData = new EasyColliderData();
}
}
///
/// Calculates a preview capsule
///
/// world vertices used to calculate the collider
/// gameobject the collider will be attached to
/// calculation method used to calculate the collider type as an int
/// will the collider be a rotated collider?
/// Calculated capsule collider data
private EasyColliderData CalculatePreviewCapsule(List worldVertices, Transform attachTo, CAPSULE_COLLIDER_METHOD method, bool isRotated)
{
switch (method)
{
case CAPSULE_COLLIDER_METHOD.BestFit:
return ECC.CalculateCapsuleBestFit(worldVertices, attachTo, isRotated);
default:
return ECC.CalculateCapsuleMinMax(worldVertices, attachTo, method, isRotated);
}
}
///
/// Calculates a preview cylinder
///
/// world vertices used to calculate the collider
/// transform the collider will be attached to
/// Calculated cylinder shaped mesh collider data
private MeshColliderData CalculatePreviewCylinder(List worldVertices, Transform attachTo)
{
EasyColliderCreator ecc = new EasyColliderCreator();
MeshColliderData data = ecc.CalculateCylinderCollider(worldVertices, attachTo);
return data;
}
///
/// Calculates the preview data for a convex mesh collider. (uses quickhull calculation for both messy and quickhull method)
///
/// World-Space vertices that are selected
/// Current AttachTo object
/// Mesh Collider generation method
/// EasyCollider data with a mesh value set.
private MeshColliderData CalculatePreviewMesh(List worldVertices, Transform attachTo, MESH_COLLIDER_METHOD method)
{
// Messy-hull not suited for preview as the intermediate mesh is essentially useless
// and the actual important result is from the internal calculation by the mesh collider, so it would have to be added.
// but since internall it uses a similar hull method, the result should be similar as well.
// automatically uses quickhull for messy-hull version as well
return EasyColliderQuickHull.CalculateHullData(worldVertices, attachTo);
}
///
/// Calculates a preview sphere
///
/// world vertices used to calculate the collider
/// gameobject the collider will be attached to
/// calculation method used to calculate the collider type as an int
/// Calculated sphere collider data
private EasyColliderData CalculatePreviewSphere(List worldVertices, Transform attachTo, SPHERE_COLLIDER_METHOD method)
{
switch (method)
{
case SPHERE_COLLIDER_METHOD.Distance:
return ECC.CalculateSphereDistance(worldVertices, attachTo);
case SPHERE_COLLIDER_METHOD.BestFit:
return ECC.CalculateSphereBestFit(worldVertices, attachTo);
default:
return ECC.CalculateSphereMinMax(worldVertices, attachTo);
}
}
#endregion
///
/// Checks to see if the collider needs to be calculated, so the preview is updated.
///
///
///
///
/// true if the preview needs to be updated.
private bool NeedsUpdate(EasyColliderEditor editor, CREATE_COLLIDER_TYPE colliderType, int method)
{
if (CurrentVertexCount != editor.SelectedVertices.Count
|| PreviewData == null
|| PreviewData.ColliderType != colliderType
|| CurrentMethod != method
|| CurrentAttachTo != editor.AttachToObject.transform
|| editor.HasTransformMoved())
{
return true;
}
return false;
}
private bool NeedsMergeUpdate(EasyColliderEditor editor, CREATE_COLLIDER_TYPE colliderType, int method)
{
if (CurrentColliderCount != editor.SelectedColliders.Count
|| PreviewData == null
|| CurrentMethod != method
|| CurrentAttachTo != editor.AttachToObject.transform
|| editor.HasTransformMoved()
|| ActualColliderType != colliderType)
{
ActualColliderType = colliderType;
return true;
}
return false;
}
///
/// Updates the current preview if needed.
///
///
///
public void UpdatePreview(EasyColliderEditor editor, EasyColliderPreferences preferences, bool forceUpdate = false)
{
if (editor != null && preferences != null && editor.SelectedGameObject != null && editor.AttachToObject != null)
{
DrawColor = preferences.PreviewDrawColor;
if (NeedsUpdate(editor, preferences.PreviewColliderType, GetMethodForColliderPreviewType(preferences)) || forceUpdate)
{
CalculatePreviewCollider(preferences.PreviewColliderType, editor.GetWorldVertices(), editor.AttachToObject, GetMethodForColliderPreviewType(preferences));
}
DrawPreview();
}
}
public void UpdateMergePreview(EasyColliderEditor editor, EasyColliderPreferences preferences, bool forceUpdate = false)
{
if (editor != null && preferences != null && editor.SelectedGameObject != null && editor.AttachToObject != null)
{
DrawColor = preferences.PreviewDrawColor;
if (NeedsMergeUpdate(editor, preferences.PreviewColliderType, GetMethodForColliderPreviewType(preferences)) || forceUpdate)
{
CalculatePreviewMerge(preferences.PreviewColliderType, editor.SelectedColliders, editor.AttachToObject, GetMethodForColliderPreviewType(preferences));
}
DrawPreview();
}
}
#region VHACDPREVIEW
///
/// Array of triangles for use in VHACDResultPreview
///
private int[] triangles;
///
/// Array of vertices for use in VHACDResultPreview
///
private Vector3[] vertices;
///
/// List of randomized colors for use in VHACDResultPreview
///
private List colors = new List();
///
/// Draws each mesh in the dictionary with a black wireframe, and a random color semi-transparent mesh.
///
/// Dictionary of transforms and meshes from the result of VHACD
public void DrawVHACDResultPreview(Dictionary previewResult)
{
Shader s = Shader.Find("Custom/EasyColliderMeshColliderPreview");
if (s != null)
{
Material wireMat = new Material(s);
wireMat.SetColor("_Color", Color.black);
Material flatMat = new Material(s);
foreach (KeyValuePair kvp in previewResult)
{
if (kvp.Key == null) continue; // transform can be null if it's deleted.
if (colors.Count < kvp.Value.Length)
{
int addColors = kvp.Value.Length - colors.Count;
for (int i = 0; i < addColors; i++)
{
colors.Add(new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
}
}
for (int i = 0; i < kvp.Value.Length; i++)
{
flatMat.SetColor("_Color", colors[i]);
flatMat.SetPass(0);
Graphics.DrawMeshNow(kvp.Value[i], kvp.Key.localToWorldMatrix);
wireMat.SetPass(0);
GL.wireframe = true;
Graphics.DrawMeshNow(kvp.Value[i], kvp.Key.localToWorldMatrix);
GL.wireframe = false;
}
}
}
else
{
Debug.LogWarning("EasyColliderEditor: Unable to find shader for preview.");
}
}
#endregion
}
}
#endif