2026-04-30 그린빈 추가

This commit is contained in:
2026-04-30 12:55:27 +09:00
parent b03f1b3e56
commit 2cc46ebea6
147 changed files with 31909 additions and 5 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 45eb3a409ea753b44ab3bb6b8585be07
timeCreated: 1591641873
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinned.cs
uploadId: 885904

View File

@@ -0,0 +1,97 @@
using System.Collections.Generic;
using UnityEngine;
namespace ECE
{
[System.Serializable]
/// <summary>
/// Class to hold data for a skinned meshes' bones
/// Used because the bones transform array can be empty in cases where optimize gameobjects is used on import
/// The bindbose index of a bone, is the same as a bone's boneindex, so boneweight's can still be used.
/// </summary>
public class EasyColliderAutoSkinnedBone
{
public SkinnedMeshRenderer renderer;
/// <summary>
/// Create a collider for this bone?
/// </summary>
public bool Enabled = true;
/// <summary>
/// What kind of collider to create.
/// </summary>
public SKINNED_MESH_COLLIDER_TYPE ColliderType = SKINNED_MESH_COLLIDER_TYPE.Box;
/// <summary>
/// Minimum skinning bone weight to include a vertex in the bone's collider calculations.
/// </summary>
public float BoneWeight = 0.5f;
/// <summary>
/// Bone's display name (transform.name)
/// </summary>
public string BoneName = "Default";
/// <summary>
/// Is this bone paired with another bone?
/// </summary>
public bool IsPaired = false;
/// <summary>
/// If this bone is paired, is this the bone that is displayed in the UI?
/// </summary>
public bool IsPairDisplayBone = false;
/// <summary>
/// Is this bone valid? (Has at least 1 vertex that meets it's boneWeight)
/// </summary>
public bool IsValid = false;
public EasyColliderAutoSkinnedBone(Matrix4x4 bp, int index, Transform t)
{
BoneIndex = index;
BindPose = bp;
Transform = t;
}
/// <summary>
/// Local to world matrix of the bone's transform.
/// </summary>
public Matrix4x4 Matrix;
/// <summary>
/// Bind pose of the bone
/// </summary>
public Matrix4x4 BindPose;
/// <summary>
/// Bind pose index is the bone index as well.
/// </summary>
public int BoneIndex;
/// <summary>
/// Transform of the bone
/// </summary>
public Transform Transform;
/// <summary>
/// List of vertices in world space for this bo
/// </summary>
/// <typeparam name="Vector3"></typeparam>
/// <returns></returns>
public List<Vector3> WorldSpaceVertices = new List<Vector3>();
[SerializeField]
/// <summary>
/// List of other bone's index's in the BoneList that this bone is paired with.
/// </summary>
/// <typeparam name="int"></typeparam>
/// <returns></returns>
public List<int> PairedBones = new List<int>();
/// <summary>
/// Collider for this bone.
/// </summary>
public Collider Collider;
/// <summary>
/// Indent level of this bone when displaying in UI.
/// </summary>
public int IndentLevel = -1;
}
}

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 22d8868ad24ea0c43828e22314004216
timeCreated: 1625687084
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderAutoSkinnedBone.cs
uploadId: 885904

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 241873a664ee81d418528c165ef0dd12
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderCreator.cs
uploadId: 885904

View File

@@ -0,0 +1,125 @@
using UnityEngine;
namespace ECE
{
/// <summary>
/// Data holder for collider calculations.
/// </summary>
public class EasyColliderData
{
/// <summary>
/// Type of collider data
/// </summary>
public CREATE_COLLIDER_TYPE ColliderType;
/// <summary>
/// Did the collider calculation complete
/// </summary>
public bool IsValid = false;
/// <summary>
/// TRS matrix of the attach to object, or TRS matrix of what the rotated collider will have
/// </summary>
public Matrix4x4 Matrix;
public void Clone(EasyColliderData data)
{
this.ColliderType = data.ColliderType;
this.IsValid = data.IsValid;
this.Matrix = data.Matrix;
}
}
/// <summary>
/// Data for creating a sphere collider
/// </summary>
public class SphereColliderData : EasyColliderData
{
/// <summary>
/// Radius of the collider
/// </summary>
public float Radius;
/// <summary>
/// Center of the collider
/// </summary>
public Vector3 Center;
public void Clone(SphereColliderData data)
{
base.Clone(data);
this.Radius = data.Radius;
this.Center = data.Center;
}
}
/// <summary>
/// Data for creating a capsule collider
/// </summary>
public class CapsuleColliderData : SphereColliderData
{
/// <summary>
/// Direction of the capsule collider
/// </summary>
public int Direction;
/// <summary>
/// Height of the capsule collider
/// </summary>
public float Height;
public void Clone(CapsuleColliderData data)
{
base.Clone(data);
this.Direction = data.Direction;
this.Height = data.Height;
}
}
/// <summary>
/// Data for creating a box collider
/// </summary>
public class BoxColliderData : EasyColliderData
{
/// <summary>
/// Center of the box collider
/// </summary>
public Vector3 Center;
/// <summary>
/// Size of the box collider
/// </summary>
public Vector3 Size;
public void Clone(BoxColliderData data)
{
base.Clone(data);
this.Center = data.Center;
this.Size = data.Size;
this.Matrix = data.Matrix;
}
public override string ToString()
{
return "Rotated box collider. Center:" + Center.ToString() + " Size:" + Size.ToString();
}
}
/// <summary>
/// Data for creating a mesh collider
/// </summary>
public class MeshColliderData : EasyColliderData
{
/// <summary>
/// Mesh of the convex mesh collider
/// </summary>
public Mesh ConvexMesh;
public void Clone(MeshColliderData data)
{
base.Clone(data);
this.ConvexMesh = data.ConvexMesh;
}
}
}

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: f703485b98da54c489209f1fd7ad2db9
timeCreated: 1593006697
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderData.cs
uploadId: 885904

View File

@@ -0,0 +1,160 @@
#if (UNITY_EDITOR)
using UnityEngine;
using UnityEditor;
namespace ECE
{
public static class EasyColliderDraw
{
/// <summary>
/// Draws a box
/// </summary>
/// <param name="transform">Transform of the box</param>
/// <param name="center">Center of the box in local space</param>
/// <param name="halfSize">Half the size of the box</param>
/// <param name="color">Color of lines used to draw the box</param>
private static void DrawBox(Transform transform, Vector3 center, Vector3 halfSize, Color color)
{
Vector3 p1 = transform.TransformPoint(center + halfSize);
Vector3 p2 = transform.TransformPoint(center - halfSize);
Vector3 p3 = transform.TransformPoint(center + new Vector3(halfSize.x, halfSize.y, -halfSize.z));
Vector3 p4 = transform.TransformPoint(center + new Vector3(halfSize.x, -halfSize.y, halfSize.z));
Vector3 p5 = transform.TransformPoint(center + new Vector3(halfSize.x, -halfSize.y, -halfSize.z));
Vector3 p6 = transform.TransformPoint(center + new Vector3(-halfSize.x, halfSize.y, halfSize.z));
Vector3 p7 = transform.TransformPoint(center + new Vector3(-halfSize.x, -halfSize.y, halfSize.z));
Vector3 p8 = transform.TransformPoint(center + new Vector3(-halfSize.x, halfSize.y, -halfSize.z));
Handles.color = color;
Handles.DrawLine(p1, p3);
Handles.DrawLine(p1, p4);
Handles.DrawLine(p1, p6);
Handles.DrawLine(p8, p3);
Handles.DrawLine(p8, p6);
Handles.DrawLine(p8, p2);
Handles.DrawLine(p7, p6);
Handles.DrawLine(p7, p2);
Handles.DrawLine(p7, p4);
Handles.DrawLine(p5, p4);
Handles.DrawLine(p5, p2);
Handles.DrawLine(p5, p3);
}
/// <summary>
/// Draws a box collider
/// </summary>
/// <param name="boxCollider">Box collider to draw</param>
/// <param name="color">Color of lines used to draw</param>
private static void DrawBoxCollider(BoxCollider boxCollider, Color color)
{
DrawBox(boxCollider.transform, boxCollider.center, boxCollider.size / 2, color);
}
private static EasyColliderPreviewer previewer;
public static EasyColliderPreviewer Previewer
{
get
{
if (previewer == null)
{
previewer = ScriptableObject.CreateInstance<EasyColliderPreviewer>();
}
return previewer;
}
}
/// <summary>
/// Draws a capsule collider
/// </summary>
/// <param name="capsuleCollider">Capsule collider to draw</param>
/// <param name="color">Color of lines to draw</param>
private static void DrawCapsuleCollider(CapsuleCollider capsuleCollider, Color color)
{
CapsuleColliderData data = new CapsuleColliderData();
data.ColliderType = CREATE_COLLIDER_TYPE.CAPSULE;
data.Height = capsuleCollider.height;
data.Radius = capsuleCollider.radius;
data.Center = capsuleCollider.center;
data.Direction = capsuleCollider.direction;
data.Matrix = capsuleCollider.transform.localToWorldMatrix;
Previewer.DrawCapsuleCollider(data, color);
return;
}
/// <summary>
/// Draws a primitive collider (box, sphere, capsule) using lines.
/// </summary>
/// <param name="collider">Collider to draw</param>
/// <param name="color">Color of lines to draw with</param>
public static void DrawCollider(Collider collider, Color color)
{
if (collider == null) return;
if (collider is BoxCollider)
{
DrawBoxCollider(collider as BoxCollider, color);
}
else if (collider is SphereCollider)
{
DrawSphereCollider(collider as SphereCollider, color);
}
else if (collider is CapsuleCollider)
{
DrawCapsuleCollider(collider as CapsuleCollider, color);
}
else if (collider is MeshCollider)
{
DrawMeshCollider(collider as MeshCollider, color);
}
}
/// <summary>
/// Shader used to draw mesh colliders.
/// </summary>
static Shader MeshColliderShader;
/// <summary>
/// Draws a mesh collider by drawing lines connecting it's meshs vertices.
/// </summary>
/// <param name="collider">Mesh Collider</param>
/// <param name="color">Color to draw lines with</param>
private static void DrawMeshCollider(MeshCollider collider, Color color)
{
// try to find mesh collider.
if (MeshColliderShader == null)
{
MeshColliderShader = Shader.Find("Custom/EasyColliderMeshColliderPreview");
}
// if we have the shader, draw it using the wireframe and the color
if (MeshColliderShader != null && collider != null && collider.sharedMesh != null)
{
Material wireMat = new Material(MeshColliderShader);
wireMat.SetColor("_Color", color);
wireMat.SetPass(0);
GL.wireframe = true;
Graphics.DrawMeshNow(collider.sharedMesh, collider.transform.localToWorldMatrix);
GL.wireframe = false;
}
else
{
// no shader? fall back to old draw box method.
DrawBox(collider.transform, collider.transform.InverseTransformPoint(collider.bounds.center), collider.transform.InverseTransformVector(collider.bounds.extents), color);
}
}
// Draws a sphere collider, taken from previous version
/// <summary>
/// Draws a sphere collider.
/// </summary>
/// <param name="sphereCollider">Sphere collider to draw</param>
/// <param name="color">Color of lines used to draw</param>
private static void DrawSphereCollider(SphereCollider sphereCollider, Color color)
{
SphereColliderData data = new SphereColliderData();
data.ColliderType = CREATE_COLLIDER_TYPE.SPHERE;
data.Center = sphereCollider.center;
data.Radius = sphereCollider.radius;
data.Matrix = sphereCollider.transform.localToWorldMatrix;
Previewer.DrawSphereCollider(data, color);
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3c95b61c7ebb7b14e9688b95552cef80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderDraw.cs
uploadId: 885904

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8765d9c9fa7824d4da8e37f5cc1913fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderEditor.cs
uploadId: 885904

View File

@@ -0,0 +1,158 @@
namespace ECE
{
public enum NORMAL_OFFSET
{
Out,
In,
Both,
}
/// <summary>
/// Capsule method to use when creating a capsule
/// </summary>
public enum CAPSULE_COLLIDER_METHOD
{
BestFit,
MinMax,
MinMaxPlusRadius,
MinMaxPlusDiameter,
}
/// <summary>
/// Type of collider to create
/// </summary>
public enum CREATE_COLLIDER_TYPE
{
BOX,
ROTATED_BOX,
SPHERE,
CAPSULE,
ROTATED_CAPSULE,
CONVEX_MESH,
CYLINDER,
}
/// <summary>
/// Orientation of collider
/// </summary>
public enum COLLIDER_ORIENTATION
{
NORMAL,
ROTATED,
}
public enum CYLINDER_ORIENTATION
{
Automatic,
LocalX,
LocalY,
LocalZ
}
/// <summary>
/// Enum for spheres ot cubes when drawing gizmos
/// </summary>
public enum GIZMO_TYPE
{
CUBE,
SPHERE,
}
/// <summary>
/// enum for Shaders or Gizmos to draw vertices with.
/// </summary>
public enum RENDER_POINT_TYPE
{
SHADER,
GIZMOS,
}
/// <summary>
/// Collider type to use when automatically generating colliders along a bone chain for a skinned mesh renderer.
/// </summary>
public enum SKINNED_MESH_COLLIDER_TYPE
{
Box,
Capsule,
Sphere,
Convex_Mesh,
}
public enum SKINNED_MESH_DEPENETRATE_ORDER
{
InOrder,
Reverse,
InsideOut,
OutsideIn,
}
/// <summary>
/// Sphere method to use when creating a sphere
/// </summary>
public enum SPHERE_COLLIDER_METHOD
{
BestFit,
Distance,
MinMax,
}
public enum VHACD_CONVERSION
{
None,
Boxes,
Spheres,
Capsules,
}
public enum MESH_COLLIDER_METHOD
{
QuickHull,
MessyHull
}
public enum VERTEX_SNAP_METHOD
{
Add,
Remove,
Both,
}
/// <summary>
/// Method used to attach mesh colliders to the attach to object.
/// </summary>
public enum VHACD_RESULT_METHOD
{
AttachTo,
ChildObject,
IndividualChildObjects
}
/// <summary>
/// Current window tab selected in the editor window.
/// </summary>
public enum ECE_WINDOW_TAB
{
None = -1,
Creation = 0,
Editing = 1,
VHACD = 2,
AutoSkinned = 3,
}
public enum COLLIDER_HOLDER
{
Default,
Once,
Always
}
public enum CONVEX_HULL_SAVE_METHOD
{
Prefab,
Mesh,
PrefabMesh,
MeshPrefab,
Folder,
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d3013554ca157424db48970772ea240c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderEnums.cs
uploadId: 885904

View File

@@ -0,0 +1,185 @@
#if (UNITY_EDITOR)
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace ECE
{
/// <summary>
/// Used to draw gizmos for selected / hovered vertices
/// Gizmos draw significantly faster than handles.
/// </summary>
[System.Serializable]
public class EasyColliderGizmos : MonoBehaviour, ISerializationCallbackReceiver
{
#region preference settings
private EasyColliderPreferences ECEPreferences
{
get { return EasyColliderPreferences.Preferences; }
}
public float CommonScale { get { return ECEPreferences.CommonScalingMultiplier; } }
public float DefaultScale { get { return ECEPreferences.DefaultScale; } }
public bool DisplayAllVertices { get { return ECEPreferences.DisplayAllVertices; } }
public Color DisplayVertexColor { get { return ECEPreferences.DisplayVerticesColour; } }
public GIZMO_TYPE GizmoType { get { return ECEPreferences.GizmoType; } }
public Color HoveredVertexColor { get { return ECEPreferences.HoverVertColour; } }
public Color OverlapVertexColor { get { return ECEPreferences.OverlapSelectedVertColour; } }
public Color SelectedVertexColor { get { return ECEPreferences.SelectedVertColour; } }
public bool UseFixedGizmoScale { get { return ECEPreferences.UseFixedGizmoScale; } }
#endregion
/// <summary>
/// calculated density scale if use density scale is enabled.
/// </summary>
public float DensityScale = 0.0f;
/// <summary>
/// List of all valid vertex positions in world space
/// </summary>
public HashSet<Vector3> DisplayVertexPositions = new HashSet<Vector3>();
/// <summary>
/// Should gizmos be drawn
/// </summary>
public bool DrawGizmos = true;
/// <summary>
/// Set of hovered vertices in world space
/// </summary>
public HashSet<Vector3> HoveredVertexPositions = new HashSet<Vector3>();
/// <summary>
/// Set of selected vertices in world space
/// </summary>
public HashSet<Vector3> SelectedVertexPositions = new HashSet<Vector3>();
float GetScale()
{
// *10 makes it more equivalent to how the shader is drawn.
float scale = DefaultScale * CommonScale * 10;
if (scale <= 0.0f)
{
scale = ECEPreferences.DefaultScale * 10;
}
return scale;
}
void OnDrawGizmos()
{
if (DrawGizmos)
{
// Keep track of gizmos color to reset at end
Color original = Gizmos.color;
// Selected vertices.
// scale for spheres.
float scale = GetScale();
// size for cubes
Vector3 size = Vector3.one * scale;
// default scaling of 1.0f
float handleSize = 1.0f;
// Display all vertices.
if (DisplayAllVertices)
{
Gizmos.color = DisplayVertexColor;
foreach (Vector3 vert in DisplayVertexPositions)
{
if (UseFixedGizmoScale)
{
handleSize = HandleUtility.GetHandleSize(vert);
}
DrawAGizmo(vert, size * handleSize, scale * handleSize, GizmoType);
}
}
// Selected vertices
Gizmos.color = SelectedVertexColor;
foreach (Vector3 vert in SelectedVertexPositions)
{
if (UseFixedGizmoScale)
{
handleSize = HandleUtility.GetHandleSize(vert);
}
DrawAGizmo(vert, size * handleSize, scale * handleSize, GizmoType);
}
// Hover vertices.
Gizmos.color = HoveredVertexColor;
foreach (Vector3 vert in HoveredVertexPositions)
{
if (SelectedVertexPositions.Contains(vert))
{
if (UseFixedGizmoScale)
{
handleSize = HandleUtility.GetHandleSize(vert);
}
Gizmos.color = OverlapVertexColor;
DrawAGizmo(vert, size * handleSize, scale * handleSize, GizmoType);
}
else
{
if (UseFixedGizmoScale)
{
handleSize = HandleUtility.GetHandleSize(vert);
}
Gizmos.color = HoveredVertexColor;
DrawAGizmo(vert, size * handleSize, scale * handleSize, GizmoType);
}
}
Gizmos.color = original;
}
}
/// <summary>
/// Draws a gizmo of type at position at size or scale.
/// </summary>
/// <param name="position">World position to draw at</param>
/// <param name="size">Size of cube to draw</param>
/// <param name="scale">Radius of sphere to draw</param>
/// <param name="gizmoType">Sphere or Cubes?</param>
private void DrawAGizmo(Vector3 position, Vector3 size, float scale, GIZMO_TYPE gizmoType)
{
switch (gizmoType)
{
case GIZMO_TYPE.SPHERE:
Gizmos.DrawSphere(position, scale / 2);
break;
case GIZMO_TYPE.CUBE:
Gizmos.DrawCube(position, size);
break;
}
}
/// <summary>
/// Sets the set of selected vertices from a list of selected world vertices
/// </summary>
/// <param name="worldVertices">List of world vertex positions that are selected</param>
public void SetSelectedVertices(List<Vector3> worldVertices)
{
SelectedVertexPositions.Clear();
SelectedVertexPositions.UnionWith(worldVertices);
}
public List<Vector3> SerializedDisplayVertexPositions = new List<Vector3>();
public List<Vector3> SerializedHoveredVertexPositions = new List<Vector3>();
public List<Vector3> SerializedSelectedVertexPositions = new List<Vector3>();
public void OnBeforeSerialize()
{
SerializedDisplayVertexPositions.AddRange(DisplayVertexPositions);
SerializedHoveredVertexPositions.AddRange(HoveredVertexPositions);
SerializedSelectedVertexPositions.AddRange(SelectedVertexPositions);
}
public void OnAfterDeserialize()
{
DisplayVertexPositions.UnionWith(SerializedDisplayVertexPositions);
HoveredVertexPositions.UnionWith(SerializedHoveredVertexPositions);
SelectedVertexPositions.UnionWith(SerializedSelectedVertexPositions);
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7302dd3862f934949a13c03008aba827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderGizmos.cs
uploadId: 885904

View File

@@ -0,0 +1,55 @@
using UnityEngine;
namespace ECE
{
public class EasyColliderPostProccessor : IEasyColliderPostProcessor
{
// The default implementation does nothing.
// Can be used to add your own code if you want to do something specifically to each type of collider.
// If you're creating colliders at runtime with EasyColliderCreator, you can also set the post processor through code,
// if you don't it will by default use this implementation
// NOTE: this happens for every single collider created in this asset / created by EasyColliderCreator
// Vertex selected created colliders, merged colliders, auto skinned colliders, vhacd colliders all go through these methods.
/// <summary>
/// post processes a box collider, properties orientation indiciates if it's a rotated collider.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
public void PostProcessCollider(BoxCollider boxCollider, EasyColliderProperties properties)
{
// in here you'll get passed a completely generated box collider, and can do whatever you want with it.
}
/// <summary>
/// post processes a capsule collider, properties orientation indiciates if it's a rotated collider.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
public void PostProcessCollider(CapsuleCollider capsuleCollider, EasyColliderProperties properties)
{
// in here you'll get passed a completely generated capsule collider, and can do whatever you want with it.
}
/// <summary>
/// post processes a sphere collider
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
public void PostProcessCollider(SphereCollider sphereCollider, EasyColliderProperties properties)
{
// in here you'll get passed a completely generated sphere collider, and can do whatever you want with it.
}
/// <summary>
/// post processes a mesh collider. cylinder colliders are mesh colliders as well.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
public void PostProcessCollider(MeshCollider meshCollider, EasyColliderProperties properties)
{
// in here you'll get passed a completely generated mesh collider, and can do whatever you want with it.
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c4635610932a87342a4bf2e41e9ab050
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderPostProccessor.cs
uploadId: 885904

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 99d3a08e9dc280246aa61e99c233b0c0
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderPreferences.asset
uploadId: 885904

View File

@@ -0,0 +1,615 @@
#if (UNITY_EDITOR)
using UnityEngine;
using UnityEditor;
using System.IO;
namespace ECE
{
[System.Serializable]
public class EasyColliderPreferences : ScriptableObject
{
/// <summary>
/// Currently set vhacd parameters.
/// </summary>
[SerializeField] private VHACDParameters _VHACDParameters;
public VHACDParameters VHACDParameters
{
get
{
if (_VHACDParameters == null)
{
_VHACDParameters = new VHACDParameters();
}
return _VHACDParameters;
}
set { _VHACDParameters = value; }
}
/// <summary>
/// Float to convert to vhacd parameters resolution using 2^Value for UI Slider when advanced parameters is expanded
/// </summary>
[SerializeField] public float VHACDResFloat = 12.97f;
/// <summary>
/// Resets vhacd parameters to default.
/// </summary>
public void VHACDSetDefaultParameters()
{
VHACDParameters = new VHACDParameters();
}
/// <summary>
/// When enabled, selecting objects in the background of the scene view can be done with a single click even when an object is currently selected for collider creation.
/// </summary>
[SerializeField] public bool AllowBackgroundSelection;
/// <summary>
/// when enabled, allows convex hulls used in mesh colliders to be saved in Packages folder.
/// THESE FILES ARE NOT SHARED WITH JUST THE PROJECT. MUST BE HANDLED BY YOUR TEAM. MUST ALSO SHARE SOURCE PACKAGES FOLDERS
/// </summary>
[SerializeField] public bool AllowSavingConvexHullsInPackages;
/// <summary>
/// When entering prefab mode, should we automatically select the root object?
/// </summary>
[SerializeField] public bool AutoSelectOnPrefabOpen;
/// <summary>
/// Auto include child skinned meshes
/// </summary>
[SerializeField] public bool AutoIncludeChildSkinnedMeshes;
/// <summary>
/// Should we try to reduce the number of vertices in auto-skinned calculations for convex mesh colliders if the result ends up trying to create a collider that has >=256 triangles.
/// </summary>
[SerializeField] public bool AutoSkinnedForce256Triangles;
/// <summary>
/// The minimum weight for a vertex to be included in a bone's calculations.
/// </summary>
[SerializeField] public float AutoSkinnedMinBoneWeight;
/// <summary>
/// The angle of mis-alignedment above which we should create a better aligned child transform to hold colliders for skinned meshes.
/// </summary>
[SerializeField] public float AutoSkinnedMinRealignAngle;
/// <summary>
/// Should we allow transforms to be created that would better align the collider with the mesh than a mis-aligned bone?
/// </summary>
[SerializeField] public bool AutoSkinnedAllowRealign;
/// <summary>
/// Type of collider to use when auto generating skinned mesh colliders along a bone chain.
/// </summary>
[SerializeField] public SKINNED_MESH_COLLIDER_TYPE AutoSkinnedColliderType = SKINNED_MESH_COLLIDER_TYPE.Box;
/// <summary>
/// Should we be attempting to compute penetration and depenetrate the colliders on the skinned mesh?
/// </summary>
[SerializeField] public bool AutoSkinnedDepenetrate;
/// <summary>
/// Are we displaying with indentation?
/// </summary>
[SerializeField] public bool AutoSkinnedIndents = true;
/// <summary>
/// number of times to run depenetration methods before stopping.
/// </summary>
[SerializeField] public int AutoSkinnedIterativeDepenetrationCount = 15;
/// <summary>
/// Are we displaying with paired bones?
/// </summary>
[SerializeField] public bool AutoSkinnedPairing = true;
/// <summary>
/// Are we using per-bone settings
/// </summary>
[SerializeField] public bool AutoSkinnedPerBoneSettings;
/// <summary>
/// Amount of collider shrinking (along the various shift's) to do before trying to shift depenetrate.
/// </summary>
[SerializeField] public float AutoSkinnedShrinkAmount = 0.5f;
[Tooltip("Enables using bone position distances during pairing of bones. (AutoSkinnedPairedDistanceDelta controls the distance when enabled.")]
[SerializeField] public bool AutoSkinnedUseDistanceDeltaPairing = true;
[Tooltip("Max distance difference between possible bone pairs to still be considered as a pair. Checked along with length of child bone-chain.")]
[SerializeField] public float AutoSkinnedPairedDistanceDelta = 0.01f;
/// <summary>
/// The way we should sort the bone list in auto-skinned.
/// </summary>
[SerializeField] public SKINNED_MESH_DEPENETRATE_ORDER AutoSkinnedDepenetrateOrder;
/// <summary>
/// Key to hold before box selection to only add vertices in the box.
/// </summary>
[SerializeField] public KeyCode BoxSelectPlusKey;
/// <summary>
/// Key to hold before box selection to only remove vertices in the box.
/// </summary>
[SerializeField] public KeyCode BoxSelectMinusKey;
/// <summary>
/// Capsule collider generation method to use when creating a capsule collider.
/// </summary>
[SerializeField] public CAPSULE_COLLIDER_METHOD CapsuleColliderMethod;
/// <summary>
/// when enabled, when generating multiple convex mesh colliders with vhacd, they are all combined and stored in a single asset file.
/// </summary>
[SerializeField] public bool CombinedVHACDColliders;
/// <summary>
/// A helpful common multiplier for all scales when using any scaling method.
/// </summary>
[SerializeField] public float CommonScalingMultiplier = 1.0f;
/// <summary>
/// should created meshes used in convex mesh colliders be read/write enabled
/// </summary>
[SerializeField] public bool ConvexMeshReadWriteEnabled = true;
[Tooltip("Default scale of the displayed boxes / gizmos around vertices. Combined with common scaling multiplier.")]
/// <summary>
/// Default scale of displayed vertices used along with the common scaling multiplier.
/// </summary>
[SerializeField] public float DefaultScale = 0.01f;
/// <summary>
/// scale for vertices that are hovered / will be added to the selected vertices
/// </summary>
[SerializeField] public float HoveredScaleMult = 1.0f;
/// <summary>
/// scale multiplier for vertices when using display all vertices
/// </summary>
[SerializeField] public float DisplayAllScaleMult = 1.0f;
/// <summary>
/// scale multiplier for vertices that will be removed from the selected vertices
/// </summary>
[SerializeField] public float OverlapScaleMult = 1.0f;
/// <summary>
/// scale multiplier for vertices that are currently selected.
/// </summary>
[SerializeField] public float SelectedScaleMult = 1.0f;
/// <summary>
/// The method to use when creating a collider: if and how a gameobject should be made to hold the collider.
/// </summary>
[SerializeField] public COLLIDER_HOLDER ColliderHolder = COLLIDER_HOLDER.Default;
/// <summary>
/// If true, puts rotated colliders on the same layer as the selected gameobject.
/// </summary>
[SerializeField] public bool CopyParentObjectLayer;
/// <summary>
/// key to press to create from the current preview.
/// </summary>
[SerializeField] public KeyCode CreateFromPreviewKey;
/// <summary>
/// For editor window tab tracking, maintains previously open tab.
/// </summary>
[SerializeField] public ECE_WINDOW_TAB CurrentWindowTab;
/// <summary>
/// Should cylinder orientation field apply to capsules as well.
/// </summary>
[SerializeField] public bool CylinderAsCapsuleOrientation = false;
/// <summary>
/// number of sides when creating a cylinder collider.
/// </summary>
[SerializeField] public int CylinderNumberOfSides = 16;
/// <summary>
/// Method to use to decide which axis a cylinder should be oriented on.
/// </summary>
[SerializeField] public CYLINDER_ORIENTATION CylinderOrientation;
/// <summary>
/// Offset in degrees when creating a cylinder.
/// </summary>
[SerializeField] public float CylinderRotationOffset = 0.0f;
/// <summary>
/// Should tips be displayed?
/// </summary>
[SerializeField] public bool DisplayTips;
/// <summary>
/// Should we display compute shader / gizmos on all the vertices?
/// </summary>
[SerializeField] public bool DisplayAllVertices;
/// <summary>
/// Display vertices colour
/// </summary>
[SerializeField] public Color DisplayVerticesColour;
[SerializeField] public bool EnableVertexToolsShortcuts = true;
/// <summary>
/// Type of gizmos to use when drawing gizmos for vertices
/// </summary>
public GIZMO_TYPE GizmoType;
/// <summary>
/// Hover vertices scaling colour
/// </summary>
[SerializeField] public Color HoverVertColour;
/// <summary>
/// Number of points to generate around a rounded portion of a collider like sphere or capsules
/// </summary>
[SerializeField] public int MergeCollidersRoundnessAccuracy = 10;
/// <summary>
/// Method to use when generating mesh colliders
/// </summary>
[SerializeField] public MESH_COLLIDER_METHOD MeshColliderMethod;
/// <summary>
/// Overlapped vertice scaling colour
/// </summary>
[SerializeField] public Color OverlapSelectedVertColour;
/// <summary>
/// Key used to select points (any point on a mesh that isn't a vertex)
/// </summary>
[SerializeField] public KeyCode PointSelectKeyCode;
[SerializeField] public bool PopupDialogOnFinish;
/// <summary>
/// Collider type we want to preview
/// </summary>
[SerializeField] public CREATE_COLLIDER_TYPE PreviewColliderType;
/// <summary>
/// Color of lines to draw previewed colliders with.
/// </summary>
[SerializeField] public Color PreviewDrawColor;
/// <summary>
/// Are previews enabled?
/// </summary>
[SerializeField] public bool PreviewEnabled;
/// <summary>
/// Raycast delay time, ie only check / select at increments of this time.
/// </summary>
[SerializeField] public float RaycastDelayTime;
/// <summary>
/// Render point method
/// </summary>
[SerializeField] public RENDER_POINT_TYPE RenderPointType;
/// <summary>
/// Should colliders that are merged together be removed after merging is completed?
/// </summary>
[SerializeField] public bool RemoveMergedColliders;
/// <summary>
/// Float that controls the amount an output collider's size should be increased or decreased, essentially scaling the output.
/// </summary>
[SerializeField] public float ShrinkGrow;
/// <summary>
/// Should the rotated colliders pivot be created at the center of the points, or at 0?
/// </summary>
[SerializeField] public bool RotatedColliderPivotAtCenter;
/// <summary>
/// Settings of the current rotate and duplicate section in collider creation.
/// </summary>
[SerializeField] public EasyColliderRotateDuplicate rotatedDupeSettings;
/// <summary>
/// When true, meshes created from creating convex hulls are saved as assets.
/// </summary>
[SerializeField] public bool SaveConvexHullAsAsset;
/// <summary>
/// Specifies how to search for a location to save the convex hull .asset files.
/// </summary>
[SerializeField] public CONVEX_HULL_SAVE_METHOD ConvexHullSaveMethod = CONVEX_HULL_SAVE_METHOD.PrefabMesh;
/// <summary>
/// if SaveConvexHullMeshAtSelected is false, saves at the path specified.
/// </summary>
[SerializeField] public string SaveConvexHullPath;
/// <summary>
/// Provides a subfolder to save colliders in an asset path when saving near the mesh
/// IE: if the mesh/prefab is found in an Environments/Meshes folder, an Environments/Meshes/ConvexHulls folder would be created to store the collision meshes
/// if using anything other than CONVEX_HULL_SAVE_METHOD.Folder
/// </summary>
[SerializeField] public string SaveConvexHullSubFolder;
/// <summary>
/// Suffix with which to save convex hulls.
/// </summary>
[SerializeField] public string SaveConvexHullSuffix;
/// <summary>
/// Selected vertice scaling colour
/// </summary>
[SerializeField] public Color SelectedVertColour;
[SerializeField] public KeyCode ShortcutInvert = KeyCode.I;
[SerializeField] public KeyCode ShortcutGrow = KeyCode.G;
[SerializeField] public KeyCode ShortcutGrowLast = KeyCode.L;
[SerializeField] public KeyCode ShortcutClear = KeyCode.C;
[SerializeField] public KeyCode ShortcutRing = KeyCode.R;
/// <summary>
/// Should the number of selected vertices be displayed in the ui?
/// </summary>
[SerializeField] public bool ShowSelectedVertexCount;
/// <summary>
/// Sphere method to use when creating a sphere collider.
/// </summary>
public SPHERE_COLLIDER_METHOD SphereColliderMethod;
/// <summary>
/// Should HandleUtility.GetHandleSize be used when using gizmos to draw to keep gizmo size constant regardless of distance to camera?
/// </summary>
[SerializeField] public bool UseFixedGizmoScale;
/// <summary>
/// Enables using left click to select vertices, and right click to select points.
/// </summary>
[SerializeField] public bool UseMouseClickSelection = true;
[SerializeField] public NORMAL_OFFSET VertexNormalOffsetType = NORMAL_OFFSET.Both;
/// <summary>
/// Amount to offset the vertex (in direction of it's averaged normal)
/// </summary>
[SerializeField] public float VertexNormalOffset = 0f;
/// <summary>
/// Amount to inset the vertex (in opposite direction of it's averaged normal)
/// </summary>
[SerializeField] public float VertexNormalInset = 0f;
/// <summary>
/// Method used when raycasting for closest vertices, add (only snap to unselected verts), remove (only snap to selected verts), both (default)
/// </summary>
[SerializeField] public VERTEX_SNAP_METHOD VertexSnapMethod;
/// <summary>
/// Key used to select vertices.
/// </summary>
[SerializeField] public KeyCode VertSelectKeyCode;
/// <summary>
/// Should we update the VHACD calculation and preview as parameters change?
/// </summary>
[SerializeField] public bool VHACDPreview;
/// <summary>
/// Sets all values to default values.
/// </summary>
public void SetDefaultValues()
{
#region VHACD settings
VHACDParameters = new VHACDParameters();
VHACDPreview = true;
#endregion
#region other settings
AutoIncludeChildSkinnedMeshes = true;
AutoSelectOnPrefabOpen = false;
DisplayTips = true;
DisplayAllVertices = false;
PopupDialogOnFinish = false;
PreviewEnabled = true;
RaycastDelayTime = 0.1f;
ShowSelectedVertexCount = false;
rotatedDupeSettings = new EasyColliderRotateDuplicate();
RotatedColliderPivotAtCenter = false;
CylinderAsCapsuleOrientation = false;
#endregion
#region autoskinned preferences
AutoSkinnedMinBoneWeight = 0.5f;
AutoSkinnedDepenetrate = false;
AutoSkinnedIterativeDepenetrationCount = 15;
AutoSkinnedPairing = true;
AutoSkinnedIndents = true;
AutoSkinnedColliderType = SKINNED_MESH_COLLIDER_TYPE.Box;
AutoSkinnedShrinkAmount = 0.5f;
// I find outside in to give the best results generally.
AutoSkinnedDepenetrateOrder = SKINNED_MESH_DEPENETRATE_ORDER.OutsideIn;
AutoSkinnedForce256Triangles = true;
AutoSkinnedUseDistanceDeltaPairing = true;
AutoSkinnedPairedDistanceDelta = 0.01f;
#endregion
#region inputs
// shifts do not work.
BoxSelectMinusKey = KeyCode.S;
BoxSelectPlusKey = KeyCode.A;
CreateFromPreviewKey = KeyCode.BackQuote;
PointSelectKeyCode = KeyCode.B;
UseMouseClickSelection = true;
VertSelectKeyCode = KeyCode.V;
EnableVertexToolsShortcuts = true;
ShortcutInvert = KeyCode.I;
ShortcutGrow = KeyCode.G;
ShortcutGrowLast = KeyCode.L;
ShortcutClear = KeyCode.C;
ShortcutRing = KeyCode.R;
#endregion
#region Collider Methods
CapsuleColliderMethod = CAPSULE_COLLIDER_METHOD.MinMax;
MeshColliderMethod = MESH_COLLIDER_METHOD.QuickHull;
PreviewColliderType = CREATE_COLLIDER_TYPE.BOX;
SphereColliderMethod = SPHERE_COLLIDER_METHOD.MinMax;
#endregion
#region Collider Settings / Paramaters
ShrinkGrow = 1f;
MergeCollidersRoundnessAccuracy = 10;
VertexNormalOffset = 0f;
VertexNormalInset = 0f;
VertexNormalOffsetType = NORMAL_OFFSET.Both;
ColliderHolder = COLLIDER_HOLDER.Default;
CylinderNumberOfSides = 16;
CylinderOrientation = CYLINDER_ORIENTATION.Automatic;
CylinderRotationOffset = 0.0f;
MeshColliderMethod = MESH_COLLIDER_METHOD.QuickHull;
RemoveMergedColliders = true;
CopyParentObjectLayer = true;
#endregion
#region drawing
CommonScalingMultiplier = 1.0f;
DefaultScale = 0.01f;
HoveredScaleMult = 1.0f;
DisplayAllScaleMult = 1.0f;
OverlapScaleMult = 1.0f;
SelectedScaleMult = 1.0f;
DisplayVerticesColour = Color.blue;
GizmoType = GIZMO_TYPE.SPHERE;
HoverVertColour = Color.cyan;
OverlapSelectedVertColour = Color.red;
PreviewDrawColor = Color.cyan;
if (SystemInfo.graphicsShaderLevel < 45)
{
RenderPointType = RENDER_POINT_TYPE.GIZMOS;
}
else
{
RenderPointType = RENDER_POINT_TYPE.SHADER;
}
SelectedVertColour = Color.green;
UseFixedGizmoScale = true;
#endregion
#region Save Convex Hull Settings
ConvexHullSaveMethod = CONVEX_HULL_SAVE_METHOD.PrefabMesh;
ResetDefaultSavePath();
SaveConvexHullAsAsset = true;
SaveConvexHullSuffix = "_ConvexHull_";
ConvexMeshReadWriteEnabled = true;
SaveConvexHullSubFolder = "";
#endregion
}
private static EasyColliderPreferences _Prefereneces;
public static EasyColliderPreferences Preferences
{
get
{
if (_Prefereneces == null)
{
// lazy load preferences when needed.
_Prefereneces = FindOrCreatePreferences();
}
return _Prefereneces;
}
}
private static EasyColliderPreferences FindOrCreatePreferences()
{
EasyColliderPreferences preferences;
string[] ecp = AssetDatabase.FindAssets("EasyColliderPreferences t:ScriptableObject");
string assetPath = "";
if (ecp.Length > 0)
{
assetPath = AssetDatabase.GUIDToAssetPath(ecp[0]);
if (ecp.Length > 1)
{
Debug.LogWarning("Easy Collider Editor has found multiple preferences files. Using the one located at " + assetPath);
}
preferences = AssetDatabase.LoadAssetAtPath(assetPath, typeof(EasyColliderPreferences)) as EasyColliderPreferences;
}
else
{
ecp = AssetDatabase.FindAssets("EasyColliderWindow t:script");
if (ecp.Length > 0)
{
assetPath = AssetDatabase.GUIDToAssetPath(ecp[0]);
if (ecp.Length > 1)
{
Debug.LogWarning("Easy Collider Editor has found multiple preferences files. Using the one located at " + assetPath);
}
}
// preferences = AssetDatabase.LoadAssetAtPath(assetPath, typeof(EasyColliderPreferences)) as EasyColliderPreferences;
// Create a new preferences file.
string prefPath = assetPath.Remove(assetPath.Length - 21) + "EasyColliderPreferences.asset";
preferences = CreateInstance<EasyColliderPreferences>();
preferences.SetDefaultValues();
AssetDatabase.CreateAsset(preferences, prefPath);
AssetDatabase.SaveAssets();
Debug.LogWarning("Easy Collider Editor did not find a preferences file, new preferences file created at " + prefPath);
}
return preferences;
}
static string defaultFolderName = "Convex Hulls";
/// <summary>
/// Resets the default save path to the location of the preferences scriptable object.
/// </summary>
public bool ResetDefaultSavePath()
{
// path to this preferences object.
SaveConvexHullPath = AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this));
SaveConvexHullPath = SaveConvexHullPath.Remove(SaveConvexHullPath.LastIndexOf("/")) + "/";
string rootPath = SaveConvexHullPath.Remove(SaveConvexHullPath.LastIndexOf("/Scripts/"), 9);
// if the preferences is in soemthing other than Assets (like packages, create a new folder at the root to hold convex hulls)
if (!SaveConvexHullPath.StartsWith("Assets"))
{
if (!AssetDatabase.IsValidFolder("Assets/Convex Hulls"))
{
AssetDatabase.CreateFolder("Assets", "Convex Hulls");
AssetDatabase.Refresh();
Debug.LogWarning("A folder has been created at: Assets/Convex Hulls to save convex mesh collider assets because Easy Collider exists in the packages folder. Folder to save assets in can be changed in Easy Collider Editor's preferences foldout.");
}
SaveConvexHullPath = "Assets/Convex Hulls/";
return true;
}
// remove invalid characters.
SaveConvexHullPath = string.Join("", SaveConvexHullPath.Split(Path.GetInvalidPathChars()));
// try creating and saving a Convex Hull specific folder if it doesn't exist.
if (!AssetDatabase.IsValidFolder(rootPath + "/" + defaultFolderName))
{
AssetDatabase.CreateFolder(rootPath, defaultFolderName);
AssetDatabase.Refresh();
if (AssetDatabase.IsValidFolder(rootPath + "/" + defaultFolderName))
{
SaveConvexHullPath = rootPath + "/" + defaultFolderName + "/";
return true;
}
}
else
{
SaveConvexHullPath = rootPath + "/" + defaultFolderName + "/";
}
return false;
}
public int GetCreationValuesHashCode()
{
return 0;
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fb5e4193615d8464a82ea935f90494a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderPreferences.cs
uploadId: 885904

View File

@@ -0,0 +1,714 @@
#if (UNITY_EDITOR)
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
namespace ECE
{
/// <summary>
/// Scriptable object to preview colliders that would be created from the given points
/// </summary>
[System.Serializable]
public class EasyColliderPreviewer : ScriptableObject
{
/// <summary>
/// Current calculation method of the collider type we are using as an int
/// </summary>
public int CurrentMethod = 0;
/// <summary>
/// Current attach to transform used in the calculation
/// </summary>
public Transform CurrentAttachTo;
/// <summary>
/// Current vertex count for the last calculation we did for a preview
/// </summary>
public int CurrentVertexCount = 0;
public float CurrentShrinkGrow = 0;
/// <summary>
/// Current count of the nubmer of colliders selected for the last preview calculation.
/// </summary>
public int CurrentColliderCount = 0;
public float CurrentVertexNormalOffset = 0;
public float CurrentVertexNormalInset = 0;
public NORMAL_OFFSET CurrentNormalOffset;
/// <summary>
/// Color to draw the preview lines with
/// </summary>
public Color DrawColor = Color.cyan;
private EasyColliderCreator _ECC;
/// <summary>
/// Creator that calculates the colliders we use to preview.
/// </summary>
/// <value></value>
public EasyColliderCreator ECC
{
get
{
if (_ECC == null)
{
_ECC = new EasyColliderCreator();
}
return _ECC;
}
set { _ECC = value; }
}
/// <summary>
/// Shader used to draw mesh colliders.
/// </summary>
private Shader MeshColliderShader;
/// <summary>
/// All the data from the current preview calculation
/// </summary>
public EasyColliderData PreviewData;
public List<EasyColliderData> RotateAndDuplicateData = new List<EasyColliderData>();
public bool HasValidRotateAndDuplicateData()
{
if (RotateAndDuplicateData == null || RotateAndDuplicateData.Count == 0) return false;
foreach (EasyColliderData d in RotateAndDuplicateData)
{
if (!d.IsValid)
{
return false;
}
}
return true;
}
//TODO: integrate calculation of auto skinned into normal update-preview stuff.
public List<EasyColliderData> AutoSkinnedData = new List<EasyColliderData>();
/// <summary>
/// Preview data hides that rotated colliders are different "collider types" so this keeps track of that for updating the preview.
/// </summary>
public CREATE_COLLIDER_TYPE ActualColliderType;
public ECE_WINDOW_TAB CurrentPreviewTab;
#region DrawPreviews
/// <summary>
/// Draws a preview from the current preview data.
/// </summary>
private void DrawPreview()
{
if (RotateAndDuplicateData != null && RotateAndDuplicateData.Count > 0)
{
// Debug.Log("Rotate and duplicate preview");
DrawList(RotateAndDuplicateData);
}
else if (AutoSkinnedData != null && AutoSkinnedData.Count > 0)
{
// Debug.Log("Auto skinned preivew");
DrawList(AutoSkinnedData);
}
else
{
// 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;
}
}
}
}
public void DrawList(List<EasyColliderData> data)
{
foreach (EasyColliderData PreviewData in data)
{
if (!PreviewData.IsValid) { continue; }
// 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;
}
}
}
/// <summary>
/// Draws a mesh collider preview
/// </summary>
/// <param name="data">Data from quickhull calculation</param>
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);
}
}
/// <summary>
/// Draws a box collider
/// </summary>
/// <param name="data">Data from box calculation</param>
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;
#if (UNITY_2017_2_OR_NEWER)
Vector3 scale = data.Matrix.lossyScale;
#else
Vector3 scale = CurrentAttachTo.transform.lossyScale;
#endif
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;
}
// dont know why mathf.approx wasn't working here, but the others don't work in rare cases. (top==bottom in this case works)
// which fixes issue where a preview would not be drawn in some extremely weird rare case.
if (data.Height < data.Radius * 2 || Mathf.Approximately(data.Height, data.Radius * 2) || top == bottom)
{
// 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);
}
/// <summary>
/// Draws a sphere collider
/// </summary>
/// <param name="data">Data from sphere calculation</param>
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;
#if (UNITY_2017_2_OR_NEWER)
Vector3 scale = data.Matrix.lossyScale;
#else
Vector3 scale = CurrentAttachTo.transform.lossyScale;
#endif
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();
PreviewData = null;
RotateAndDuplicateData = null;
AutoSkinnedData = null;
}
/// <summary>
/// Gets the method for the current preview collider type
/// </summary>
/// <param name="colliderType">type of collider we are previewing</param>
/// <returns>enum of method selected for collider type as an int</returns>
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 + (int)preferences.CylinderOrientation + ((int)preferences.CylinderRotationOffset * 1000);
}
else
{
return 0;
}
}
#region CalculatePreviews
/// <summary>
/// Calculates the preview data to be used to display.
/// </summary>
/// <param name="preferences">current preferences</param>
/// <param name="ece">current editor</param>
private void CalculatePreviewCollider(EasyColliderPreferences preferences, EasyColliderEditor ece)
{
// private void CalculatePreviewCollider(CREATE_COLLIDER_TYPE type, List<Vector3> worldVertices, List<Vector3> normals, GameObject attachTo, int method)
// CalculatePreviewCollider(preferences.PreviewColliderType, editor.GetWorldVertices(), editor.GetNormals(), editor.AttachToObject, GetMethodForColliderPreviewType(preferences));
List<Vector3> worldVertices = ece.GetWorldVertices(true);
switch (ActualColliderType)
{
case CREATE_COLLIDER_TYPE.BOX:
case CREATE_COLLIDER_TYPE.ROTATED_BOX:
PreviewData = ECC.CalculateBox(worldVertices, CurrentAttachTo.transform, ActualColliderType == CREATE_COLLIDER_TYPE.ROTATED_BOX, true);
break;
case CREATE_COLLIDER_TYPE.SPHERE:
PreviewData = CalculatePreviewSphere(worldVertices, CurrentAttachTo.transform, preferences.SphereColliderMethod);
break;
case CREATE_COLLIDER_TYPE.ROTATED_CAPSULE:
case CREATE_COLLIDER_TYPE.CAPSULE:
PreviewData = CalculatePreviewCapsule(worldVertices, CurrentAttachTo.transform, preferences.CapsuleColliderMethod, ActualColliderType == CREATE_COLLIDER_TYPE.ROTATED_CAPSULE);
break;
case CREATE_COLLIDER_TYPE.CONVEX_MESH:
PreviewData = CalculatePreviewMesh(worldVertices, ece.GetNormals(), CurrentAttachTo.transform, preferences.MeshColliderMethod);
break;
case CREATE_COLLIDER_TYPE.CYLINDER:
PreviewData = CalculatePreviewCylinder(worldVertices, CurrentAttachTo.transform);
break;
}
}
/// <summary>
/// Calculates the merge colliders preview data
/// </summary>
/// <param name="preferences">current preferences</param>
/// <param name="ece">current editor</param>
private void CalculatePreviewMerge(EasyColliderPreferences preferences, EasyColliderEditor ece)
{
// private void CalculatePreviewMerge(CREATE_COLLIDER_TYPE mergeTo, List<Collider> selectedColliders, GameObject attachTo, int method)
// CalculatePreviewMerge(preferences.PreviewColliderType, editor.SelectedColliders, editor.AttachToObject, GetMethodForColliderPreviewType(preferences));
if (CurrentColliderCount > 0)
{
PreviewData = ECC.MergeCollidersPreview(ece.SelectedColliders, preferences.PreviewColliderType, CurrentAttachTo);
}
else
{
PreviewData = new EasyColliderData();
}
}
/// <summary>
/// Calculates the rotate and duplicate preview data.
/// </summary>
/// <param name="preferences">current preferences</param>
/// <param name="ece">current editor</param>
private void CalculatePreviewRotateAndDuplicate(EasyColliderPreferences preferences, EasyColliderEditor ece)
{
if (ece.AttachToObject == null || ece.SelectedGameObject == null || preferences.rotatedDupeSettings.pivot == null) return;
RotateAndDuplicateData = ECC.CalculateRotateAndDuplicate(preferences, ece);
}
/// <summary>
/// Calculates a preview capsule
/// </summary>
/// <param name="worldVertices">world vertices used to calculate the collider</param>
/// <param name="attachTo">gameobject the collider will be attached to</param>
/// <param name="method">calculation method used to calculate the collider type as an int</param>
/// <param name="isRotated">will the collider be a rotated collider?</param>
/// <returns>Calculated capsule collider data</returns>
private EasyColliderData CalculatePreviewCapsule(List<Vector3> worldVertices, Transform attachTo, CAPSULE_COLLIDER_METHOD method, bool isRotated)
{
switch (method)
{
case CAPSULE_COLLIDER_METHOD.BestFit:
return ECC.CalculateCapsuleBestFit(worldVertices, attachTo, isRotated, true);
default:
return ECC.CalculateCapsuleMinMax(worldVertices, attachTo, method, isRotated, true);
}
}
/// <summary>
/// Calculates a preview cylinder
/// </summary>
/// <param name="worldVertices">world vertices used to calculate the collider</param>
/// <param name="attachTo">transform the collider will be attached to</param>
/// <returns>Calculated cylinder shaped mesh collider data</returns>
private MeshColliderData CalculatePreviewCylinder(List<Vector3> worldVertices, Transform attachTo)
{
MeshColliderData data = ECC.CalculateCylinderCollider(worldVertices, attachTo);
return data;
}
/// <summary>
/// Calculates the preview data for a convex mesh collider. (uses quickhull calculation for both messy and quickhull method)
/// </summary>
/// <param name="worldVertices">World-Space vertices that are selected</param>
/// <param name="attachTo">Current AttachTo object</param>
/// <param name="method">Mesh Collider generation method</param>
/// <returns>EasyCollider data with a mesh value set.</returns>
private MeshColliderData CalculatePreviewMesh(List<Vector3> worldVertices, List<Vector3> normals, 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.
// previewer now goes through ECC for meshes as well, as it should.
return ECC.CalculateHullData(worldVertices, attachTo);
}
/// <summary>
/// Calculates a preview sphere
/// </summary>
/// <param name="worldVertices">world vertices used to calculate the collider</param>
/// <param name="attachTo">gameobject the collider will be attached to</param>
/// <param name="method">calculation method used to calculate the collider type as an int</param>
/// <returns>Calculated sphere collider data</returns>
private EasyColliderData CalculatePreviewSphere(List<Vector3> 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
/// <summary>
/// Checks to see if the collider needs to be calculated, so the preview is updated.
/// </summary>
/// <param name="editor"></param>
/// <param name="colliderType"></param>
/// <param name="method"></param>
/// <returns>true if the preview needs to be updated.</returns>
private bool NeedsUpdate(EasyColliderPreferences preferences, EasyColliderEditor editor)
{
// private bool NeedsUpdate(EasyColliderEditor editor, CREATE_COLLIDER_TYPE colliderType, int method)
//(NeedsUpdate(editor, preferences.PreviewColliderType, GetMethodForColliderPreviewType(preferences)) || forceUpdate)
if (preferences.CurrentWindowTab == ECE_WINDOW_TAB.Creation)
{
if (CurrentVertexCount != editor.SelectedVertices.Count
|| (PreviewData == null && RotateAndDuplicateData == null && preferences.rotatedDupeSettings.enabled)
|| (PreviewData == null && preferences.rotatedDupeSettings.enabled == false)
|| (PreviewData != null && PreviewData.ColliderType != preferences.PreviewColliderType)
|| CurrentMethod != GetMethodForColliderPreviewType(preferences)
|| CurrentAttachTo != editor.AttachToObject.transform
|| ActualColliderType != preferences.PreviewColliderType
|| editor.HasTransformMoved()
|| CurrentVertexNormalOffset != preferences.VertexNormalOffset
|| CurrentVertexNormalInset != preferences.VertexNormalInset
|| CurrentShrinkGrow != preferences.ShrinkGrow
|| CurrentNormalOffset != preferences.VertexNormalOffsetType
)
{
return true;
}
}
else if (preferences.CurrentWindowTab == ECE_WINDOW_TAB.Editing)
{
if (CurrentColliderCount != editor.SelectedColliders.Count
|| PreviewData == null
|| CurrentMethod != GetMethodForColliderPreviewType(preferences)
|| CurrentAttachTo != editor.AttachToObject.transform
|| editor.HasTransformMoved()
|| ActualColliderType != preferences.PreviewColliderType)
{
return true;
}
return false;
}
return false;
}
/// <summary>
/// Updates the current preview if needed.
/// </summary>
/// <param name="editor"></param>
/// <param name="preferences"></param>
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(preferences, editor) || forceUpdate)
{
ActualColliderType = preferences.PreviewColliderType;
CurrentAttachTo = editor.AttachToObject.transform;
CurrentColliderCount = editor.SelectedColliders.Count;
CurrentMethod = GetMethodForColliderPreviewType(preferences);
CurrentPreviewTab = preferences.CurrentWindowTab;
CurrentVertexCount = editor.SelectedVertices.Count;
CurrentVertexNormalOffset = preferences.VertexNormalOffset;
CurrentVertexNormalInset = preferences.VertexNormalInset;
CurrentShrinkGrow = preferences.ShrinkGrow;
CurrentNormalOffset = preferences.VertexNormalOffsetType;
if (PreviewData == null)
{
PreviewData = new EasyColliderData();
}
if (preferences.CurrentWindowTab == ECE_WINDOW_TAB.Creation)
{
if (preferences.rotatedDupeSettings.enabled == true)
{
RotateAndDuplicateData = new List<EasyColliderData>();
CurrentAttachTo = preferences.rotatedDupeSettings.pivot.transform;
CalculatePreviewRotateAndDuplicate(preferences, editor);
}
else
{
CalculatePreviewCollider(preferences, editor);
}
PreviewData.ColliderType = ActualColliderType;
}
else if (preferences.CurrentWindowTab == ECE_WINDOW_TAB.Editing)
{
CalculatePreviewMerge(preferences, editor);
}
}
DrawPreview();
}
}
#region VHACDPREVIEW
/// <summary>
/// Array of triangles for use in VHACDResultPreview
/// </summary>
private int[] triangles;
/// <summary>
/// Array of vertices for use in VHACDResultPreview
/// </summary>
private Vector3[] vertices;
/// <summary>
/// List of randomized colors for use in VHACDResultPreview
/// </summary>
private List<Color> colors = new List<Color>();
public void DrawVHACDConversionPreview(List<EasyColliderData> data, VHACD_CONVERSION convertTo)
{
foreach (EasyColliderData d in data)
{
if (d == null) continue;
if (convertTo == VHACD_CONVERSION.Boxes)
{
BoxColliderData bcd = d as BoxColliderData;
if (bcd == null) continue;
DrawPreviewBox(bcd, DrawColor);
}
else if (convertTo == VHACD_CONVERSION.Capsules)
{
CapsuleColliderData ccd = d as CapsuleColliderData;
if (ccd == null) continue;
DrawPreviewCapsule(ccd, DrawColor);
}
else if (convertTo == VHACD_CONVERSION.Spheres)
{
SphereColliderData scd = d as SphereColliderData;
if (scd == null) continue;
DrawPreviewSphere(scd, DrawColor);
}
}
}
/// <summary>
/// Draws each mesh in the dictionary with a black wireframe, and a random color semi-transparent mesh.
/// </summary>
/// <param name="previewResult">Dictionary of transforms and meshes from the result of VHACD</param>
public void DrawVHACDResultPreview(Dictionary<Transform, Mesh[]> 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<Transform, Mesh[]> 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(1);
Graphics.DrawMeshNow(kvp.Value[i], kvp.Key.localToWorldMatrix);
wireMat.SetPass(1);
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

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 0975e5f3c5571d940891fd8dc17e7587
timeCreated: 1592761340
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderPreviewer.cs
uploadId: 885904

View File

@@ -0,0 +1,100 @@
using UnityEngine;
namespace ECE
{
/// <summary>
/// Properties to use when creating a collider.
/// </summary>
public struct EasyColliderProperties
{
/// <summary>
/// Marks the collider's isTrigger property
/// </summary>
public bool IsTrigger;
/// <summary>
/// Layer of gameobject when creating a rotated collider.
/// </summary>
public int Layer;
#if (UNITY_6000_0_OR_NEWER)
/// <summary>
/// Physic material to set on collider.
/// </summary>
public PhysicsMaterial PhysicMaterial;
#else
/// <summary>
/// Physic material to set on collider.
/// </summary>
public PhysicMaterial PhysicMaterial;
public void SetPhysicMat(PhysicMaterial physmat)
{
PhysicMaterial = physmat;
}
#endif
#if (UNITY_2022_2_OR_NEWER)
/// <summary>
/// whether or not the collidere generates contacts for physics.contact event.
/// </summary>
public bool ProvidesContacts;
/// <summary>
/// layer overrides - layer override priority property on a collider.
/// </summary>
public int LayerOverridePriority;
/// <summary>
/// layer overrides exclude layer mask property on colliders.
/// </summary>
public LayerMask ExcludeLayers;
/// <summary>
/// layer overrides include layer mask property on colliders.
/// </summary>
public LayerMask IncludeLayers;
#endif
/// <summary>
/// Orientation of created collider.
/// </summary>
public COLLIDER_ORIENTATION Orientation;
/// <summary>
/// Gameobject collider gets added to.
/// </summary>
public GameObject AttachTo;
/// <summary>
/// Properties with which to create a collider
/// </summary>
/// <param name="isTrigger">Should the collider's isTrigger property be true?</param>
/// <param name="layer">Layer of gameobject when creating a rotated collider</param>
/// <param name="physicMaterial">Physic Material to apply to a collider</param>
/// <param name="attachTo">GameObject to attach the collider to</param>
/// <param name="orientation">Orientation of the collider for generation</param>
public EasyColliderProperties(bool isTrigger, int layer,
#if (UNITY_6000_0_OR_NEWER)
PhysicsMaterial physicMaterial,
#else
PhysicMaterial physicMaterial,
#endif
GameObject attachTo, COLLIDER_ORIENTATION orientation = COLLIDER_ORIENTATION.NORMAL)
{
IsTrigger = isTrigger;
Layer = layer;
PhysicMaterial = physicMaterial;
AttachTo = attachTo;
Orientation = orientation;
#if (UNITY_2022_2_OR_NEWER)
ProvidesContacts = false;
LayerOverridePriority = 0;
IncludeLayers = 0;
ExcludeLayers = 0;
#endif
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6beb8fffd8449374b81cf9857e315469
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderProperties.cs
uploadId: 885904

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: ed797196cc351a34db4a5f88338cc1fe
timeCreated: 1600696552
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderQuickHull.cs
uploadId: 885904

View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace ECE
{
[System.Serializable]
public class EasyColliderRotateDuplicate
{
public enum ROTATE_AXIS
{
X,
Y,
Z,
}
public bool enabled;
public ROTATE_AXIS axis;
public int NumberOfDuplications = 4;
public float StartRotation = 0.0f;
public float EndRotation = 360f;
public GameObject pivot;
public GameObject attachTo;
}
}

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 5e6a373c203fb6b459f89497990e1b24
timeCreated: 1618839991
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderRotateDuplicate.cs
uploadId: 885904

View File

@@ -0,0 +1,397 @@
#if(UNITY_EDITOR)
using UnityEngine;
using UnityEditor;
using System.IO;
#if (UNITY_2021_2_OR_NEWER)
// prefab stage out of experimental.
using UnityEditor.SceneManagement;
#elif (UNITY_2018_3_OR_NEWER)
// prefabstage is still experimental
// but what it inherits from, PreviewSceneStage/Stage is not experimental as of 2020.1
using UnityEditor.Experimental.SceneManagement;
#endif
namespace ECE
{
public static class EasyColliderSaving
{
public class PrefabData
{
public UnityEngine.Object FoundObject;
public string AssetPath;
}
static PrefabData TryFindPrefabObject(GameObject go)
{
UnityEngine.Object foundObject = null;
PrefabData foundData = new PrefabData();
#if UNITY_2018_2_OR_NEWER
// 2018_2+
PrefabStage stage = PrefabStageUtility.GetCurrentPrefabStage();
if (stage != null)
{
if (stage.prefabContentsRoot != null)
{
foundObject = PrefabUtility.GetCorrespondingObjectFromSource(stage.prefabContentsRoot);
}
#if (UNITY_2020_1_OR_NEWER)
// asset path in 2020.1+
foundData.AssetPath = stage.assetPath;
#elif (UNITY_2018_3_OR_NEWER)
// prefab asset path 2018.3 to 2019.4
foundData.AssetPath = stage.prefabAssetPath;
#endif
}
// try using just the GO if were not in a prefab stage.
if (foundObject == null)
{
foundObject = PrefabUtility.GetCorrespondingObjectFromSource(go);
if (foundObject == null)
{
foundObject = PrefabUtility.GetOutermostPrefabInstanceRoot(go);
}
}
#else
// legacy unity support. -- (I dont test in earlier than 2019.4+ anymore)
foundObject = PrefabUtility.GetPrefabParent(go);
if (foundObject == null)
{
foundObject = PrefabUtility.FindPrefabRoot(go);
}
#endif
foundData.FoundObject = foundObject;
return foundData;
}
static PrefabData TryFindMeshOrSkinnedMeshObject(GameObject go)
{
PrefabData foundPrefabData = new PrefabData();
MeshFilter mf = go.GetComponent<MeshFilter>();
if (mf != null)
{
foundPrefabData.FoundObject = mf.sharedMesh;
}
else
{
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
if (smr != null)
{
foundPrefabData.FoundObject = smr.sharedMesh;
}
}
return foundPrefabData;
}
/// <summary>
/// Static preferences asset that is currently loaded.
/// </summary>
/// <value></value>
static EasyColliderPreferences ECEPreferences { get { return EasyColliderPreferences.Preferences; } }
/// <summary>
/// removes invalid characters (so path is valid) and trailing slashes (for compatibility with older versions of unity)
/// </summary>
/// <param name="path"></param>
/// <returns>Assets/Folder1/Folder</returns>
static string CleanAssetPath(string path)
{
//remove any invalid path characters.
path = string.Join("", path.Split(Path.GetInvalidPathChars()));
// no trailing slash is more compatible with older version of unity and AssetDatabase.IsValidFolder, so prefer no trailing slash when checking.
// folder path is saved with / at the end, so remove if we have to.
if (path[path.Length - 1] == '/')
{
path = path.Remove(path.Length - 1);
}
return path;
}
/// <summary>
/// Gets a valid path to save a convex hull at to feed into save convex hull meshes function.
/// </summary>
/// <param name="go">selected gameobject</param>
/// <param name="ECEPreferences">preferences object</param>
/// <returns>assetdb path like: Assets/Folder/OptionSubfolder/baseObjectsName</returns>
public static string GetValidConvexHullPath(GameObject go)
{
// use default specified path
// remove invalid characters from file name, just in case (user reported error, thanks!)
string goName = string.Join("_", go.name.Split(Path.GetInvalidFileNameChars()));
// start with the default path specified in preferences.
string path = ECEPreferences.SaveConvexHullPath;
// get path to gameobject
if (ECEPreferences.ConvexHullSaveMethod != CONVEX_HULL_SAVE_METHOD.Folder)
{
// bandaid for scaled temporary skinned mesh:
// as the scaled mesh filter is added during setup with the name Scaled Mesh Filter (Temporary)
if (go.name.Contains("Scaled Mesh Filter"))
{
go = go.transform.parent.gameObject; // set the gameobject to the temp's parent (as that will be a part of the prefab if it is one and thus should work.)
}
PrefabData foundPrefabData = null;
if (ECEPreferences.ConvexHullSaveMethod == CONVEX_HULL_SAVE_METHOD.Prefab)
{
foundPrefabData = TryFindPrefabObject(go);
}
else if (ECEPreferences.ConvexHullSaveMethod == CONVEX_HULL_SAVE_METHOD.Mesh)
{
foundPrefabData = TryFindMeshOrSkinnedMeshObject(go);
}
else if (ECEPreferences.ConvexHullSaveMethod == CONVEX_HULL_SAVE_METHOD.PrefabMesh)
{
foundPrefabData = TryFindPrefabObject(go);
if (foundPrefabData.FoundObject == null && string.IsNullOrEmpty(foundPrefabData.AssetPath))
{
foundPrefabData = TryFindMeshOrSkinnedMeshObject(go);
}
}
else if (ECEPreferences.ConvexHullSaveMethod == CONVEX_HULL_SAVE_METHOD.MeshPrefab)
{
foundPrefabData = TryFindMeshOrSkinnedMeshObject(go);
if (foundPrefabData.FoundObject == null)
{
foundPrefabData = TryFindPrefabObject(go);
}
}
// by default, use the found AssetPath, this should be the outermost prefab which is what is wanted.
string pathToPrefabOrMesh = foundPrefabData.AssetPath;
if (string.IsNullOrEmpty(pathToPrefabOrMesh) && foundPrefabData.FoundObject != null)
{
pathToPrefabOrMesh = AssetDatabase.GetAssetPath(foundPrefabData.FoundObject);
}
// but only use the path it if it exists.
// here we trim the object name just down to the last / so Asset/FolderWithPrefabInIt/
if (!string.IsNullOrEmpty(pathToPrefabOrMesh))
{
int index = pathToPrefabOrMesh.LastIndexOf("/");
if (index >= 0)
{
// removes object name.
path = pathToPrefabOrMesh.Remove(index + 1);
}
}
}
string originalPath = path;
path = CleanAssetPath(path);
// AssetDatabase.IsValidFolder("Assets/"); // not valid in 2019
// AssetDatabase.IsValidFolder("Assets"); // valid in 2019 and 6000. Prefer this one!
// prefab/mesh searched for a folder to save in and failed to find a valid path, default to the save convex hull path specified in preferences.
if ((!AssetDatabase.IsValidFolder(path) && path + "/" != ECEPreferences.SaveConvexHullPath)
// saving in a non-asset path and does not have allow saving convex hulls in packages enabled
|| (!path.StartsWith("Assets") && !ECEPreferences.AllowSavingConvexHullsInPackages))
{
path = ECEPreferences.SaveConvexHullPath;
path = CleanAssetPath(path);
// could not find a valid mesh/prefab location, but the fallback save convex hull folder DOES work.
if (AssetDatabase.IsValidFolder(path))
{
Debug.LogWarning("Easy Collider Editor: Could not find a valid location to save the collider. Saving in the folder specified in preferences: " + path);
}
}
// path and path fallback both are clean and have no trailing / here.
// this will automatically be true if we're using a folder (and it failed) OR the fallback on prefab/mesh fails (which also uses SaveConvexHullPath)
if (!AssetDatabase.IsValidFolder(path) && (path + "/").Contains(ECEPreferences.SaveConvexHullPath))
{
// path to ece preferences (in scripts typically)
path = AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(ECEPreferences));
// because the above is the full path to the file including the files name.
path = path.Remove(path.LastIndexOf("/"));
path = path.Replace("/Scripts", "");
path = string.Join("", path.Split(Path.GetInvalidPathChars()));
// asset path for preferences file is invalid, not in assets folder a
if (path.StartsWith("Assets") || ECEPreferences.AllowSavingConvexHullsInPackages)
{
if (!AssetDatabase.IsValidFolder(path + "/Convex Hulls"))
{
AssetDatabase.CreateFolder(path, "Convex Hulls");
AssetDatabase.Refresh();
}
path = path + "/Convex Hulls";
if (originalPath == ECEPreferences.SaveConvexHullPath)
{
// original path was saving to a folder.
ECEPreferences.SaveConvexHullPath = path + "/";
Debug.LogWarning("Easy Collider Editor: Convex Hull save path specified in Easy Collider Editor's preferences could not be found, or it is an invalid asset folder. Saving in: " + path + " as a fallback and updating preferences. If the folder has been moved or deleted, update to a different folder in the edit preferences foldout.\n\n Original path: " + originalPath);
}
// so that if we already have a valid
else if (!ECEPreferences.SaveConvexHullPath.StartsWith("Assets"))
{
// used an alternative mesh/prefab/prefabmesh save method.
ECEPreferences.SaveConvexHullPath = path + "/";
Debug.LogWarning("Easy Collider Editor: Could not find a valid location to save the collider and the save path specified in preferences could not be found, or it is an invalid asset folder. Saving in: " + path + " and updated preferences to this folder. If the folder has been moved or deleted, update to a different folder in the edit preferences foldout.\n\n Original path: " + originalPath);
}
}
else // in a packages folder probably without AllowSavingConvexHullsInPackages enabled.
{
// final fallback.
path = "Assets/Convex Hulls";
if (originalPath == ECEPreferences.SaveConvexHullPath)
{
// change default path to a valid path.
ECEPreferences.SaveConvexHullPath = path + "/";
#if (UNITY_2020_3_OR_NEWER)
AssetDatabase.SaveAssetIfDirty(ECEPreferences);
#endif
}
if (!AssetDatabase.IsValidFolder(path))
{
AssetDatabase.CreateFolder("Assets", "Convex Hulls");
AssetDatabase.Refresh();
if (originalPath == ECEPreferences.SaveConvexHullPath)
{
// original path was saving to a folder.
Debug.LogWarning("Easy Collider Editor: Convex Hull save path specified in Easy Collider Editor's preferences could not be found, or it is an invalid asset folder. A folder to save collider assets was created at: " + path + " and automatically set in preferences. A different folder in Easy Collider Editor's preferences foldout can be specified if desired.\n\n Original path: " + originalPath);
}
else
{
// used an alternative mesh/prefab/prefabmesh save method.
Debug.LogWarning("Easy Collider Editor: Could not find a valid location to save the collider and the save path specified in preferences could not be found, or it is an invalid asset folder. A new folder has been created at " + path + " to save mesh collider assets and automatically set in preferences. A different folder in Easy Collider Editor's preferences foldout can be specified if desired. \n\n Original path: " + originalPath);
}
}
}
}
// if they want a subfolder, create it if needed and use it.
if (!string.IsNullOrEmpty(ECEPreferences.SaveConvexHullSubFolder))
{
if (!AssetDatabase.IsValidFolder(path + "/" + ECEPreferences.SaveConvexHullSubFolder))
{
AssetDatabase.CreateFolder(path, ECEPreferences.SaveConvexHullSubFolder);
}
path += "/" + ECEPreferences.SaveConvexHullSubFolder + "/";
path = CleanAssetPath(path);
}
string fullPath = path + "/" + goName;
return fullPath;
}
/// <summary>
/// goes thorugh the path and finds the first non-existing path that can be used to save.
/// </summary>
/// <param name="path">Full path up to save location: ie C:/UnityProjects/ProjectName/Assets/Folder/baseObject</param>
/// <param name="suffix">Suffix to add to save files ie _Suffix_</param>
/// <returns>first valid path for AssetDatabase.CreateAsset ie baseObject_Suffix_0</returns>
private static string GetFirstValidAssetPath(string path, string suffix)
{
string validPath = path;
if (File.Exists(validPath + suffix + "0.asset"))
{
int i = 1;
while (File.Exists(validPath + suffix + i + ".asset"))
{
i += 1;
}
validPath += suffix + i + ".asset";
}
else
{
validPath += suffix + "0.asset";
}
// replace application's data path (Unity Editor: <path to project folder>/Assets)
// "Assets" earlier in the path should no longer cause issues.
validPath = validPath.Replace(Application.dataPath, "Assets");
return validPath;
}
/// <summary>
/// Creates and saves a mesh asset in the asset database with appropriate path and suffix.
/// </summary>
/// <param name="mesh">mesh</param>
/// <param name="attachTo">gameobject the mesh will be attached to, used to find asset path.</param>
public static void CreateAndSaveMeshAsset(Mesh mesh, GameObject attachTo)
{
if (mesh != null && !DoesMeshAssetExists(mesh))
{
string savePath = GetValidConvexHullPath(attachTo);
if (savePath != "")
{
string assetPath = GetFirstValidAssetPath(savePath, ECEPreferences.SaveConvexHullSuffix);
AssetDatabase.CreateAsset(mesh, assetPath);
AssetDatabase.SaveAssets();
}
}
}
/// <summary>
/// Checks if the asset already exists (needed for rotate and duplicate, as the mesh is the same mesh repeated.)
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
public static bool DoesMeshAssetExists(Mesh mesh)
{
string p = AssetDatabase.GetAssetPath(mesh);
if (p == null || p.Length == 0)
{
return false;
}
return true;
}
/// <summary>
/// Creates and saves an array of mesh assets in the assetdatabase at the path with the the format "savePath"+"suffix"+[0-n].asset
/// </summary>
/// <param name="savePath">Full path up to save location: ie C:/UnityProjects/ProjectName/Assets/Folder/baseObject</param>
/// <param name="suffix">Suffix to add to save files ie _Suffix_</param>
public static Mesh[] CreateAndSaveMeshAssets(Mesh[] meshes, string savePath, string suffix)
{
string firstAssetPath = null;
int assetSuffixIndex = -1;
for (int i = 0; i < meshes.Length; i++)
{
// get a new valid path for each mesh to save.
string path = GetFirstValidAssetPath(savePath, suffix);
try
{
if (ECEPreferences.CombinedVHACDColliders && firstAssetPath != null)
{
//adding a name to the mesh even though it isn't required to match the first assets name as it by default has the path's name.
string name = firstAssetPath.Remove(assetSuffixIndex, firstAssetPath.Length - assetSuffixIndex);
name = name.Remove(0, name.LastIndexOf("/") + 1);
meshes[i].name = name + suffix + i.ToString();
AssetDatabase.AddObjectToAsset(meshes[i], firstAssetPath);
}
else
{
AssetDatabase.CreateAsset(meshes[i], path);
}
}
catch (System.Exception error)
{
Debug.LogError("Error saving at path:" + path + ". Try changing the save Convex Hull path in Easy Collider Editor's preferences to a different folder.\n" + error.ToString());
}
if (firstAssetPath == null)
{
firstAssetPath = path;
assetSuffixIndex = firstAssetPath.IndexOf(suffix);
}
}
AssetDatabase.SaveAssets();
if (ECEPreferences.CombinedVHACDColliders)
{
// need to reload the assets and update the meshes array otherwise they don't point to the correct object
// only the first one will without this as for whatever reason create asset will correctly link the objects but adding an object to an asset will not.
var assets = AssetDatabase.LoadAllAssetsAtPath(firstAssetPath);
for (int i = 0; i < assets.Length; i++)
{
meshes[i] = (Mesh)assets[i];
}
}
return meshes;
}
}
}
#endif

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 6ddf66fc4a34fe740aee3cf08e0549af
timeCreated: 1600696849
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderSaving.cs
uploadId: 885904

View File

@@ -0,0 +1,26 @@
#if (UNITY_EDITOR)
namespace ECE
{
/// <summary>
/// A list of tips for users of easy collider editor.
/// </summary>
public static class EasyColliderTips
{
public const string TRY_MOUSE_CONTROL = "Want to be able to select vertices with the mouse? Try enabling the mouse controls in preferences.";
public const string NEW_MOUSE_CONTROL = "Use left click to select the highlighted vertex, and the right click to select the point under the mouse.";
public const string COMPUTE_SHADER_TIP = "You're system's shader model does not support shaders with a compute buffer. Be sure to use the gizmo display method instead of shader in the preferences.";
public const string EDIT_PREFS_FORCED_FOCUSED = "You are editing preferences with forced window focus enabled. To make this easier, try disabling vertex selection, or the force focus scene option in preferences.";
public const string FORCED_FOCUSED_WINDOW = "Forced window focus is enabled, you may not be able to edit values easily by typing. Disable vertex selection, or the force focus scene option in preferences.";
public const string IN_PLAY_MODE = "Vertex selection is not enabled when in play mode.";
public const string NO_MESH_FILTER_FOUND = "No mesh filter is on the selected gameobject, try enabling include child meshes.";
public const string WRONG_FOCUSED_WINDOW = "Vertex selection only works when the scene view window is focused.";
public const string ROTATED_BOX_COLLIDER_TIP = "The first 3 points determine the orientation of a rotated box collider.";
public const string ROTATED_CAPSULE_COLLIDER_TIP = "The first 2 points determine the orientation of a rotated capsule collider.";
public const string COLLIDER_CREATION_SHORTCUTS_1 = "Shortcuts:\n\t[1-7] Change collider preview\n\t[~] Create the collider being previewed\n\tDouble Tap [1-7] Create the collider";
public const string AUTO_SKILLED_CONTROL_PARAMETERS = "Holding CTRL while adjusting a parameter in Per Bone Settings will also adjust all of it's children.";
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3855a2c50be7c1742950276affa94392
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderTips.cs
uploadId: 885904

View File

@@ -0,0 +1,918 @@
#if (UNITY_EDITOR)
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
namespace ECE
{
public static class EasyColliderUIHelpers
{
/// <summary>
/// Stores all the collider icons
/// </summary>
public static Texture2D[] ColliderIcons;
public static Texture2D[] ColliderMergeIcons;
/// <summary>
/// Creates a bunch of buttons that function similar to an enum popup where only one can be selected.
/// </summary>
/// <param name="selected">Current selected enum</param>
/// <returns>Selected enum</returns>
public static Enum EnumButtonArray(Enum selected, string[] labels, string[] toolTips)
{
GUIStyle style = new GUIStyle(GUI.skin.button);
style.padding.left = 5;
style.padding.right = 5;
style.padding.bottom = 2;
style.padding.top = 0;
Array a = Enum.GetValues(selected.GetType());
int currentValue = Convert.ToInt32(selected);
for (int i = 0; i < a.Length; i++)
{
GUIContent content = new GUIContent(labels[i], toolTips[i]);
if ((int)a.GetValue(i) == currentValue)
{
Color TempGUIColor = GUI.color;
GUI.color = _DisabledButtonColor;
GUILayout.Button(content, style);
GUI.color = TempGUIColor;
}
else
{
if (GUILayout.Button(content, style))
{
return (Enum)a.GetValue(i);
}
}
}
return selected;
}
/// <summary>
/// Creates buttons with icons as content that return an Enum when clicked.
/// </summary>
/// <param name="selected"></param>
/// <param name="iconContentNames"></param>
/// <param name="toolTips"></param>
/// <returns></returns>
public static Enum EnumIconButtonArray(Enum selected, string[] iconContentNames, string[] toolTips)
{
GUIStyle style = new GUIStyle(GUI.skin.box);
style.padding = new RectOffset(0, 0, 0, 0);
style.margin = new RectOffset(0, 2, 4, 0);
Array a = Enum.GetValues(selected.GetType());
int currentValue = Convert.ToInt32(selected);
for (int i = 0; i < a.Length; i++)
{
GUIContent content = EditorGUIUtility.IconContent(iconContentNames[i], toolTips[i]);
if ((int)a.GetValue(i) == currentValue)
{
Color TempGUIColor = GUI.color;
GUI.color = _DisabledButtonColor;
GUILayout.Button(content, style);
GUI.color = TempGUIColor;
}
else
{
if (GUILayout.Button(content, style))
{
return (Enum)a.GetValue(i);
}
}
}
return selected;
}
/// <summary>
/// Gets all the editor icons and loads them and stores them in an array
/// </summary>
public static void GetIcons()
{
ColliderIcons = new Texture2D[7];
ColliderMergeIcons = new Texture2D[7];
string[] locs = AssetDatabase.FindAssets("ECEUIBox t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[0] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[0] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUIRotatedBox t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[1] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[1] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUISphere t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[2] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[2] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUICapsule t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[3] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[3] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUIRotatedCapsule t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[4] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[4] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUIConvexMesh t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[5] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[5] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
locs = AssetDatabase.FindAssets("ECEUICylinder t:texture2D");
if (locs.Length > 0)
{
ColliderIcons[6] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[0]));
ColliderMergeIcons[6] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
// ColliderMergeIcons[3] = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(locs[1]));
}
}
/// <summary>
/// checks to see if the icons have moved / need to be found.
/// </summary>
public static void VerifyIcons()
{
bool needsUpdate = false;
if (ColliderIcons == null || ColliderMergeIcons == null)
{
GetIcons();
}
else
{
foreach (Texture2D t2d in ColliderIcons)
{
if (t2d == null)
{
needsUpdate = true;
}
}
foreach (Texture2D t2d in ColliderMergeIcons)
{
if (t2d == null)
{
needsUpdate = true;
}
}
if (needsUpdate)
{
GetIcons();
}
}
}
/// <summary>
/// Helper method to create a button with a 32x32 Icon where the icons are found seperately and stored in an array.
/// </summary>
/// <param name="tooltip">tooltip to display when button is enabled</param>
/// <param name="disabledToolTip">tooltip to display when disabled</param>
/// <param name="iconNumber">icon number in texture2d array</param>
/// <param name="isEnabled">is this icon enable?</param>
/// <returns>true if enabled and the button is clicked, false otherwise</returns>
public static bool DisableableIconButton(string tooltip, string disabledToolTip, int iconNumber, bool isEnabled)
{
VerifyIcons();
if (ColliderIcons[iconNumber] == null) return false; // an icon was deleted.
GUIStyle style = new GUIStyle(GUI.skin.button);
style.imagePosition = ImagePosition.ImageAbove;
style.padding = new RectOffset(4, 4, 4, 4);
GUIContent content = new GUIContent("Text", ColliderIcons[iconNumber]);
GUI.enabled = isEnabled;
bool button = GUILayout.Button(content, style, GUILayout.ExpandWidth(false));
GUI.enabled = true;
return button;
}
static string[] alphaKeys = new string[7] { "1", "2", "3", "4", "5", "6", "7" };
private static string KeyCodeToString(KeyCode keyCode)
{
return alphaKeys[((int)keyCode - 257)];
}
private static GUIStyle _highlightedStyle;
private static GUIStyle HighLightedStyle
{
get
{
if (_highlightedStyle == null)
{
_highlightedStyle = new GUIStyle(GUI.skin.box);
Texture2D text = new Texture2D(1, 1);
text.SetPixel(0, 0, Color.red);
_highlightedStyle.normal.background = text;
_highlightedStyle.fixedHeight = 0;
_highlightedStyle.padding = new RectOffset(4, 4, 1, 16);
}
return _highlightedStyle;
}
}
private static GUIStyle _iconButtonLabelStyle;
private static GUIStyle IconButtonLabelStyle
{
get
{
if (_iconButtonLabelStyle == null)
{
_iconButtonLabelStyle = new GUIStyle(GUI.skin.box);
_iconButtonLabelStyle.richText = true;
_iconButtonLabelStyle.fixedHeight = 16;
_iconButtonLabelStyle.fixedWidth = 40;
_iconButtonLabelStyle.padding.bottom = 1;
_iconButtonLabelStyle.alignment = TextAnchor.LowerCenter;
}
return _iconButtonLabelStyle;
}
}
/// <summary>
/// Helper method to create a button with a 32x32 Icon where the icons are found seperately and stored in an array.
/// </summary>
/// <param name="tooltip">tooltip to display when button is enabled</param>
/// <param name="disabledToolTip">tooltip to display when disabled</param>
/// <param name="iconNumber">icon number in texture2d array</param>
/// <param name="isEnabled">is this icon enable?</param>
/// <returns>true if enabled and the button is clicked, false otherwise</returns>
public static bool DisableableIconButtonShortcutCreation(string tooltip, string disabledToolTip, int iconNumber, bool isEnabled, KeyCode shortCut, bool highlight)
{
VerifyIcons();
if (ColliderIcons.Length - 1 < iconNumber || ColliderIcons[iconNumber] == null) return false; // an icon was deleted.
GUIStyle style = new GUIStyle(GUI.skin.button);
style.padding = new RectOffset(4, 4, 1, 16);
GUIStyle labelStyle = new GUIStyle(GUI.skin.box);
labelStyle.richText = true;
labelStyle.fixedHeight = 16;
labelStyle.fixedWidth = 40;
labelStyle.padding.bottom = 1;
labelStyle.alignment = TextAnchor.LowerCenter;
GUIContent content = new GUIContent(ColliderIcons[iconNumber]);
Rect r = GUILayoutUtility.GetRect(content, style);
style.fixedHeight = 48;
GUI.enabled = isEnabled;
if (highlight && isEnabled)
{
Rect rect = new Rect(r.x - 2, r.y - 2, r.width + 4, r.height + 3);
Color bc = GUI.backgroundColor;
GUI.backgroundColor = new Color(0, 0.5f, 1f, 1f);
GUI.Box(rect, GUIContent.none, HighLightedStyle);
GUI.backgroundColor = bc;
}
style.fixedHeight = 48;
content.tooltip = isEnabled ? tooltip : disabledToolTip;
bool button = GUI.Button(r, content, style);
r.y += 32;
// better dark mode colors, also light mode enabled/disabled button colors.
string colorCode = isEnabled ? "black" : "#525252";
if (EditorGUIUtility.isProSkin)
{
colorCode = isEnabled ? "#C4C4C4" : "#6C6C6C";
}
GUI.Label(r, "<color=" + colorCode + "><b>" + KeyCodeToString(shortCut) + "</b></color>", IconButtonLabelStyle);
GUI.enabled = true;
return button;
}
public static bool DisableableIconButtonShortcutMerge(string tooltip, string disabledToolTip, int iconNumber, bool isEnabled, KeyCode shortCut, bool highlight)
{
VerifyIcons();
if (ColliderMergeIcons.Length - 1 < iconNumber || ColliderMergeIcons[iconNumber] == null) return false; // an icon was deleted.
GUIStyle style = new GUIStyle(GUI.skin.button);
style.padding = new RectOffset(4, 4, 1, 16);
GUIStyle labelStyle = new GUIStyle(GUI.skin.box);
labelStyle.richText = true;
labelStyle.fixedHeight = 16;
labelStyle.fixedWidth = 40;
labelStyle.padding.bottom = 1;
labelStyle.alignment = TextAnchor.LowerCenter;
GUIContent content = new GUIContent(ColliderMergeIcons[iconNumber]);
Rect r = GUILayoutUtility.GetRect(content, style);
style.fixedHeight = 48;
GUI.enabled = isEnabled;
if (highlight && isEnabled)
{
Rect rect = new Rect(r.x - 2, r.y - 2, r.width + 4, r.height + 3);
Color bc = GUI.backgroundColor;
GUI.backgroundColor = new Color(0, 0.5f, 1f, 1f);
GUI.Box(rect, GUIContent.none, HighLightedStyle);
GUI.backgroundColor = bc;
}
content.tooltip = isEnabled ? tooltip : disabledToolTip;
bool button = GUI.Button(r, content, style);
r.y += 32;
// better dark mode colors, also light mode enabled/disabled button colors.
string colorCode = isEnabled ? "black" : "#525252";
if (EditorGUIUtility.isProSkin)
{
colorCode = isEnabled ? "#C4C4C4" : "#6C6C6C";
}
GUI.Label(r, "<color=" + colorCode + "><b>" + KeyCodeToString(shortCut) + "</b></color>", IconButtonLabelStyle);
GUI.enabled = true;
return button;
}
/// <summary>
/// Background GUI Color to use for buttons when they are disabled
/// </summary>
public static Color _DisabledButtonColor = new Color(0.7f, 0.7f, 0.7f, 1f);
/// <summary>
/// Background GUI Color for toggles when they are disabled
/// </summary>
public static Color _DisabledToggleColor = new Color(1, 1, 1, 0.33f);
/// <summary>
/// Creates a disableable and undoable foldout list.
/// Allows you to pass a method to check if an item a user is trying to add is valid before adding to the list.
/// Has an X button beside each item to directly remove it, and a clear list button at the bottom.
/// </summary>
/// <param name="obj">Object to record undo on</param>
/// <param name="foldoutContent">GUI Content of the foldout</param>
/// <param name="disabledText">Text to be displayed when the foldout is disabled and open</param>
/// <param name="isOpen">references to the bool controlling if the foldout is open</param>
/// <param name="list">List of items to display</param>
/// <param name="objType">Type to use in object field</param>
/// <param name="OnAddMethod">Method that returns true if the object should be added to the list</param>
/// <param name="isEnabled">Is the foldout enabled?</param>
/// <typeparam name="T">List's type</typeparam>
public static void DisableableFoldoutList<T>(
UnityEngine.Object obj,
GUIContent foldoutContent,
string disabledText, ref bool isOpen,
ref List<T> list,
System.Type objType,
Func<T, bool> OnAddMethod,
bool isEnabled) where T : UnityEngine.Object
{
// createa foldout.
isOpen = EditorGUILayout.Foldout(isOpen, foldoutContent);
if (isOpen) // only display if the foldout is open.
{
if (isEnabled) // if the list is enabled, display it
{
// Variables to keep track of current item being displayed
T previous; // current item prior to it changing (if it does)
T current; // current item after changing (if it does)
List<T> itemsToRemove = new List<T>();
for (int i = 0; i < list.Count; i++)
{
// each item is horizontally displayed
EditorGUILayout.BeginHorizontal();
// quick removal button
if (GUILayout.Button("X", GUILayout.ExpandWidth(false)))
{
itemsToRemove.Add(list[i]);
}
else
{
// Create a field for each list
previous = list[i];
current = (T)EditorGUILayout.ObjectField(list[i], objType, true);
if (current == null) // if the new item is null, mark for removal.
{
itemsToRemove.Add(list[i]);
}
// otherwise, call the onadd method to see if the item in the field should be added to the list
else if (!OnAddMethod(current))
{
// if it shouldn't be added, then set the item to the previous item
// this cleans up items that are in the list when the onadd method uses different parameters
if (!OnAddMethod(previous))
{
// if that item is also invalid, remove it fromt he list.
itemsToRemove.Add(list[i]);
}
}
else
{
// the new item is valid, set that list item to that value.
list[i] = current;
}
}
EditorGUILayout.EndHorizontal();
}
if (itemsToRemove.Count > 0)
{
// record the removal of all items to remove.
Undo.RecordObject(obj, "Change List");
foreach (T item in itemsToRemove) list.Remove(item);
}
// Use an empty object field at the bottom as a way to quickly add to the list.
current = (T)EditorGUILayout.ObjectField(null, objType, true);
// if its not null and passes the OnAddMethod function
if (current != null && OnAddMethod(current))
{
// record the undo of adding an object
Undo.RecordObject(obj, "Change List");
list.Add(current);
}
if (GUILayout.Button("Clear List"))
{
Undo.RecordObject(obj, "Clear List");
list.Clear();
}
}
else // list is disabled, just display the disabled text as a lebel when the foldout is opened.
{
GUIStyle label = new GUIStyle(GUI.skin.label);
label.wordWrap = true;
EditorGUILayout.LabelField(disabledText, label);
}
}
}
/// <summary>
/// Creates a button that allows undoable changing of a keycode value.
/// Button displays current keycode, then press any key when pressed and listens for a keypress, then updates the keycode.
/// </summary>
/// <param name="obj">Object to record undo on.</param>
/// <param name="label">Label to display beside button</param>
/// <param name="key">KeyCode to change. Should be unique for each button.</param>
/// <param name="isChanging">Bool representing whether it should be listening to key presses. Should be unique for each button.</param>
public static bool ChangeButtonKeyCodeUndoable(UnityEngine.Object obj, string label, string labelTooltip, ref KeyCode key, ref bool isChanging, float windowWidth, bool drawlabel = false)
{
float labelWidth = 100;
// -15 to add padding to prevent a horizontal scrollbar always being present, this can probably be actually calculated from some unity internal padding values but whatever.
float buttonWidth = windowWidth - labelWidth - 25;
if (buttonWidth < 120) { buttonWidth = 120; }
GUIStyle pressedButtonStyle = new GUIStyle(GUI.skin.box);
pressedButtonStyle.fontStyle = FontStyle.Bold;
pressedButtonStyle.fixedWidth = buttonWidth;
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.fixedWidth = buttonWidth;
//buttonStyle.min
//buttonStyle.stretchWidth = true;
// EditorGUILayout.BeginHorizontal();
// GUILayout.Label(new GUIContent(label, labelTooltip));
// EditorGUILayout.LabelField(new GUIContent(label, labelTooltip), GUILayout.ExpandWidth(false));
string buttonTitle = isChanging ? "Press a key" : key.ToString();
if (drawlabel)
{
EditorGUILayout.BeginHorizontal();
// GUILayout.Label(new GUIContent(label, labelTooltip), GUILayout.ExpandWidth(false));
LabelWidth(label, labelTooltip, labelWidth);
}
if (GUILayout.Button(new GUIContent(buttonTitle, "Click then press a key to change.\nModifier keys (like alt, ctrl, space, shift, etc.) can not be used."), isChanging ? pressedButtonStyle : buttonStyle))
{
isChanging = true;
}
if (drawlabel)
{
EditorGUILayout.EndHorizontal();
}
// EditorGUILayout.EndHorizontal();
if (isChanging)
{
if (CheckKeypressChangeUndoable(obj, ref key))
{
isChanging = false;
}
}
return isChanging;
}
/// <summary>
/// Changes the KeyCode of keyCode through an undoable action when a key is pressed down.
/// </summary>
/// <param name="obj">Object to record undo on.</param>
/// <param name="keyCode">KeyCode to change to new key</param>
/// <returns>true if key was changed.</returns>
private static bool CheckKeypressChangeUndoable(UnityEngine.Object obj, ref KeyCode keyCode)
{
if (Event.current != null // have an event.
&& Event.current.type == EventType.KeyDown // a key down event.
&& Event.current.keyCode != KeyCode.None && !IsModifierKeyUsed(Event.current.keyCode)) // a key down event that wasn't None.
{
Undo.RecordObject(obj, "Change keycode");
keyCode = Event.current.keyCode;
return true;
}
return false;
}
public static void HorizontalLineLight()
{
HorizontalLine(new Color(0, 0, 0, 0.25f));
}
/// <summary>
/// Draws a vertical line of thickness and color at the current control rect at a single line height.
/// </summary>
/// <param name="color"></param>
/// <param name="lineThickness"></param>
/// <param name="paddingLeft"></param>
/// <param name="extraHeight"></param>
public static void VerticalLine(Color color, float lineThickness = 1f, float paddingLeft = 0f, float extraHeight = 2f)
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.Width(lineThickness));
r.height = EditorGUIUtility.singleLineHeight + extraHeight;
r.width = lineThickness;
r.x += paddingLeft;
EditorGUI.DrawRect(r, color);
}
/// <summary>
/// draws a horizontal line of a given color, thickness, and padding. Centered in the rect.
/// </summary>
/// <param name="color">color of the line</param>
/// <param name="lineThickness">thickness of the line</param>
/// <param name="padding"></param>
public static void HorizontalLine(Color color, float lineThickness = 2f, float padding = 6f)
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.Height(padding));
// thickness of line to draw is the height
r.height = lineThickness;
// center the line in the rect.
r.y = r.y + (padding - lineThickness) / 2;
// draw the rect.
EditorGUI.DrawRect(r, color);
}
public static void VerticalSpace(float size)
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.Height(size));
EditorGUI.DrawRect(r, new Color(0, 0, 0, 0));
}
/// <summary>
/// checks to see if the keycode is one of the many modifier keys.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static bool IsModifierKeyUsed(KeyCode value)
{
switch (value)
{
case KeyCode.LeftShift:
case KeyCode.RightShift:
case KeyCode.LeftControl:
case KeyCode.RightControl:
case KeyCode.LeftAlt:
case KeyCode.RightAlt:
case KeyCode.LeftCommand:
case KeyCode.RightCommand:
case KeyCode.Numlock:
case KeyCode.CapsLock:
case KeyCode.LeftWindows: // covers both keycodes for right/left apple as well.
case KeyCode.RightWindows:
return true;
default:
return false;
}
}
/// <summary>
/// In case we want to modify all labels used in the future more easily.
/// Just makes a GUILayout.Label(label)
/// </summary>
/// <param name="label"></param>
public static void Label(string label, string tooltip = "")
{
GUILayout.Label(new GUIContent(label, tooltip));
}
public static void LabelWidth(string label, string tooltip = "", float width = -1)
{
GUIStyle labelStyle = new GUIStyle(GUI.skin.label);
if (width > 0) { labelStyle.fixedWidth = width; }
GUILayout.Label(new GUIContent(label, tooltip), labelStyle);
}
public static void LabelEmptyNoStretch(float size = 50f)
{
// float currentSize = EditorGUIUtility.labelWidth;
// EditorGUIUtility.labelWidth = size;
GUIStyle style = new GUIStyle(GUI.skin.label);
style.stretchWidth = false;
GUILayout.Label("", style);
// EditorGUIUtility.labelWidth = currentSize;
}
public static void LabelBold(string label, string tooltip = "")
{
GUIStyle style = new GUIStyle(GUI.skin.label);
style.fontStyle = FontStyle.Bold;
style.stretchWidth = false;
if (tooltip == "")
{
GUILayout.Label(label, style);
}
else
{
GUILayout.Label(new GUIContent(label, tooltip), style);
}
}
public static bool FoldoutBold(string label, ref bool foldout, string tooltip = "")
{
GUIStyle style = new GUIStyle(EditorStyles.foldout);
style.fontStyle = FontStyle.Bold;
style.stretchWidth = false;
if (tooltip == "")
{
// GUILayout.Label(label, style);
foldout = EditorGUILayout.Foldout(foldout, label, style);
}
else
{
foldout = EditorGUILayout.Foldout(foldout, new GUIContent(label, tooltip), style);
}
return foldout;
}
public static void LabelIcon(string label, string iconName, string tooltip = "")
{
GUIStyle style = new GUIStyle(GUI.skin.label);
style.wordWrap = true;
GUIContent icon = EditorGUIUtility.IconContent(iconName, tooltip);
EditorGUILayout.BeginHorizontal();
GUILayout.Label(icon, GUILayout.ExpandWidth(false));
GUILayout.Label(new GUIContent(label, tooltip), style);
GUILayout.Label(icon, GUILayout.ExpandWidth(false));
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// Creates an undoable color field
/// </summary>
/// <param name="obj">Object to record the undo on</param>
/// <param name="content">GUI Content for the auto-layout field</param>
/// <param name="undoString">String to use for undos</param>
/// <param name="value">Value of the color</param>
public static void ColorFieldUndoable(UnityEngine.Object obj, string undoString, ref Color value)
{
Color _ColorField = value;
EditorGUI.BeginChangeCheck();
_ColorField = EditorGUILayout.ColorField(_ColorField);
if (EditorGUI.EndChangeCheck())
{
Undo.RegisterCompleteObjectUndo(obj, undoString);
value = _ColorField;
}
}
public static bool DisableableButtonWithShortcut(string text, string enabledTooltip, string disabledTooltip, bool isEnabled, KeyCode shortcut, bool isShortcutEnabled)
{
if (isShortcutEnabled)
{
text = text + " (" + shortcut.ToString() + ")";
}
GUI.enabled = isEnabled;
bool button = GUILayout.Button(new GUIContent(text, isEnabled ? enabledTooltip : disabledTooltip));
GUI.enabled = true;
return button;
}
/// <summary>
/// Creates a button that displays different if it is enabled or disabled. Button always returns false if disabled.
/// <param name="title">text of button</param>
/// <param name="enabledTooltip">tool tip for button when enabled</param>
/// <param name="disabledTooltip">tool tip for box when disabled</param>
/// <param name="isEnabled">is the button enabled?</param>
/// <returns>false if disabled, true if enabled and button is clicked</returns>
public static bool DisableableButton(string text, string enabledTooltip, string disabledTooltip, bool isEnabled)
{
GUI.enabled = isEnabled;
bool button = GUILayout.Button(new GUIContent(text, isEnabled ? enabledTooltip : disabledTooltip));
GUI.enabled = true;
return button;
}
/// <summary>
/// Creates an undoable float field that can also be disabled
/// </summary>
/// <param name="obj">Object to record the undo on</param>
/// <param name="label">Label of the float field</param>
/// <param name="tooltip">Tooltip to display when enabled</param>
/// <param name="disabledTooltip">Tooltip to display when disabled</param>
/// <param name="undoString">String to use for undos</param>
/// <param name="value">Value of the float</param>
/// <param name="isEnabled">Is the float field enabled?</param>
public static void DisableableFloatFieldUndoable(UnityEngine.Object obj, string label, string tooltip, string disabledTooltip, string undoString, ref float value, bool isEnabled)
{
GUI.enabled = isEnabled;
FloatFieldUndoable(obj, new GUIContent(label, isEnabled ? tooltip : disabledTooltip), undoString, ref value);
GUI.enabled = true;
}
/// <summary>
/// Creates a left toggle if the toggle is enabled that functions normally,
/// otherwise creates a style toggle that is not toggleable and grayed-out.
/// </summary>
/// <param name="text">Text to show beside the toggle</param>
/// <param name="enabledTooltip">Tool tip when toggle is enabled</param>
/// <param name="disabledTooltip">Tool tip when toggle is disabled</param>
/// <param name="isEnabled">Is the toggle enabled</param>
/// <param name="toggle">Bool the toggle controls</param>
/// <returns>Value of toggle</returns>
public static bool DisableableToggleLeft(string text, string enabledTooltip, string disabledTooltip, bool isEnabled, bool toggle)
{
GUI.enabled = isEnabled;
bool toggleValue = GUILayout.Toggle(toggle, new GUIContent(text, isEnabled ? enabledTooltip : disabledTooltip));
GUI.enabled = true;
return toggleValue;
}
/// <summary>
/// Creates an undoable float field.
/// </summary>
/// <param name="obj">Object to record the undo on</param>
/// <param name="content">GUI Content for the auto-layout field</param>
/// <param name="undoString">String to use for undos</param>
/// <param name="value">Value of the float</param>
public static void FloatFieldUndoable(UnityEngine.Object obj, GUIContent content, string undoString, ref float value, int labelSize = -1)
{
float _FloatField = value;
EditorGUI.BeginChangeCheck();
float w = EditorGUIUtility.labelWidth;
if (labelSize > 0)
{
EditorGUIUtility.labelWidth = labelSize;
}
_FloatField = EditorGUILayout.FloatField(content, _FloatField);
if (EditorGUI.EndChangeCheck())
{
Undo.RegisterCompleteObjectUndo(obj, undoString);
value = _FloatField;
}
EditorGUIUtility.labelWidth = w;
}
static GUIStyle ButtonStyle;
public static bool IconButton(string iconContentName, string tooltip)
{
ButtonStyle = new GUIStyle(GUI.skin.button);
ButtonStyle.padding = new RectOffset(4, 4, 1, 1);
ButtonStyle.margin = new RectOffset(4, 4, 2, 0);
ButtonStyle.stretchWidth = false;
GUIContent icon = EditorGUIUtility.IconContent(iconContentName, tooltip);
// text was not tooltip!
icon.tooltip = tooltip;
if (GUILayout.Button(icon, ButtonStyle))
{
return true;
}
return false;
}
/// <summary>
/// Creates a float slider that converts basePow^current to an integer in actual result.
/// </summary>
/// <param name="current">Current Value of the float slider</param>
/// <param name="min">Min Value of the float slider</param>
/// <param name="max">Max value of the float slider</param>
/// <param name="actualResult">Result of all calculations that is actually used</param>
/// <param name="aMin">Min value to clamp result to</param>
/// <param name="aMax">Max value to clamp result to</param>
/// <param name="basePow">power to use when calculating actual result in Mathf.Pow(basePow, current)</param>
/// <returns>Current value of the slider itself</returns>
public static float SliderFloatToIntBase2(GUIContent label, float current, float min, float max, ref int actualResult, int aMin, int aMax, float basePow = 2f)
{
EditorGUILayout.BeginHorizontal();
// get the current control rect.
Rect r = EditorGUILayout.GetControlRect();
// quarter of width goes to the label.
r.width = r.width / 4;
// label of the slider
EditorGUI.LabelField(r, label);
// set up the control name for the slider
GUI.SetNextControlName("FloatSlider" + current);
// keep track if the slider changes
EditorGUI.BeginChangeCheck();
//sliderbar uses half of space.
r.x += r.width;
r.width *= 2;
// draw the slider.
current = GUI.HorizontalSlider(r, current, min, max);
// if it changes, and is not focused, change the focus to the slider
// this helps keep the area where you can directly enter the number with the keyboard updated as the slider moves.
if (EditorGUI.EndChangeCheck() && GUI.GetNameOfFocusedControl() != "FloatSlider" + current)
{
GUI.FocusControl("FloatSlider" + current);
}
// Set the actual result using the current slider.
actualResult = (int)Mathf.Clamp(Mathf.Pow(basePow, current), aMin, aMax);
// check if the user has updated the int field input
EditorGUI.BeginChangeCheck();
// adjust rect to use last quarter of space.
r.x += r.width + 8;
r.width = r.width / 2 - 8;
actualResult = EditorGUI.IntField(r, actualResult);
// actualResult = EditorGUILayout.IntField(actualResult, GUILayout.ExpandWidth(false));
if (EditorGUI.EndChangeCheck())
{
actualResult = Mathf.Clamp(actualResult, aMin, aMax);
// update the current value if the text has changed.
current = Mathf.Log(actualResult, 2);
}
EditorGUILayout.EndHorizontal();
// were done, return the current value.
return current;
}
/// <summary>
/// Creates an undoable toggle field.
/// </summary>
/// <param name="obj">Object to record the undo on</param>
/// <param name="content">GUI Content for the auto-layout field</param>
/// <param name="undoString">String to use for undos</param>
/// <param name="value">Value of the toggle</param>
public static void ToggleLeftUndoable(UnityEngine.Object obj, GUIContent content, string undoString, ref bool value, float labelWidth = 10f)
{
bool _ToggleField = value;
EditorGUI.BeginChangeCheck();
// _ToggleField = EditorGUILayout.ToggleLeft(content, _ToggleField);
float lw = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = labelWidth;
// _ToggleField = GUILayout.Toggle(_ToggleField, content, GUILayout.ExpandWidth(false));
_ToggleField = EditorGUILayout.ToggleLeft(content, _ToggleField);
EditorGUIUtility.labelWidth = lw;
if (EditorGUI.EndChangeCheck())
{
// again record only works in some cases, and complete works significantly better.
// ie can't record changing DrawGizmos without the complete object undo.
Undo.RegisterCompleteObjectUndo(obj, undoString);
value = _ToggleField;
}
}
/// <summary>
/// creates a toggle left with no change check
/// </summary>
/// <param name="content"></param>
/// <param name="value"></param>
/// <param name="labelWidth"></param>
public static bool ToggleLeft(GUIContent content, bool value, float labelWidth = 10f)
{
bool _ToggleField = value;
float lw = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = labelWidth;
_ToggleField = EditorGUILayout.ToggleLeft(content, _ToggleField);
EditorGUIUtility.labelWidth = lw;
return _ToggleField;
}
public static Enum EnumPopup(GUIContent content, Enum selected, float labelWidth = 50f)
{
float lw = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = labelWidth;
GUIStyle miniPopupStyle = GUI.skin.GetStyle("MiniPopup");
if (miniPopupStyle != null)
{
GUIStyle enumStyle = new GUIStyle(miniPopupStyle);
Enum val = EditorGUILayout.EnumPopup(content, selected, enumStyle);
EditorGUIUtility.labelWidth = lw;
return val;
}
else
{
Enum val = EditorGUILayout.EnumPopup(content, selected);
EditorGUIUtility.labelWidth = lw;
return val;
}
}
}
}
#endif

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 6d01889b53b9585409077f8e53708c46
timeCreated: 1592405766
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderUIHelpers.cs
uploadId: 885904

View File

@@ -0,0 +1,478 @@
#if (UNITY_EDITOR)
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
using System;
using System.Linq;
namespace ECE
{
//Future potential features:
// The option to covert the convex hulls generated into other colliders, like box colliders (boxelization is a fun term).
// So that we can leverage VHACD results into alternative basic colliders.
// New version of vhacd is out but in my testing it is much slower than the older performance branch version
// additionally, it offers less adjustable parameters, which makes it harder to get a good fit,
// even though the fit isn't perfect with any vhacd implementation since it's approximate convex decomposition
// and even though the current parameters aren't particularily understandable, you can adjust them and see the output quickly until something is close enough.
public class EasyColliderVHACD
{
// If you're getting an error about DLL not found exception, you'll likely be pointed to this line by the error
// near the top of this file you'll find " const string dllName = "ECE_VHACD"; " ,
// this is where the dll name is placed (these same comments are there as well)
// To fix this on Mac: sometimes it can not recognize the .bundle, try either "ECE_VHACD.bundle" or "ECE_VHACD" as the dllName string
// Occasionally this can also happen when the dll/bundles are updated, and imported into unity after they were already used
// to correctly update when vhacd is updated, be sure to close unity, and immediately update the asset on opening the project
// currently only supports windows, and OSX (arm / silicon and intel) versions of unity.
const string dllName = "ECE_VHACD";
[DllImport(dllName, EntryPoint = "GetMaxNumVerticesPerCH")]
private static extern uint GetMaxNumVerticesPerCH();
// extern "C" VHACD_API double* GetConvexHullCenter();
[DllImport(dllName, EntryPoint = "GetConvexHullCenter")]
private static extern IntPtr GetConvexHullCenter();
// extern "C" VHACD_API unsigned int* GetConvexHullTriangles();
[DllImport(dllName, EntryPoint = "GetConvexHullTriangles")]
private static extern IntPtr GetConvexHullTriangles();
// extern "C" VHACD_API double* GetConvexHullPoints();
[DllImport(dllName, EntryPoint = "GetConvexHullPoints")]
private static extern IntPtr GetConvexHullPoints();
// extern "C" VHACD_API void Compute();
[DllImport(dllName, EntryPoint = "Compute")]
private static extern void Compute();
// extern "C" VHACD_API bool Create();
[DllImport(dllName, EntryPoint = "Create")]
private static extern bool Create();
// extern "C" VHACD_API bool Create_ASYNC();
[DllImport(dllName, EntryPoint = "Create_ASYNC")]
private static extern bool Create_ASYNC();
// extern "C" VHACD_API void Destroy();
[DllImport(dllName, EntryPoint = "Destroy")]
private static extern void Destroy();
// extern "C" VHACD_API int GetConvexHullNumPoints();
[DllImport(dllName, EntryPoint = "GetConvexHullNumPoints")]
private static extern int GetConvexHullNumPoints();
// extern "C" VHACD_API int GetConvexHullNumTriangles();
[DllImport(dllName, EntryPoint = "GetConvexHullNumTriangles")]
private static extern int GetConvexHullNumTriangles();
// extern "C" VHACD_API double GetConvexHullPoint(int index);
[DllImport(dllName, EntryPoint = "GetConvexHullPoint")]
private static extern double GetConvexHullPoint(int index);
// extern "C" VHACD_API int GetConvexHullTriangle(int index);
[DllImport(dllName, EntryPoint = "GetConvexHullTriangle")]
private static extern int GetConvexHullTriangle(int index);
// extern "C" VHACD_API double GetConvexHullVolume();
[DllImport(dllName, EntryPoint = "GetConvexHullVolume")]
private static extern double GetConvexHullVolume();
// extern "C" VHACD_API int GetNumberOfConvexHulls();
[DllImport(dllName, EntryPoint = "GetNumberOfConvexHulls")]
private static extern int GetNumberOfConvexHulls();
// extern "C" VHACD_API int GetPointSize();
[DllImport(dllName, EntryPoint = "GetPointSize")]
private static extern int GetPointSize();
// extern "C" VHACD_API int GetPointSize();
[DllImport(dllName, EntryPoint = "IsReady")]
private static extern bool IsReady();
// extern "C" VHACD_API void SetConvexHull(int index);
[DllImport(dllName, EntryPoint = "SetConvexHull")]
private static extern void SetConvexHull(int index);
// extern "C" VHACD_API void SetMaxHulls(int value);
[DllImport(dllName, EntryPoint = "SetMaxHulls")]
private static extern void SetMaxHulls(int value);
// extern "C" VHACD_API void SetMaxVerticesPerHull(int value);
[DllImport(dllName, EntryPoint = "SetMaxVerticesPerHull")]
private static extern void SetMaxVerticesPerHull(int value);
// For branch PERFORMANCE ENHANCEMENTS
// extern "C" VHACD_API void SetParameters(
// double concavity,
// double alpha,
// double beta,
// double minVolumePerConvexHull,
// int resolution,
// int maxNumVerticesPerCH,
// int planeDownsampling,
// int convexhullDownsampling,
// int maxConvexHulls,
// bool projectHullVertices,
// unsigned int fillMode);
[DllImport(dllName, EntryPoint = "SetParameters")]
private static extern void SetParameters(
double concavity,
double alpha,
double beta,
double minVolumePerConvexHull,
int resolution,
int maxNumVerticesPerCH,
int planeDownsampling,
int convexhullDownsampling,
int maxConvexHulls,
bool projectHullVertices,
uint fillMode
);
// extern "C" VHACD_API void SetPoint(int index, float value);
[DllImport(dllName, EntryPoint = "SetPoint")]
private static extern void SetPoint(int index, float value);
// extern "C" VHACD_API void SetPoints(float pointsArr[], int size);
[DllImport(dllName, EntryPoint = "SetPoints")]
private static extern void SetPoints(float[] pointsArr, int size);
// extern "C" VHACD_API void SetPointSize(int size);
[DllImport(dllName, EntryPoint = "SetPointSize")]
private static extern void SetPointSize(int size);
// extern "C" VHACD_API void SetResolution(int value);
[DllImport(dllName, EntryPoint = "SetResolution")]
private static extern void SetResolution(int value);
// extern "C" VHACD_API void SetTriangle(int index, int value);
[DllImport(dllName, EntryPoint = "SetTriangle")]
private static extern void SetTriangle(int index, int value);
// extern "C" VHACD_API void SetTriangles(int trianglesArr[], int size);
[DllImport(dllName, EntryPoint = "SetTriangles")]
private static extern void SetTriangles(int[] trianglesArr, int size);
// extern "C" VHACD_API void SetTriangleSize(int size);
[DllImport(dllName, EntryPoint = "SetTriangleSize")]
private static extern void SetTriangleSize(int size);
/// <summary>
/// Is an instance of vhacd initialized?
/// </summary>
/// this is a fix for the new version of vhacd which is not used yet for various reasons.
private bool isInitialized = false;
/// <summary>
/// Initializes a VHACD instance
/// </summary>
/// <param name="async">Use async process?</param>
/// <returns></returns>
public bool Init(bool async = true)
{
Clean();
if (async)
{
// fixes an issue in new vhacd without changing the api.
isInitialized = true;
// If you're getting an error about DLL not found exception, you'll likely be pointed to this line by the error
// near the top of this file you'll find " const string dllName = "ECE_VHACD"; " ,
// this is where the dll name is placed (these same comments are there as well)
// To fix this on Mac: sometimes it can not recognize the .bundle, try either "ECE_VHACD.bundle" or "ECE_VHACD" as the dllName string
// Occasionally this can also happen when the dll/bundles are updated, and imported into unity after they were already used
// to correctly update when vhacd is updated, be sure to close unity, and immediately update the asset on opening the project
// currently only supports windows, and OSX (arm / silicon and intel) versions of unity.
return Create_ASYNC();
}
else
{
isInitialized = true;
return Create();
}
}
/// <summary>
/// Destroys the VHACD instance, if it is already initialized.
/// </summary>
/// <returns>true if destroyed, false if not destroyed(because it wasn't initialized yet)</returns>
public bool Clean()
{
// fixes issue where vhacd isn't initialized and tries to destroy itself (in new vhacd)
if (isInitialized)
{
// Debug.Log("Clean" + dllName);
isInitialized = false;
Destroy();
return true;
}
else
{
return false;
}
}
/// <summary>
/// Gets all calculated convex hulls and turn them into meshes.
/// </summary>
/// <returns>convex hull meshes</returns>
public Mesh[] CreateConvexHullMeshes()
{
int numHulls = GetNumberOfConvexHulls();
Mesh[] meshes = new Mesh[numHulls];
for (int i = 0; i < numHulls; i++)
{
// get the current convex hull
SetConvexHull(i);
// get the ch data
int pointCount = GetConvexHullNumPoints();
int triangleCount = GetConvexHullNumTriangles();
// create new vertex and triangles array.
Vector3[] vertices = new Vector3[pointCount];
int[] triangles = new int[triangleCount * 3];
for (int j = 0; j < triangleCount * 3; j++)
{
triangles[j] = GetConvexHullTriangle(j);
}
// assing each point to a vertex in the array
Vector3 point = Vector3.zero;
for (int j = 0; j < pointCount * 3; j += 3)
{
// note that in VHACD, each vertex is not a vector.
// instead each vertex is simply the 3 values in order.
point.x = (float)GetConvexHullPoint(j);
point.y = (float)GetConvexHullPoint(j + 1);
point.z = (float)GetConvexHullPoint(j + 2);
vertices[j / 3] = point;
}
// create and save the mesh.
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
// be sure to add it to our meshes array
meshes[i] = mesh;
}
//return the array of meshes so they can be added as convex mesh colliders.
return meshes;
}
/// <summary>
/// Gets the number of convex hulls calcalated
/// </summary>
/// <returns># convex hulls computed</returns>
public int GetConvexHullCount()
{
return GetNumberOfConvexHulls();
}
/// <summary>
/// Checks if the async computation of convex hulls is complete
/// </summary>
/// <returns>true if finished</returns>
public bool IsComputeFinished()
{
return IsReady();
}
/// <summary>
/// Checks to see if each convex hull is under 256 vertices and 256 triangles.
/// </summary>
/// <returns>True if under limits</returns>
public bool IsValid()
{
// calculate max triangle count and max vertex count
int maxTriangleCount = 0;
int maxVertexCount = 0;
int numHulls = GetConvexHullCount();
for (int i = 0; i < numHulls; i++)
{
SetConvexHull(i);
int currentTriCount = GetConvexHullNumTriangles();
int currentVertCount = GetConvexHullNumPoints();
if (currentTriCount > maxTriangleCount)
{
maxTriangleCount = currentTriCount;
}
if (currentVertCount > maxVertexCount)
{
maxVertexCount = currentVertCount;
}
}
// if both are under 256, unity will generate no errors.
// (these errors are hidden in some older versions of unity.)
if (maxVertexCount < 256 && maxTriangleCount < 256)
{
return true;
}
return false;
}
/// <summary>
/// Sets the vertices and triangles array for VHACD from mesh
/// </summary>
/// <param name="meshFilter">mesh filter of the source mesh</param>
/// <param name="attachTo">transform the mesh collider will be attached to</param>
/// <returns>true if preparation succeeds, false otherwise</returns>
public bool PrepareMeshData(MeshFilter meshFilter, Transform attachTo, Mesh mesh)
{
if (mesh == null) return false;
// convert from meshes' object to world space, to attach to's local space, then calculate, then add.
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
List<Vector3> localAttachVertices = new List<Vector3>();
if (meshFilter != null && meshFilter.transform != attachTo && meshFilter.sharedMesh != null && meshFilter.sharedMesh == mesh)
{
foreach (Vector3 v in vertices)
{
// transform from mesh filters local space, to world space, to the attach to's local space.
localAttachVertices.Add(attachTo.transform.InverseTransformPoint(meshFilter.transform.TransformPoint(v)));
}
}
else
{
localAttachVertices = vertices.ToList();
}
// could have used an intptr but this way is fast enough
// and we can let VHACD take care of itself.
SetPointSize(localAttachVertices.Count * 3);
SetTriangleSize(triangles.Length);
for (int i = 0; i < localAttachVertices.Count; i++)
{
SetPoint(i * 3, localAttachVertices[i].x);
SetPoint(i * 3 + 1, localAttachVertices[i].y);
SetPoint(i * 3 + 2, localAttachVertices[i].z);
}
for (int i = 0; i < triangles.Length; i++)
{
SetTriangle(i, triangles[i]);
}
return true;
}
/// <summary>
/// prepares an array of meshfilters for a single convex hull decomposition.
/// </summary>
/// <param name="meshFilters">array of meshfilters</param>
public bool PrepareMeshData(List<MeshFilter> meshFilters, Transform attachTo)
{
// convert vertices from the mesh filters local space, to world space, to the attach to's local space.
int vertexCount = 0;
int triangleCount = 0;
List<Vector3> localAttachVertices = new List<Vector3>();
List<int> triangles = new List<int>();
foreach (MeshFilter mf in meshFilters)
{
// skip any mesh filters that were deleted, or had their shared mesh changed to null
if (mf == null || mf.sharedMesh == null) continue;
// convert to local vertices of the attach to object.
Vector3[] verts = mf.sharedMesh.vertices;
foreach (Vector3 v in verts)
{
localAttachVertices.Add(attachTo.transform.InverseTransformPoint(mf.transform.TransformPoint(v)));
}
// get current shared mesh triangles
int[] tris = mf.sharedMesh.triangles;
for (int i = 0; i < tris.Length; i++)
{
// add the vertex index, plus the offset of the total running vertex count.
triangles.Add(tris[i] + vertexCount);
}
vertexCount += mf.sharedMesh.vertices.Length;
triangleCount += mf.sharedMesh.triangles.Length;
}
// could have used intptr etc. to pass all the data at once
// but this way is still fast enough, and we can just let VHACD handle it's own stuff.
SetPointSize(vertexCount * 3);
SetTriangleSize(triangleCount);
for (int i = 0; i < localAttachVertices.Count; i++)
{
SetPoint(i * 3, localAttachVertices[i].x);
SetPoint(i * 3 + 1, localAttachVertices[i].y);
SetPoint(i * 3 + 2, localAttachVertices[i].z);
}
for (int i = 0; i < triangles.Count; i++)
{
SetTriangle(i, triangles[i]);
}
return true;
}
/// <summary>
/// Recalculates the current convex hulls based on max triangles and vertices with an aim to get max triangles below 256
/// </summary>
/// <returns></returns>
public bool RecomputeVHACD()
{
int maxTriangleCount = 0;
int maxVertexCount = 0;
int numHulls = GetConvexHullCount();
// calculate max triangles and vertices from convex hulls.
for (int i = 0; i < numHulls; i++)
{
SetConvexHull(i);
int currentTriCount = GetConvexHullNumTriangles();
int currentVertCount = GetConvexHullNumPoints();
if (currentTriCount > maxTriangleCount)
{
maxTriangleCount = currentTriCount;
}
if (currentVertCount > maxVertexCount)
{
maxVertexCount = currentVertCount;
}
}
// reduce the max number of vertices.
float trisPerVertMax = (float)maxTriangleCount / maxVertexCount;
int maxVerticesPerConvexHull = (int)(255 / trisPerVertMax);
// set the new max number of vertices
SetMaxVerticesPerHull(maxVerticesPerConvexHull);
// compute again.
Compute();
return true;
}
/// <summary>
/// Calls compute method on the current VHACD instance
/// </summary>
/// <returns></returns>
public bool RunVHACD()
{
Compute();
return true;
}
/// <summary>
/// Sets parameters on the current VHACD instance
/// </summary>
/// <param name="parameters">parameters to set</param>
public bool SetParameters(VHACDParameters parameters)
{
SetParameters(
parameters.concavity,
parameters.alpha,
parameters.beta,
parameters.minVolumePerCH,
parameters.resolution,
parameters.maxNumVerticesPerConvexHull,
parameters.planeDownsampling,
parameters.convexhullDownSampling,
parameters.maxConvexHulls,
parameters.projectHullVertices,
(uint)parameters.fillMode
);
return true;
}
}
/// <summary>
/// Fill mode for VHACD
/// </summary>
public enum VHACD_FILL_MODE
{
FLOOD_FILL,
SURFACE_ONLY,
RAYCAST_FILL,
}
}
#endif

View File

@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 492957b54e69e0944a816663cdf414ff
timeCreated: 1591919081
licenseType: Store
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderVHACD.cs
uploadId: 885904

View File

@@ -0,0 +1,60 @@
#if (UNITY_EDITOR)
using System;
using UnityEngine;
namespace ECE
{
/// <summary>
/// A vertex represented by the transform it's attached to and it's local position.
/// </summary>
[System.Serializable]
public class EasyColliderVertex : IEquatable<EasyColliderVertex>
{
/// <summary>
/// Local position of the vertex on the transform.
/// </summary>
public Vector3 LocalPosition;
public Vector3 Normal = Vector3.zero;
/// <summary>
/// Transform the vertex comes from.
/// </summary>
public Transform T;
/// <summary>
/// Create a new Easy Collider Vertex
/// </summary>
/// <param name="transform">Transform the vertex is on</param>
/// <param name="localPosition">Local position of the vertex</param>
public EasyColliderVertex(Transform transform, Vector3 localPosition)
{
this.T = transform;
this.LocalPosition = localPosition;
}
public EasyColliderVertex(EasyColliderVertex ecv)
{
this.T = ecv.T;
this.LocalPosition = ecv.LocalPosition;
}
// since we've used hashsets prior to adding normals, and we calculate smoothed normals
// when things get selected and store the those as the selected vertices,
// various things would have to change to include the normals in the equals and get hash code.
// so for now, we're still just position based.
public bool Equals(EasyColliderVertex other)
{
return (other.LocalPosition == this.LocalPosition && other.T == this.T);// && other.Normal == this.Normal);
}
public override int GetHashCode()
{
int hashCode = 13 * 31 + LocalPosition.GetHashCode();
// hashCode += 17 * this.Normal.GetHashCode();
return hashCode * 31 + T.GetHashCode();
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 41abfd88235d8c444be04e8d0ddd38b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderVertex.cs
uploadId: 885904

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8aba5803d0eee804792b9405ce7990cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/EasyColliderWindow.cs
uploadId: 885904

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ac9df4839a2693149a285fc07e42c21f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ECE
{
public interface IEasyColliderPostProcessor
{
/// <summary>
/// post processes a box collider, properties orientation indiciates if it's a rotated collider.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
void PostProcessCollider(BoxCollider boxCollider, EasyColliderProperties properties);
/// <summary>
/// post processes a capsule collider, properties orientation indiciates if it's a rotated collider.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
void PostProcessCollider(CapsuleCollider capsuleCollider, EasyColliderProperties properties);
/// <summary>
/// post processes a mesh collider. cylinder colliders are mesh colliders as well.
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
void PostProcessCollider(MeshCollider meshCollider, EasyColliderProperties properties);
/// <summary>
/// post processes a sphere collider
/// </summary>
/// <param name="box"></param>
/// <param name="properties"></param>
void PostProcessCollider(SphereCollider sphereCollider, EasyColliderProperties properties);
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b6cf4bc0f3d036f46a48313c8a796bed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Interfaces/IEasyColliderPostProcessor.cs
uploadId: 885904

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a8a38e163b87eb748aebb1b989f8503b
folderAsset: yes
timeCreated: 1591980098
licenseType: Store
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,96 @@
fileFormatVersion: 2
guid: 68a88bb9c40937341bf6536bbe774f0d
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: Windows
- first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Plugins/ECE_VHACD.dll
uploadId: 885904

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d834607d4f50eef45918d26ac1d3e340
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,70 @@
fileFormatVersion: 2
guid: 253ee65f4209bc24ab1c970ff13bf752
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: Linux
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Plugins/Linux/ECE_VHACD.so
uploadId: 885904

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88f8f3ea0a40624418d9c27ab616b7ea
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,74 @@
fileFormatVersion: 2
guid: a9491b77ec748994585680ed2c3066b5
folderAsset: yes
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
: OSXIntel
second:
enabled: 1
settings: {}
- first:
: OSXIntel64
second:
enabled: 1
settings: {}
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: OSX
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6a8585019d15a9a46bcd1c5a4672a0dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>22G313</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>ECE_VHACD</string>
<key>CFBundleIdentifier</key>
<string>com.pmurph0305.vhacd</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ECE_VHACD</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string></string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>14.0</string>
<key>DTSDKBuild</key>
<string>23A334</string>
<key>DTSDKName</key>
<string>macosx14.0</string>
<key>DTXcode</key>
<string>1501</string>
<key>DTXcodeBuild</key>
<string>15A507</string>
<key>LSMinimumSystemVersion</key>
<string>10.14.6</string>
<key>NSHumanReadableCopyright</key>
<string>Patrick Murphy</string>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a0f15c4d5e14a654984875f8b9c256e2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1b2f199639e70343a1ee88ed11361b5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,159 @@
#pragma once
#ifndef HACD_CIRCULAR_LIST_INL
# define HACD_CIRCULAR_LIST_INL
namespace VHACD
{
template <typename T>
inline bool CircularList<T>::Delete(CircularListElement<T>* element)
{
if (!element)
{
return false;
}
if (m_size > 1)
{
CircularListElement<T>* next = element->GetNext();
CircularListElement<T>* prev = element->GetPrev();
delete element;
m_size--;
if (element == m_head)
{
m_head = next;
}
next->GetPrev() = prev;
prev->GetNext() = next;
return true;
}
else if (m_size == 1)
{
delete m_head;
m_size--;
m_head = 0;
return true;
}
else
{
return false;
}
}
template <typename T>
inline bool CircularList<T>::Delete()
{
if (m_size > 1)
{
CircularListElement<T>* next = m_head->GetNext();
CircularListElement<T>* prev = m_head->GetPrev();
delete m_head;
m_size--;
m_head = next;
next->GetPrev() = prev;
prev->GetNext() = next;
return true;
}
else if (m_size == 1)
{
delete m_head;
m_size--;
m_head = 0;
return true;
}
else
{
return false;
}
}
template <typename T>
inline CircularListElement<T>* CircularList<T>::Add(const T* data)
{
if (m_size == 0)
{
if (data)
{
m_head = new CircularListElement<T>(*data);
}
else
{
m_head = new CircularListElement<T>();
}
m_head->GetNext() = m_head->GetPrev() = m_head;
}
else
{
CircularListElement<T>* next = m_head->GetNext();
CircularListElement<T>* element = m_head;
if (data)
{
m_head = new CircularListElement<T>(*data);
}
else
{
m_head = new CircularListElement<T>();
}
m_head->GetNext() = next;
m_head->GetPrev() = element;
element->GetNext() = m_head;
next->GetPrev() = m_head;
}
m_size++;
return m_head;
}
template <typename T>
inline CircularListElement<T>* CircularList<T>::Add(const T& data)
{
const T* pData = &data;
return Add(pData);
}
template <typename T>
inline bool CircularList<T>::Next()
{
if (m_size == 0)
{
return false;
}
m_head = m_head->GetNext();
return true;
}
template <typename T>
inline bool CircularList<T>::Prev()
{
if (m_size == 0)
{
return false;
}
m_head = m_head->GetPrev();
return true;
}
template <typename T>
inline CircularList<T>::CircularList(const CircularList& rhs)
{
if (rhs.m_size > 0)
{
CircularListElement<T>* current = rhs.m_head;
do
{
current = current->GetNext();
Add(current->GetData());
} while (current != rhs.m_head);
}
}
template <typename T>
inline const CircularList<T>& CircularList<T>::operator=(const CircularList& rhs)
{
if (&rhs != this)
{
Clear();
if (rhs.m_size > 0)
{
CircularListElement<T>* current = rhs.m_head;
do
{
current = current->GetNext();
Add(current->GetData());
} while (current != rhs.m_head);
}
}
return (*this);
}
} // namespace VHACD
#endif

View File

@@ -0,0 +1,375 @@
#pragma once
#ifndef VHACD_VECTOR_INL
# define VHACD_VECTOR_INL
namespace VHACD
{
template <typename T>
inline Vec3<T> operator*(T lhs, const Vec3<T>& rhs)
{
return Vec3<T>(lhs * rhs.X(), lhs * rhs.Y(), lhs * rhs.Z());
}
template <typename T>
inline T& Vec3<T>::X()
{
return m_data[0];
}
template <typename T>
inline T& Vec3<T>::Y()
{
return m_data[1];
}
template <typename T>
inline T& Vec3<T>::Z()
{
return m_data[2];
}
template <typename T>
inline const T& Vec3<T>::X() const
{
return m_data[0];
}
template <typename T>
inline const T& Vec3<T>::Y() const
{
return m_data[1];
}
template <typename T>
inline const T& Vec3<T>::Z() const
{
return m_data[2];
}
template <typename T>
inline T Vec3<T>::Normalize()
{
T n = sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2]);
if (n != 0.0)
(*this) /= n;
return n;
}
template <typename T>
inline T Vec3<T>::GetNorm() const
{
return sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2]);
}
template <typename T>
inline void Vec3<T>::operator=(const Vec3& rhs)
{
this->m_data[0] = rhs.m_data[0];
this->m_data[1] = rhs.m_data[1];
this->m_data[2] = rhs.m_data[2];
}
template <typename T>
inline void Vec3<T>::operator+=(const Vec3& rhs)
{
this->m_data[0] += rhs.m_data[0];
this->m_data[1] += rhs.m_data[1];
this->m_data[2] += rhs.m_data[2];
}
template <typename T>
inline void Vec3<T>::operator-=(const Vec3& rhs)
{
this->m_data[0] -= rhs.m_data[0];
this->m_data[1] -= rhs.m_data[1];
this->m_data[2] -= rhs.m_data[2];
}
template <typename T>
inline void Vec3<T>::operator-=(T a)
{
this->m_data[0] -= a;
this->m_data[1] -= a;
this->m_data[2] -= a;
}
template <typename T>
inline void Vec3<T>::operator+=(T a)
{
this->m_data[0] += a;
this->m_data[1] += a;
this->m_data[2] += a;
}
template <typename T>
inline void Vec3<T>::operator/=(T a)
{
this->m_data[0] /= a;
this->m_data[1] /= a;
this->m_data[2] /= a;
}
template <typename T>
inline void Vec3<T>::operator*=(T a)
{
this->m_data[0] *= a;
this->m_data[1] *= a;
this->m_data[2] *= a;
}
template <typename T>
inline Vec3<T> Vec3<T>::operator^(const Vec3<T>& rhs) const
{
return Vec3<T>(m_data[1] * rhs.m_data[2] - m_data[2] * rhs.m_data[1],
m_data[2] * rhs.m_data[0] - m_data[0] * rhs.m_data[2],
m_data[0] * rhs.m_data[1] - m_data[1] * rhs.m_data[0]);
}
template <typename T>
inline T Vec3<T>::operator*(const Vec3<T>& rhs) const
{
return (m_data[0] * rhs.m_data[0] + m_data[1] * rhs.m_data[1] + m_data[2] * rhs.m_data[2]);
}
template <typename T>
inline Vec3<T> Vec3<T>::operator+(const Vec3<T>& rhs) const
{
return Vec3<T>(m_data[0] + rhs.m_data[0], m_data[1] + rhs.m_data[1], m_data[2] + rhs.m_data[2]);
}
template <typename T>
inline Vec3<T> Vec3<T>::operator-(const Vec3<T>& rhs) const
{
return Vec3<T>(m_data[0] - rhs.m_data[0], m_data[1] - rhs.m_data[1], m_data[2] - rhs.m_data[2]);
}
template <typename T>
inline Vec3<T> Vec3<T>::operator-() const
{
return Vec3<T>(-m_data[0], -m_data[1], -m_data[2]);
}
template <typename T>
inline Vec3<T> Vec3<T>::operator*(T rhs) const
{
return Vec3<T>(rhs * this->m_data[0], rhs * this->m_data[1], rhs * this->m_data[2]);
}
template <typename T>
inline Vec3<T> Vec3<T>::operator/(T rhs) const
{
return Vec3<T>(m_data[0] / rhs, m_data[1] / rhs, m_data[2] / rhs);
}
template <typename T>
inline Vec3<T>::Vec3(T a)
{
m_data[0] = m_data[1] = m_data[2] = a;
}
template <typename T>
inline Vec3<T>::Vec3(T x, T y, T z)
{
m_data[0] = x;
m_data[1] = y;
m_data[2] = z;
}
template <typename T>
inline Vec3<T>::Vec3(const Vec3& rhs)
{
m_data[0] = rhs.m_data[0];
m_data[1] = rhs.m_data[1];
m_data[2] = rhs.m_data[2];
}
template <typename T>
inline Vec3<T>::~Vec3(void){};
template <typename T>
inline Vec3<T>::Vec3()
{
}
template <typename T>
inline const bool Colinear(const Vec3<T>& a, const Vec3<T>& b, const Vec3<T>& c)
{
return ((c.Z() - a.Z()) * (b.Y() - a.Y()) - (b.Z() - a.Z()) * (c.Y() - a.Y()) == 0.0 /*EPS*/) &&
((b.Z() - a.Z()) * (c.X() - a.X()) - (b.X() - a.X()) * (c.Z() - a.Z()) == 0.0 /*EPS*/) &&
((b.X() - a.X()) * (c.Y() - a.Y()) - (b.Y() - a.Y()) * (c.X() - a.X()) == 0.0 /*EPS*/);
}
template <typename T>
inline const T ComputeVolume4(const Vec3<T>& a, const Vec3<T>& b, const Vec3<T>& c, const Vec3<T>& d)
{
return (a - d) * ((b - d) ^ (c - d));
}
template <typename T>
inline bool Vec3<T>::operator<(const Vec3& rhs) const
{
if (X() == rhs[0])
{
if (Y() == rhs[1])
{
return (Z() < rhs[2]);
}
return (Y() < rhs[1]);
}
return (X() < rhs[0]);
}
template <typename T>
inline bool Vec3<T>::operator>(const Vec3& rhs) const
{
if (X() == rhs[0])
{
if (Y() == rhs[1])
{
return (Z() > rhs[2]);
}
return (Y() > rhs[1]);
}
return (X() > rhs[0]);
}
template <typename T>
inline Vec2<T> operator*(T lhs, const Vec2<T>& rhs)
{
return Vec2<T>(lhs * rhs.X(), lhs * rhs.Y());
}
template <typename T>
inline T& Vec2<T>::X()
{
return m_data[0];
}
template <typename T>
inline T& Vec2<T>::Y()
{
return m_data[1];
}
template <typename T>
inline const T& Vec2<T>::X() const
{
return m_data[0];
}
template <typename T>
inline const T& Vec2<T>::Y() const
{
return m_data[1];
}
template <typename T>
inline void Vec2<T>::Normalize()
{
T n = sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1]);
if (n != 0.0)
(*this) /= n;
}
template <typename T>
inline T Vec2<T>::GetNorm() const
{
return sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1]);
}
template <typename T>
inline void Vec2<T>::operator=(const Vec2& rhs)
{
this->m_data[0] = rhs.m_data[0];
this->m_data[1] = rhs.m_data[1];
}
template <typename T>
inline void Vec2<T>::operator+=(const Vec2& rhs)
{
this->m_data[0] += rhs.m_data[0];
this->m_data[1] += rhs.m_data[1];
}
template <typename T>
inline void Vec2<T>::operator-=(const Vec2& rhs)
{
this->m_data[0] -= rhs.m_data[0];
this->m_data[1] -= rhs.m_data[1];
}
template <typename T>
inline void Vec2<T>::operator-=(T a)
{
this->m_data[0] -= a;
this->m_data[1] -= a;
}
template <typename T>
inline void Vec2<T>::operator+=(T a)
{
this->m_data[0] += a;
this->m_data[1] += a;
}
template <typename T>
inline void Vec2<T>::operator/=(T a)
{
this->m_data[0] /= a;
this->m_data[1] /= a;
}
template <typename T>
inline void Vec2<T>::operator*=(T a)
{
this->m_data[0] *= a;
this->m_data[1] *= a;
}
template <typename T>
inline T Vec2<T>::operator^(const Vec2<T>& rhs) const
{
return m_data[0] * rhs.m_data[1] - m_data[1] * rhs.m_data[0];
}
template <typename T>
inline T Vec2<T>::operator*(const Vec2<T>& rhs) const
{
return (m_data[0] * rhs.m_data[0] + m_data[1] * rhs.m_data[1]);
}
template <typename T>
inline Vec2<T> Vec2<T>::operator+(const Vec2<T>& rhs) const
{
return Vec2<T>(m_data[0] + rhs.m_data[0], m_data[1] + rhs.m_data[1]);
}
template <typename T>
inline Vec2<T> Vec2<T>::operator-(const Vec2<T>& rhs) const
{
return Vec2<T>(m_data[0] - rhs.m_data[0], m_data[1] - rhs.m_data[1]);
}
template <typename T>
inline Vec2<T> Vec2<T>::operator-() const
{
return Vec2<T>(-m_data[0], -m_data[1]);
}
template <typename T>
inline Vec2<T> Vec2<T>::operator*(T rhs) const
{
return Vec2<T>(rhs * this->m_data[0], rhs * this->m_data[1]);
}
template <typename T>
inline Vec2<T> Vec2<T>::operator/(T rhs) const
{
return Vec2<T>(m_data[0] / rhs, m_data[1] / rhs);
}
template <typename T>
inline Vec2<T>::Vec2(T a)
{
m_data[0] = m_data[1] = a;
}
template <typename T>
inline Vec2<T>::Vec2(T x, T y)
{
m_data[0] = x;
m_data[1] = y;
}
template <typename T>
inline Vec2<T>::Vec2(const Vec2& rhs)
{
m_data[0] = rhs.m_data[0];
m_data[1] = rhs.m_data[1];
}
template <typename T>
inline Vec2<T>::~Vec2(void){};
template <typename T>
inline Vec2<T>::Vec2()
{
}
/*
InsideTriangle decides if a point P is Inside of the triangle
defined by A, B, C.
*/
template <typename T>
inline const bool InsideTriangle(const Vec2<T>& a, const Vec2<T>& b, const Vec2<T>& c, const Vec2<T>& p)
{
T ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
T cCROSSap, bCROSScp, aCROSSbp;
ax = c.X() - b.X();
ay = c.Y() - b.Y();
bx = a.X() - c.X();
by = a.Y() - c.Y();
cx = b.X() - a.X();
cy = b.Y() - a.Y();
apx = p.X() - a.X();
apy = p.Y() - a.Y();
bpx = p.X() - b.X();
bpy = p.Y() - b.Y();
cpx = p.X() - c.X();
cpy = p.Y() - c.Y();
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0));
}
} // namespace VHACD
#endif // VHACD_VECTOR_INL

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 053bcd82e5178b84f92ae9aad65047d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>Resources/FloatMath.inl</key>
<data>
FQ02laH8m+/JTJ+xcwqF4fWRY4w=
</data>
<key>Resources/vhacdCircularList.inl</key>
<data>
bg5DMaZXXwN+gpc16iPOfKbGOD4=
</data>
<key>Resources/vhacdVector.inl</key>
<data>
LRgKZE2bbNoNlvmVBByTASOiAlI=
</data>
</dict>
<key>files2</key>
<dict>
<key>Resources/FloatMath.inl</key>
<dict>
<key>hash2</key>
<data>
7c/f0AfVvxJKCxxwRLRSiPEGbmEje0PvDdrLJebRkXU=
</data>
</dict>
<key>Resources/vhacdCircularList.inl</key>
<dict>
<key>hash2</key>
<data>
WmykxJuTQfrpkVu/j2zvrquydZ+YDyad4TWxrxQgeIE=
</data>
</dict>
<key>Resources/vhacdVector.inl</key>
<dict>
<key>hash2</key>
<data>
s1ji9RXyq8hW10f7880Nngs65lwHqd3pgFjjovRn1mM=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^Resources/</key>
<true/>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^.*</key>
<true/>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^Resources/.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^Resources/.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Resources/Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2011, Khaled Mamou (kmamou at gmail dot com)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 0a79fff97a7a7294b835aea10e1cb6cf
timeCreated: 1596043168
licenseType: Store
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Plugins/VHACDLicense.txt
uploadId: 885904

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e07e2f113dc48584a95e5460e745c076
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,911 @@
#if (UNITY_EDITOR)
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace ECE
{
// Can't figure out how to do platform dependant compilation with attributes, and I have quite a headache, so...
// there's just two copies of the class here. all so that the warning isn't displayed when going from prefab mode to play mode
/// <summary>
/// Not actually a compute shader. Just uses a regular shader with a structured buffer.
/// "In regular graphics shaders the compute buffer support requires minimum shader model 4.5."
/// </summary>
#if (UNITY_2018_3_OR_NEWER)
[System.Serializable, ExecuteAlways]
public class EasyColliderCompute : MonoBehaviour
{
#region preference values
private EasyColliderPreferences ECEPreferences
{
get { return EasyColliderPreferences.Preferences; }
}
public Color SelectedColor { get { return ECEPreferences.SelectedVertColour; } }
public Color HoveredColor { get { return ECEPreferences.HoverVertColour; } }
public Color OverlapColor { get { return ECEPreferences.OverlapSelectedVertColour; } }
public Color DisplayAllColor { get { return ECEPreferences.DisplayVerticesColour; } }
public float DefaultScale { get { return ECEPreferences.DefaultScale; } }
public float CommonScale { get { return ECEPreferences.CommonScalingMultiplier; } }
public float HoveredScale { get { return ECEPreferences.HoveredScaleMult; } }
public float SelectedScale { get { return ECEPreferences.SelectedScaleMult; } }
public float DisplayAllScale { get { return ECEPreferences.DisplayAllScaleMult; } }
public float OverlapScale { get { return ECEPreferences.OverlapScaleMult; } }
public bool DisplayAllVertices { get { return ECEPreferences.DisplayAllVertices; } }
#endregion
// shader to create materials from
public Shader GeometryShader;
// hovered material for use with hovered buffer, color, and size.
[SerializeField]
Material _HoveredMaterial;
[SerializeField]
Material _SelectedMaterial;
[SerializeField]
Material _OverlapMaterial;
[SerializeField]
Material _DisplayAllMaterial;
// bools to use to check if the buffer is valid (not empty), and if they are valid, to render the vertices/geometry
[SerializeField]
private bool _ValidOverlapBuffer = true;
[SerializeField]
private bool _ValidSelectedBuffer = true;
[SerializeField]
private bool _ValidHoverBuffer = true;
[SerializeField]
private bool _ValidDisplayAllBuffer = true;
/// <summary>
/// Calculated density values.
/// </summary>
public float DensityScale = 0.0f;
// Lists of points to be used in buffers.
[HideInInspector]
private List<Vector3> _SelectedWorldPoints = new List<Vector3>();
[HideInInspector]
private HashSet<Vector3> _SelectedWorldPointsSet = new HashSet<Vector3>();
[HideInInspector]
private List<Vector3> _OverlappedPoints = new List<Vector3>();
[HideInInspector]
private List<Vector3> _HoveredPoints = new List<Vector3>();
[HideInInspector]
private List<Vector3> _DisplayAllPoints = new List<Vector3>();
public int SelectedPointCount { get { try { return _SelectedBuffer != null ? _SelectedWorldPoints.Count : 0; } catch { return 0; } } }
public int HoveredPointCount
{
get { try { return (_HoveredBuffer != null && _OverlapBuffer != null) ? _HoveredPoints.Count + _OverlappedPoints.Count : 0; } catch { return 0; } }
}
public int DisplayPointCount { get { try { return (_DisplayAllBuffer != null) ? _DisplayAllPoints.Count : 0; } catch { return 0; } } }
// Compute buffers
[SerializeField]
ComputeBuffer _SelectedBuffer;
[SerializeField]
ComputeBuffer _HoveredBuffer;
[SerializeField]
ComputeBuffer _OverlapBuffer;
[SerializeField]
ComputeBuffer _DisplayAllBuffer;
/// <summary>
/// Called when the current editor scene is saved.
/// </summary>
/// <param name="scene"></param>
void OnSceneSaved(UnityEngine.SceneManagement.Scene scene)
{
SetSelectedBuffer();
SetDisplayAllBuffer();
}
void Start()
{
if (EditorApplication.isPlaying)
{
Destroy(this);
}
}
void OnEnable()
{
// we need to recreate the selected and display buffer when the scene is saved so they are displayed correctly.
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved += OnSceneSaved;
// find the geometry shader
if (GeometryShader == null)
{
string[] ecp = AssetDatabase.FindAssets("EasyColliderShader t:Shader");
if (ecp.Length > 0)
{
string assetPath = AssetDatabase.GUIDToAssetPath(ecp[0]);
GeometryShader = AssetDatabase.LoadAssetAtPath(assetPath, typeof(Shader)) as Shader;
}
if (GeometryShader == null)
{
Debug.LogError("EasyColliderEditor was unable to find the shader needed for displaying vertices. If you deleted it because your system does not support it, be sure to set Render Vertex Method to Gizmos in preferences.");
DestroyImmediate(this);
}
}
// create materials
_OverlapMaterial = new Material(GeometryShader);
_HoveredMaterial = new Material(GeometryShader);
_SelectedMaterial = new Material(GeometryShader);
_DisplayAllMaterial = new Material(GeometryShader);
// create buffers
if (_DisplayAllBuffer == null)
{
_ValidDisplayAllBuffer = false;
_DisplayAllBuffer = new ComputeBuffer(1, 12);
}
if (_HoveredBuffer == null)
{
_ValidHoverBuffer = false;
_HoveredBuffer = new ComputeBuffer(1, 12);
}
if (_SelectedBuffer == null)
{
_ValidSelectedBuffer = false;
_SelectedBuffer = new ComputeBuffer(1, 12);
}
if (_OverlapBuffer == null)
{
_ValidOverlapBuffer = false;
_OverlapBuffer = new ComputeBuffer(1, 12);
}
}
float GetScale()
{
float scale = DefaultScale * CommonScale;
if (scale <= 0.0f)
{
scale = ECEPreferences.DefaultScale;
}
return scale;
}
void OnRenderObject()
{
float size = GetScale();
// only need to re-update the selected buffer due to undo/redo operations.
if (!_ValidSelectedBuffer && _SelectedWorldPoints.Count > 0)
{
UpdateSelectedBuffer(_SelectedWorldPoints);
}
#if (UNITY_2018_1_OR_NEWER)
if (DisplayAllVertices && _DisplayAllBuffer != null && _ValidDisplayAllBuffer && _DisplayAllBuffer.IsValid())
#else
if (DisplayAllVertices && _DisplayAllBuffer != null && _ValidDisplayAllBuffer)
#endif
{
_DisplayAllMaterial.SetColor("_Color", DisplayAllColor);
_DisplayAllMaterial.SetFloat("_Size", size * DisplayAllScale);
_DisplayAllMaterial.SetBuffer("worldPositions", _DisplayAllBuffer);
_DisplayAllMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _DisplayAllBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _DisplayAllBuffer.count);
#endif
//TODO: customizable / enablable wireframe drawing for the points.
// GL.wireframe = true;
// _DisplayAllMaterial.SetColor("_Color", Color.black);
// _DisplayAllMaterial.SetPass(0);
// Graphics.DrawProcedural(MeshTopology.Points, _DisplayAllBuffer.count);
// GL.wireframe = false;
}
#if (UNITY_2018_1_OR_NEWER)
if (_SelectedBuffer == null || !_SelectedBuffer.IsValid() || _SelectedBuffer.count != _SelectedWorldPoints.Count)
{
SetSelectedBuffer();
}
if (_SelectedBuffer != null && _ValidSelectedBuffer && _SelectedBuffer.IsValid())
#else
if (_SelectedBuffer != null && _ValidSelectedBuffer)
#endif
{
_SelectedMaterial.SetColor("_Color", SelectedColor);
_SelectedMaterial.SetFloat("_Size", size * SelectedScale);
_SelectedMaterial.SetBuffer("worldPositions", _SelectedBuffer);
_SelectedMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _SelectedBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _SelectedBuffer.count);
#endif
}
#if (UNITY_2018_1_OR_NEWER)
if (_HoveredBuffer != null && _ValidHoverBuffer && _HoveredBuffer.IsValid())
#else
if (_HoveredBuffer != null && _ValidHoverBuffer)
#endif
{
_HoveredMaterial.SetColor("_Color", HoveredColor);
_HoveredMaterial.SetFloat("_Size", size * HoveredScale);
_HoveredMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _HoveredBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _HoveredBuffer.count);
#endif
}
#if (UNITY_2018_1_OR_NEWER)
if (_OverlapBuffer != null && _ValidOverlapBuffer && _OverlapBuffer.IsValid())
#else
if (_OverlapBuffer != null && _ValidOverlapBuffer)
#endif
{
_OverlapMaterial.SetColor("_Color", OverlapColor);
// scale overlap to be always larger than currently selected so they are always visible.
_OverlapMaterial.SetFloat("_Size", size * OverlapScale);
_OverlapMaterial.SetPass(0);
// draw the topology as points. the squares are drawn in the shader by triangles from the points passed in through the overlap buffer when it is updated.
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _OverlapBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _OverlapBuffer.count);
#endif
}
}
/// <summary>
/// Updates the selected world points buffer.
/// </summary>
/// <param name="worldPoints">New list of world points</param>
public void UpdateSelectedBuffer(List<Vector3> worldPoints)
{
_SelectedWorldPointsSet.Clear();
_SelectedWorldPoints = worldPoints;
_SelectedWorldPointsSet.UnionWith(worldPoints);
worldPoints.ForEach(point => _SelectedWorldPointsSet.Add(point));
SetSelectedBuffer();
}
/// <summary>
/// Clears the current selected buffer and resets it from _SelectedWorldPoints
/// </summary>
private void SetSelectedBuffer()
{
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_SelectedWorldPoints.Count > 0)
{
_SelectedBuffer = new ComputeBuffer(_SelectedWorldPoints.Count, 12);
_SelectedBuffer.SetData(_SelectedWorldPoints.ToArray());
_ValidSelectedBuffer = true;
}
else
{
_SelectedBuffer = new ComputeBuffer(1, 12);
_SelectedBuffer.SetCounterValue(0);
_ValidSelectedBuffer = false;
}
if (_SelectedBuffer != null && _SelectedMaterial != null)
{
_SelectedMaterial.SetBuffer("worldPositions", _SelectedBuffer);
}
else
{
_SelectedBuffer.Release();
}
}
/// <summary>
/// Updates both the overlap and hovered buffer
/// </summary>
/// <param name="worldPoints">All vertices highlighted for possible selection</param>
public void UpdateOverlapHoveredBuffer(HashSet<Vector3> worldPoints)
{
_OverlappedPoints.Clear();
_HoveredPoints.Clear();
foreach (Vector3 p in _SelectedWorldPoints)
{
if (worldPoints.Contains(p))
{
_OverlappedPoints.Add(p);
}
}
foreach (Vector3 p in worldPoints)
{
if (!_SelectedWorldPointsSet.Contains(p))
{
_HoveredPoints.Add(p);
}
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_OverlappedPoints.Count > 0)
{
_OverlapBuffer = new ComputeBuffer(_OverlappedPoints.Count, 12);
// _OverlapBuffer.SetData(_OverlappedPoints);
_OverlapBuffer.SetData(_OverlappedPoints.ToArray());
_ValidOverlapBuffer = true;
}
else
{
_OverlapBuffer = new ComputeBuffer(1, 12);
_OverlapBuffer.SetCounterValue(0);
_ValidOverlapBuffer = false;
}
if (_OverlapMaterial != null)
{
_OverlapMaterial.SetBuffer("worldPositions", _OverlapBuffer);
}
else
{
_OverlapBuffer.Release();
}
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_HoveredPoints.Count > 0)
{
_HoveredBuffer = new ComputeBuffer(_HoveredPoints.Count, 12);
// _HoveredBuffer.SetData(_HoveredPoints);
_HoveredBuffer.SetData(_HoveredPoints.ToArray());
_ValidHoverBuffer = true;
}
else
{
_HoveredBuffer = new ComputeBuffer(1, 12);
_HoveredBuffer.SetCounterValue(0);
_ValidHoverBuffer = false;
}
if (_HoveredMaterial != null)
{
_HoveredMaterial.SetBuffer("worldPositions", _HoveredBuffer);
}
else
{
_HoveredBuffer.Dispose();
}
}
public void SetDisplayAllBuffer(HashSet<Vector3> worldPoints)
{
_DisplayAllPoints.Clear();
_DisplayAllPoints = worldPoints.ToList();
SetDisplayAllBuffer();
}
/// <summary>
/// Clears the current display all buffer and updates it using _DisplayAllPoints
/// </summary>
private void SetDisplayAllBuffer()
{
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
if (_DisplayAllPoints.Count > 0)
{
_DisplayAllBuffer = new ComputeBuffer(_DisplayAllPoints.Count, 12);
_DisplayAllBuffer.SetData(_DisplayAllPoints.ToArray());
_ValidDisplayAllBuffer = true;
}
else
{
_DisplayAllBuffer = new ComputeBuffer(1, 12);
_DisplayAllBuffer.SetCounterValue(0);
_ValidDisplayAllBuffer = false;
}
if (_DisplayAllBuffer != null && _DisplayAllMaterial != null)
{
_DisplayAllMaterial.SetBuffer("worldPositions", _DisplayAllBuffer);
}
else
{
_DisplayAllBuffer.Release();
}
}
void OnDestroy()
{
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
}
void OnDisable()
{
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
// Unregister from the scene save delegate
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved -= OnSceneSaved;
}
}
#else
[System.Serializable, ExecuteInEditMode]
public class EasyColliderCompute : MonoBehaviour
{
#region preference values
private EasyColliderPreferences ECEPreferences
{
get { return EasyColliderPreferences.Preferences; }
}
public Color SelectedColor { get { return ECEPreferences.SelectedVertColour; } }
public Color HoveredColor { get { return ECEPreferences.HoverVertColour; } }
public Color OverlapColor { get { return ECEPreferences.OverlapSelectedVertColour; } }
public Color DisplayAllColor { get { return ECEPreferences.DisplayVerticesColour; } }
public float DefaultScale { get { return ECEPreferences.DefaultScale; } }
public float CommonScale { get { return ECEPreferences.CommonScalingMultiplier; } }
public float HoveredScale { get { return ECEPreferences.HoveredScaleMult; } }
public float SelectedScale { get { return ECEPreferences.SelectedScaleMult; } }
public float DisplayAllScale { get { return ECEPreferences.DisplayAllScaleMult; } }
public float OverlapScale { get { return ECEPreferences.OverlapScaleMult; } }
public bool DisplayAllVertices { get { return ECEPreferences.DisplayAllVertices; } }
#endregion
// shader to create materials from
public Shader GeometryShader;
// hovered material for use with hovered buffer, color, and size.
[SerializeField]
Material _HoveredMaterial;
[SerializeField]
Material _SelectedMaterial;
[SerializeField]
Material _OverlapMaterial;
[SerializeField]
Material _DisplayAllMaterial;
// bools to use to check if the buffer is valid (not empty), and if they are valid, to render the vertices/geometry
[SerializeField]
private bool _ValidOverlapBuffer = true;
[SerializeField]
private bool _ValidSelectedBuffer = true;
[SerializeField]
private bool _ValidHoverBuffer = true;
[SerializeField]
private bool _ValidDisplayAllBuffer = true;
/// <summary>
/// Calculated density values.
/// </summary>
public float DensityScale = 0.0f;
// Lists of points to be used in buffers.
[HideInInspector]
private List<Vector3> _SelectedWorldPoints = new List<Vector3>();
[HideInInspector]
private HashSet<Vector3> _SelectedWorldPointsSet = new HashSet<Vector3>();
[HideInInspector]
private List<Vector3> _OverlappedPoints = new List<Vector3>();
[HideInInspector]
private List<Vector3> _HoveredPoints = new List<Vector3>();
[HideInInspector]
private List<Vector3> _DisplayAllPoints = new List<Vector3>();
public int SelectedPointCount { get { try { return _SelectedBuffer != null ? _SelectedWorldPoints.Count : 0; } catch { return 0; } } }
public int HoveredPointCount
{
get { try { return (_HoveredBuffer != null && _OverlapBuffer != null) ? (_HoveredPoints.Count + _OverlappedPoints.Count) : 0; } catch { return 0; } }
}
public int DisplayPointCount { get { try { return (_DisplayAllBuffer != null) ? _DisplayAllPoints.Count : 0; } catch { return 0; } } }
// Compute buffers
[SerializeField] ComputeBuffer _SelectedBuffer;
[SerializeField] ComputeBuffer _HoveredBuffer;
[SerializeField] ComputeBuffer _OverlapBuffer;
[SerializeField] ComputeBuffer _DisplayAllBuffer;
/// <summary>
/// Called when the current editor scene is saved.
/// </summary>
/// <param name="scene"></param>
void OnSceneSaved(UnityEngine.SceneManagement.Scene scene)
{
SetSelectedBuffer();
SetDisplayAllBuffer();
}
void Start()
{
if (EditorApplication.isPlaying)
{
Destroy(this);
}
}
void OnEnable()
{
// we need to recreate the selected and display buffer when the scene is saved so they are displayed correctly.
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved += OnSceneSaved;
// find the geometry shader
if (GeometryShader == null)
{
string[] ecp = AssetDatabase.FindAssets("EasyColliderShader t:Shader");
if (ecp.Length > 0)
{
string assetPath = AssetDatabase.GUIDToAssetPath(ecp[0]);
GeometryShader = AssetDatabase.LoadAssetAtPath(assetPath, typeof(Shader)) as Shader;
}
if (GeometryShader == null)
{
Debug.LogError("EasyColliderEditor was unable to find the shader needed for displaying vertices. If you deleted it because your system does not support it, be sure to set Render Vertex Method to Gizmos in preferences.");
DestroyImmediate(this);
}
}
// create materials
_OverlapMaterial = new Material(GeometryShader);
_HoveredMaterial = new Material(GeometryShader);
_SelectedMaterial = new Material(GeometryShader);
_DisplayAllMaterial = new Material(GeometryShader);
// create buffers
if (_DisplayAllBuffer == null)
{
_ValidDisplayAllBuffer = false;
_DisplayAllBuffer = new ComputeBuffer(1, 12);
}
if (_HoveredBuffer == null)
{
_ValidHoverBuffer = false;
_HoveredBuffer = new ComputeBuffer(1, 12);
}
if (_SelectedBuffer == null)
{
_ValidSelectedBuffer = false;
_SelectedBuffer = new ComputeBuffer(1, 12);
}
if (_OverlapBuffer == null)
{
_ValidOverlapBuffer = false;
_OverlapBuffer = new ComputeBuffer(1, 12);
}
}
float GetScale()
{
float scale = DefaultScale * CommonScale;
if (scale <= 0.0f)
{
scale = ECEPreferences.DefaultScale;
}
return scale;
}
void OnRenderObject()
{
float size = GetScale();
// only need to re-update the selected buffer due to undo/redo operations.
if (!_ValidSelectedBuffer && _SelectedWorldPoints.Count > 0)
{
UpdateSelectedBuffer(_SelectedWorldPoints);
}
#if (UNITY_2018_1_OR_NEWER)
if (DisplayAllVertices && _DisplayAllBuffer != null && _ValidDisplayAllBuffer && _DisplayAllBuffer.IsValid())
#else
if (DisplayAllVertices && _DisplayAllBuffer != null && _ValidDisplayAllBuffer)
#endif
{
_DisplayAllMaterial.SetColor("_Color", DisplayAllColor);
_DisplayAllMaterial.SetFloat("_Size", size * DisplayAllScale);
_DisplayAllMaterial.SetBuffer("worldPositions", _DisplayAllBuffer);
_DisplayAllMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _DisplayAllBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _DisplayAllBuffer.count);
#endif
//TODO: customizable / enablable wireframe drawing for the points.
// GL.wireframe = true;
// _DisplayAllMaterial.SetColor("_Color", Color.black);
// _DisplayAllMaterial.SetPass(0);
// Graphics.DrawProcedural(MeshTopology.Points, _DisplayAllBuffer.count);
// GL.wireframe = false;
}
#if (UNITY_2018_1_OR_NEWER)
if (_SelectedBuffer != null && _ValidSelectedBuffer && _SelectedBuffer.IsValid())
#else
if (_SelectedBuffer != null && _ValidSelectedBuffer)
#endif
{
_SelectedMaterial.SetColor("_Color", SelectedColor);
_SelectedMaterial.SetFloat("_Size", size * SelectedScale);
_SelectedMaterial.SetBuffer("worldPositions", _SelectedBuffer);
_SelectedMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _SelectedBuffer.count);
#else
// because for some reason using vhacd with vertices selected, but not the use selected vertices toggle,
// causes the selected buffer to be lost. Tried to record an undo and full object undo, but that didn't do anything.
// but try/catch/finally works.
try
{
Graphics.DrawProcedural(MeshTopology.Points, _SelectedBuffer.count);
}
catch
{
SetSelectedBuffer();
}
finally
{
Graphics.DrawProcedural(MeshTopology.Points, _SelectedBuffer.count);
}
#endif
}
#if (UNITY_2018_1_OR_NEWER)
if (_HoveredBuffer != null && _ValidHoverBuffer && _HoveredBuffer.IsValid())
#else
if (_HoveredBuffer != null && _ValidHoverBuffer)
#endif
{
_HoveredMaterial.SetColor("_Color", HoveredColor);
_HoveredMaterial.SetFloat("_Size", size * HoveredScale);
_HoveredMaterial.SetPass(0);
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _HoveredBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _HoveredBuffer.count);
#endif
}
#if (UNITY_2018_1_OR_NEWER)
if (_OverlapBuffer != null && _OverlapBuffer.IsValid())
#else
if (_OverlapBuffer != null && _ValidOverlapBuffer)
#endif
{
_OverlapMaterial.SetColor("_Color", OverlapColor);
// scale overlap to be always larger than currently selected so they are always visible.
_OverlapMaterial.SetFloat("_Size", size * OverlapScale);
_OverlapMaterial.SetPass(0);
// draw the topology as points. the squares are drawn in the shader by triangles from the points passed in through the overlap buffer when it is updated.
#if (UNITY_2019_1_OR_NEWER)
Graphics.DrawProceduralNow(MeshTopology.Points, _OverlapBuffer.count);
#else
Graphics.DrawProcedural(MeshTopology.Points, _OverlapBuffer.count);
#endif
}
}
/// <summary>
/// Updates the selected world points buffer.
/// </summary>
/// <param name="worldPoints">New list of world points</param>
public void UpdateSelectedBuffer(List<Vector3> worldPoints)
{
_SelectedWorldPointsSet.Clear();
_SelectedWorldPoints = worldPoints;
_SelectedWorldPointsSet.UnionWith(worldPoints);
worldPoints.ForEach(point => _SelectedWorldPointsSet.Add(point));
SetSelectedBuffer();
}
/// <summary>
/// Clears the current selected buffer and resets it from _SelectedWorldPoints
/// </summary>
private void SetSelectedBuffer()
{
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_SelectedWorldPoints.Count > 0)
{
_SelectedBuffer = new ComputeBuffer(_SelectedWorldPoints.Count, 12);
_SelectedBuffer.SetData(_SelectedWorldPoints.ToArray());
_ValidSelectedBuffer = true;
}
else
{
_SelectedBuffer = new ComputeBuffer(1, 12);
_SelectedBuffer.SetCounterValue(0);
_ValidSelectedBuffer = false;
}
if (_SelectedBuffer != null && _SelectedMaterial != null)
{
_SelectedMaterial.SetBuffer("worldPositions", _SelectedBuffer);
}
else
{
_SelectedBuffer.Release();
}
}
/// <summary>
/// Updates both the overlap and hovered buffer
/// </summary>
/// <param name="worldPoints">All vertices highlighted for possible selection</param>
public void UpdateOverlapHoveredBuffer(HashSet<Vector3> worldPoints)
{
_OverlappedPoints.Clear();
_HoveredPoints.Clear();
foreach (Vector3 p in _SelectedWorldPoints)
{
if (worldPoints.Contains(p))
{
_OverlappedPoints.Add(p);
}
}
foreach (Vector3 p in worldPoints)
{
if (!_SelectedWorldPointsSet.Contains(p))
{
_HoveredPoints.Add(p);
}
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_OverlappedPoints.Count > 0)
{
_OverlapBuffer = new ComputeBuffer(_OverlappedPoints.Count, 12);
// _OverlapBuffer.SetData(_OverlappedPoints);
_OverlapBuffer.SetData(_OverlappedPoints.ToArray());
_ValidOverlapBuffer = true;
}
else
{
_OverlapBuffer = new ComputeBuffer(1, 12);
_OverlapBuffer.SetCounterValue(0);
_ValidOverlapBuffer = false;
}
if (_OverlapMaterial != null)
{
_OverlapMaterial.SetBuffer("worldPositions", _OverlapBuffer);
}
else
{
_OverlapBuffer.Release();
}
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_HoveredPoints.Count > 0)
{
_HoveredBuffer = new ComputeBuffer(_HoveredPoints.Count, 12);
// _HoveredBuffer.SetData(_HoveredPoints);
_HoveredBuffer.SetData(_HoveredPoints.ToArray());
_ValidHoverBuffer = true;
}
else
{
_HoveredBuffer = new ComputeBuffer(1, 12);
_HoveredBuffer.SetCounterValue(0);
_ValidHoverBuffer = false;
}
if (_HoveredMaterial != null)
{
_HoveredMaterial.SetBuffer("worldPositions", _HoveredBuffer);
}
else
{
_HoveredBuffer.Dispose();
}
}
public void SetDisplayAllBuffer(HashSet<Vector3> worldPoints)
{
_DisplayAllPoints.Clear();
_DisplayAllPoints = worldPoints.ToList();
SetDisplayAllBuffer();
}
/// <summary>
/// Clears the current display all buffer and updates it using _DisplayAllPoints
/// </summary>
private void SetDisplayAllBuffer()
{
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
if (_DisplayAllPoints.Count > 0)
{
_DisplayAllBuffer = new ComputeBuffer(_DisplayAllPoints.Count, 12);
_DisplayAllBuffer.SetData(_DisplayAllPoints.ToArray());
_ValidDisplayAllBuffer = true;
}
else
{
_DisplayAllBuffer = new ComputeBuffer(1, 12);
_DisplayAllBuffer.SetCounterValue(0);
_ValidDisplayAllBuffer = false;
}
if (_DisplayAllBuffer != null && _DisplayAllMaterial != null)
{
_DisplayAllMaterial.SetBuffer("worldPositions", _DisplayAllBuffer);
}
else
{
_DisplayAllBuffer.Release();
}
}
void OnDestroy()
{
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
}
void OnDisable()
{
if (_HoveredBuffer != null)
{
_HoveredBuffer.Release();
}
if (_SelectedBuffer != null)
{
_SelectedBuffer.Release();
}
if (_OverlapBuffer != null)
{
_OverlapBuffer.Release();
}
if (_DisplayAllBuffer != null)
{
_DisplayAllBuffer.Release();
}
// Unregister from the scene save delegate
UnityEditor.SceneManagement.EditorSceneManager.sceneSaved -= OnSceneSaved;
}
}
#endif
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 58c194f3826ab0b429ddebb6e0fa5adf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Shader/EasyColliderCompute.cs
uploadId: 885904

View File

@@ -0,0 +1,103 @@
// just a simple transparent color shader.
Shader "Custom/EasyColliderMeshColliderPreview" {
Properties {
_Color("Main Color", Color) = (1,1,1,1)
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Blend SrcAlpha OneMinusSrcAlpha
// For single convex-hulls so we can view them inside of the mesh with the new mesh offsets.
Pass {
ZTest always
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float4 normal: NORMAL;
};
struct v2f {
float4 vertex : SV_POSITION;
};
half4 _Color;
v2f vert (appdata_t v)
{
v2f o;
//world space vert
o.vertex = mul(unity_ObjectToWorld, v.vertex);
// world space normal
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
// offset by a small amount (1mm) (to prevent z-clipping)
o.vertex.xyz += worldNormal * 0.001f;
// back to to world space
o.vertex = mul(unity_WorldToObject, o.vertex);
// object to clip position
o.vertex = UnityObjectToClipPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// all mesh-collider previews have an alpha value that can be changed if you wish.
_Color.a = 0.8f;
return _Color;
}
ENDCG
}
//VHACD pass, normal z-testing.
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 2.0
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float4 normal: NORMAL;
};
struct v2f {
float4 vertex : SV_POSITION;
};
half4 _Color;
v2f vert (appdata_t v)
{
v2f o;
//world space vert
o.vertex = mul(unity_ObjectToWorld, v.vertex);
// world space normal
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
// offset by a small amount (1mm) (to prevent z-clipping)
o.vertex.xyz += worldNormal * 0.001f;
// back to to world space
o.vertex = mul(unity_WorldToObject, o.vertex);
// object to clip position
o.vertex = UnityObjectToClipPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// all mesh-collider previews have an alpha value that can be changed if you wish.
_Color.a = 0.8f;
return _Color;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,17 @@
fileFormatVersion: 2
guid: fc68b98d1257f434680734c4924323ef
timeCreated: 1600087651
licenseType: Store
ShaderImporter:
externalObjects: {}
defaultTextures: []
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Shader/EasyColliderMeshColliderPreview.shader
uploadId: 885904

View File

@@ -0,0 +1,181 @@
Shader "Custom/EasyColliderShader"
{
// Attempted to write this shader as human readable as possible in case someone is looking to modify it.
// I'm not great with shaders, so I'm sure a lot could be improved
// If you encounter any shader issues please contact me so I can try and fix them!
// the first pass is for Windows, the second is for Mac/Metal and uses PSIZE instead of geometry shader.
Properties
{
_Color ("Color", Color) = (0,1,0,1)
_Size ("Size", float) = 0.01
}
// everything else (vulkan does not use psize)
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
// Always draw selected vertex things on top.
ZTest Always
CGPROGRAM
#pragma target 4.5
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
// In regular graphics shaders the compute buffer support requires minimum shader model 4.5.
#include "UnityCG.cginc"
// default values
float4 _Color = float4(0,1,0,1);
float _Size = 0.01;
// struct of data we pass in from the compute script.
struct worldPosition {
float3 position;
};
// buffer of all our world points passed from our compute script.
StructuredBuffer<worldPosition> worldPositions;
// output from vertex shader, goes into the geometry shader, and the fragment shader.
struct vertOutput {
float4 position: SV_POSITION;
};
// for the vertex shader, we just need to vertex id, which matches with the worldPoints buffer of data
vertOutput vert(uint id: SV_VertexID)
{
// Create a new vertexOutput, and set the position on it.
vertOutput o;
o.position = float4(worldPositions[id].position, 1.0f);
return o;
}
// 36 vertices to make the box that is displayed over vertices.
#define totalVerts 36
// geometry shader creates a box at each vertOutput passed in.
[maxvertexcount(totalVerts)]
void geom(point vertOutput p[1], inout TriangleStream<vertOutput> triStream)
{
// dimensions of length & width are the size / 2.
float d = _Size/2;
// scale of the square will be based on camera distance,
// so that as you get closer or further, the displayed cube remains the same size on screen.
float scale = distance(_WorldSpaceCameraPos, p[0].position);
if (unity_OrthoParams.w ==1) {
scale = unity_OrthoParams.y*2;
}
// verts:
// by x (negative z): ---, -+-, +--, ++-
// (positive z): --+, -++, +-+, +++
// create a square, order of vertices in triangles matters for normals.
const float4 square[totalVerts] = {
// z+
float4(-d,d,d,0), float4(-d,-d,d,0), float4(d,-d,d,0),
float4(d,d,d,0), float4(-d,d,d,0), float4(d,-d,d,0),
// z-
float4(d,-d,-d,0), float4(-d,-d,-d,0), float4(-d,d,-d,0),
float4(d,d,-d,0), float4(d,-d,-d,0), float4(-d,d,-d,0),
// y+
float4(-d,d,d,0), float4(d,d,d,0), float4(d,d,-d,0),
float4(d,d,-d,0), float4(-d,d,-d,0), float4(-d,d,d,0),
// y-
float4(-d,-d,d,0), float4(d,-d,-d,0), float4(d,-d,d,0),
float4(d,-d,-d,0), float4(-d,-d,d,0), float4(-d,-d,-d,0),
// x+
float4(d,d,-d,0), float4(d,d,d,0), float4(d,-d,d,0),
float4(d,d,-d,0), float4(d,-d,d,0), float4(d,-d,-d,0),
// x-
float4(-d,d,-d,0), float4(-d,-d,d,0), float4(-d,d,d,0),
float4(-d,d,-d,0), float4(-d,-d,-d,0), float4(-d,-d,d,0),
};
// array of vertices to make the triangles with
vertOutput Vertex[totalVerts];
// build the cube
for(int i=0; i<totalVerts; i+=3) {
// get vertex positions for every 3 points on the cube
Vertex[i].position = UnityObjectToClipPos(p[0].position + square[i]*scale);
Vertex[i+1].position = UnityObjectToClipPos(p[0].position + square[i+1]*scale);
Vertex[i+2].position = UnityObjectToClipPos(p[0].position + square[i+2]*scale);
// create a triangle for every 3 outputs.
triStream.Append(Vertex[i]);
triStream.Append(Vertex[i+1]);
triStream.Append(Vertex[i+2]);
// allows for unconnected triangles to be output.
triStream.RestartStrip();
}
}
// just returns the color for all fragments. simple.
float4 frag(vertOutput i) : COLOR
{
return _Color;
}
ENDCG
}
}
// METAL only shader. (w/ psize)
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
// Always draw selected vertex things on top.
ZTest Always
CGPROGRAM
// In regular graphics shaders the compute buffer support requires minimum shader model 4.5.
#pragma target 4.5
#pragma only_renderers metal
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// default values
float4 _Color = float4(0,1,0,1);
float _Size = 0.01;
// struct of data we pass in from the compute script.
struct worldPosition {
float3 position;
};
// buffer of all our world points passed from our compute script.
StructuredBuffer<worldPosition> worldPositions;
// output from vertex shader, goes into the geometry shader, and the fragment shader.
struct vertOutput {
float4 position: SV_POSITION;
};
// for the vertex shader, we just need to vertex id, which matches with the worldPoints buffer of data
vertOutput vert(uint id: SV_VertexID, out float pointSize:PSIZE)
{
// Create a new vertexOutput, and set the position on it.
vertOutput o;
o.position = float4(worldPositions[id].position, 1.0f);
o.position = UnityObjectToClipPos(o.position);
// approximately equal to the windows version size.
pointSize = _Size * 700;
return o;
}
// just returns the color for all fragments. simple.
float4 frag(vertOutput i) : COLOR
{
return _Color;
}
ENDCG
}
}
FallBack "Diffuse"
}

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: 47ca8cc19e520064baecb98dc4e2eb9e
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/Shader/EasyColliderShader.shader
uploadId: 885904

View File

@@ -0,0 +1,208 @@
#if (UNITY_EDITOR)
namespace ECE
{
using UnityEngine;
using System.Collections.Generic;
[System.Serializable]
public class VHACDParameters
{
public float NormalExtrudeMultiplier = 0.0f;
[HideInInspector]
/// <summary>
/// is the current calculation for displaying a preview?
/// </summary>
public bool IsCalculationForPreview;
public VHACD_CONVERSION ConvertTo;
[HideInInspector]
/// <summary>
/// the suffix to add to saved convex hulls: objectName_suffix_01 etc.
/// </summary>
public string SaveSuffix = "_ConvexHull_";
/// <summary>
/// Method to use to attach resulting convex hulls to attach to object.
/// </summary>
public VHACD_RESULT_METHOD vhacdResultMethod = VHACD_RESULT_METHOD.AttachTo;
[HideInInspector]
/// <summary>
/// Run VHACD only on the selected vertices?
/// </summary>
public bool UseSelectedVertices = false;
[HideInInspector]
/// <summary>
/// Current mesh filter VHACD is calculating.
/// </summary>
public int CurrentMeshFilter = 0;
[HideInInspector]
/// <summary>
/// Should child meshes be seperately done in the calculation / adding of convex hulls.
/// </summary>
public bool SeparateChildMeshes = false;
[HideInInspector]
/// <summary>
/// should the attach to method be run per mesh that is separated?
/// </summary>
public bool PerMeshAttachOverride = false;
[HideInInspector]
/// <summary>
/// List of mesh filters for VHACD calculation.
/// </summary>
public List<MeshFilter> MeshFilters = new List<MeshFilter>();
[HideInInspector]
/// <summary>
/// Gameobject to attach mesh colliders to using the result of VHACD.
/// </summary>
public GameObject AttachTo;
[HideInInspector]
/// <summary>
/// Save path of current VHACD meshes
/// </summary>
public string SavePath;
[Range(0, 1f)]
/// <summary>
/// maximum concavity
/// </summary>
public double concavity;
[Range(0, 1f)]
/// <summary>
/// controls bias toward clipping along symmetry planes
/// </summary>
public double alpha;
[Range(0, 1f)]
/// <summary>
/// controls bias toward clipping along revolution axes
/// </summary>
public double beta;
[Range(0, 1f)]
/// <summary>
/// controls adaptive sampling of the generated convex-hulls
/// </summary>
public double minVolumePerCH;
[Range(10000, 64000000)]
/// <summary>
/// maximum number of voxels generated during voxelization stage
/// </summary>
public int resolution;
[Range(4, 1024)]
/// <summary>
/// controls maximum number of triangles per convex hull
/// </summary>
public int maxNumVerticesPerConvexHull;
[Range(1, 16)]
/// <summary>
/// controls the granularity of the search for the "best" clipping plane
/// </summary>
public int planeDownsampling;
[Range(1, 16)]
/// <summary>
/// controls the precision of the convex-hull generation process during the clipping plane selection stage
/// </summary>
public int convexhullDownSampling;
[Range(1, 128)]
/// <summary>
/// maximum number of convex hulls
/// </summary>
public int maxConvexHulls;
/// <summary>
/// When enabled, will project the output convex hull vertices onto the original source mesh
/// </summary>
public bool projectHullVertices;
/// <summary>
/// Fill mode to determine what is inside/outside the mesh
/// Flood fill: basic flood fill algorithm
/// Raycast fill: raycasts are used to determine inside/outside
/// Surface: used when the surface is to represent a hollow object.
/// </summary>
public VHACD_FILL_MODE fillMode;
/// <summary>
/// Should we force recalculation when resulting hulls have a hull with triangle count >=256.
/// </summary>
public bool forceUnder256Triangles;
/// <summary>
/// Creates a VHACD parameters object with default values.
/// </summary>
public VHACDParameters()
{
concavity = 0.0025;
alpha = 0.05;
beta = 0.05;
minVolumePerCH = 0.0001;
resolution = 10000;
maxNumVerticesPerConvexHull = 64;
planeDownsampling = 4;
convexhullDownSampling = 4;
maxConvexHulls = 1;
projectHullVertices = true;
fillMode = VHACD_FILL_MODE.FLOOD_FILL;
forceUnder256Triangles = true;
}
/// <summary>
/// Creates a VHACDParameters object with the values of another VHACDParam
/// </summary>
/// <param name="other">Values to copy from</param>
public VHACDParameters(VHACDParameters other)
{
ConvertTo = other.ConvertTo;
SaveSuffix = other.SaveSuffix;
vhacdResultMethod = other.vhacdResultMethod;
AttachTo = other.AttachTo;
concavity = other.concavity;
alpha = other.alpha;
beta = other.beta;
minVolumePerCH = other.minVolumePerCH;
resolution = other.resolution;
maxNumVerticesPerConvexHull = other.maxNumVerticesPerConvexHull;
planeDownsampling = other.planeDownsampling;
convexhullDownSampling = other.convexhullDownSampling;
maxConvexHulls = other.maxConvexHulls;
projectHullVertices = other.projectHullVertices;
fillMode = other.fillMode;
forceUnder256Triangles = other.forceUnder256Triangles;
SeparateChildMeshes = other.SeparateChildMeshes;
UseSelectedVertices = other.UseSelectedVertices;
MeshFilters = new List<MeshFilter>();
foreach (MeshFilter f in other.MeshFilters)
{
MeshFilters.Add(f);
}
NormalExtrudeMultiplier = other.NormalExtrudeMultiplier;
PerMeshAttachOverride = other.PerMeshAttachOverride;
}
/// <summary>
/// Clones the current instance of VHACDParameters.
/// </summary>
/// <returns>Copy of the VHACDParameters instance.</returns>
public VHACDParameters Clone()
{
return new VHACDParameters(this);
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 77dedb303367131439faaeb70ed23f25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/VHACDParameters.cs
uploadId: 885904

View File

@@ -0,0 +1,74 @@
#if (UNITY_EDITOR)
namespace ECE
{
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class VHACDScriptableSettings : ScriptableObject
{
[SerializeField] VHACDParameters _vhacdParameters;
public VHACDParameters GetParameters() { return _vhacdParameters; }
public void SetParameters(VHACDParameters parameters)
{
VHACDParameters copy = new VHACDParameters(parameters);
_vhacdParameters = copy;
}
/// <summary>
/// Saves the current parameters after popping up a save file panel asking where to save.
/// </summary>
/// <param name="vhacdParamsToSave"></param>
public static void Save(VHACDParameters vhacdParamsToSave)
{
string firstAssetPath = FindFirstSOAssetPath();
string path = EditorUtility.SaveFilePanel("Select where to save the current VHACD Settings.", firstAssetPath, "VHACDSettings", "asset");
if (path.Contains(Application.dataPath))
{
path = path.Replace(Application.dataPath, "Assets");
if (!string.IsNullOrEmpty(path))
{
VHACDScriptableSettings settingstoSave = ScriptableObject.CreateInstance<VHACDScriptableSettings>();
settingstoSave.SetParameters(vhacdParamsToSave);
AssetDatabase.CreateAsset(settingstoSave, path);
AssetDatabase.SaveAssets();
}
}
}
/// <summary>
/// Pops open a file panel and loads
/// </summary>
/// <returns></returns>
public static VHACDScriptableSettings Load()
{
string firstAssetPath = FindFirstSOAssetPath(); // probably just better to load wherever it finds a vhacd settings object.
string path = EditorUtility.OpenFilePanel("Select VHACD Settings to Load", firstAssetPath, "asset");
path = path.Replace(Application.dataPath, "Assets");
VHACDScriptableSettings settings = AssetDatabase.LoadAssetAtPath<VHACDScriptableSettings>(path);
return settings;
}
public static string FindFirstSOAssetPath()
{
string[] ecp = AssetDatabase.FindAssets("t:VHACDScriptableSettings");
string assetPath = "";
if (ecp.Length > 0)
{
assetPath = AssetDatabase.GUIDToAssetPath(ecp[0]);
}
if (!string.IsNullOrEmpty(assetPath))
{
int index = assetPath.LastIndexOf("/");
if (index >= 0)
{
assetPath = assetPath.Remove(index, assetPath.Length - index);
}
}
return assetPath;
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4787408722c64ab4db0b7df26d9653a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/VHACDScriptableSettings.cs
uploadId: 885904

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f2afcddae4edf9041bf10bee2f4e86df
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: b342658f1ee710947aa3d97df0dc576f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 67880
packageName: Easy Collider Editor
packageVersion: 6.20.1
assetPath: Assets/EasyColliderEditor/Scripts/VHACDSettings/ECE_DefaultVHACDSettings.asset
uploadId: 885904