#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