You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

419 lines
15 KiB
C#

6 months ago
using UnityEditor;
using UnityEngine;
using System.Collections;
using System;
using System.Reflection;
namespace RootMotion.FinalIK
{
/*
* Custom inspector for RotationLimitPolygonal
* */
[CustomEditor(typeof(RotationLimitPolygonal))]
[CanEditMultipleObjects]
public class RotationLimitPolygonalInspector : RotationLimitInspector
{
/*
* Used for quick symmetric editing in the scene
* */
public enum Symmetry
{
Off,
X,
Y,
Z
}
private RotationLimitPolygonal script { get { return target as RotationLimitPolygonal; } }
private RotationLimitPolygonal clone;
private int selectedPoint = -1, deletePoint = -1, addPoint = -1;
private float degrees = 90;
private Symmetry symmetry;
#region Inspector
public void OnEnable()
{
// If initialized, set up the default polygon
if (script.points == null || (script.points != null && script.points.Length < 3))
{
script.ResetToDefault();
EditorUtility.SetDirty(script);
}
}
public override void OnInspectorGUI()
{
GUI.changed = false;
// Clamping values
script.twistLimit = Mathf.Clamp(script.twistLimit, 0, 180);
script.smoothIterations = Mathf.Clamp(script.smoothIterations, 0, 3);
DrawDefaultInspector();
if (GUI.changed) EditorUtility.SetDirty(script);
}
#endregion Inspector
#region Scene
public void OnSceneGUI()
{
GUI.changed = false;
// Set defaultLocalRotation so that the initial local rotation will be the zero point for the rotation limit
if (!Application.isPlaying && !script.defaultLocalRotationOverride) script.defaultLocalRotation = script.transform.localRotation;
if (script.axis == Vector3.zero) return;
// Quick Editing Tools
Handles.BeginGUI();
GUILayout.BeginArea(new Rect(10, 10, 550, 140), "Rotation Limit Polygonal", "Window");
// Cloning values from another RotationLimitPolygonal
EditorGUILayout.BeginHorizontal();
if (Inspector.Button("Clone From", "Make this rotation limit identical to another", script, GUILayout.Width(220))) CloneLimit();
clone = (RotationLimitPolygonal)EditorGUILayout.ObjectField("", clone, typeof(RotationLimitPolygonal), true);
EditorGUILayout.EndHorizontal();
// Symmetry
symmetry = (Symmetry)EditorGUILayout.EnumPopup("Symmetry", symmetry, GUILayout.Width(220));
// Flipping
EditorGUILayout.BeginHorizontal();
if (Inspector.Button("Flip X", "Flip points along local X axis", script, GUILayout.Width(100))) FlipLimit(0);
if (Inspector.Button("Flip Y", "Flip points along local Y axis", script, GUILayout.Width(100))) FlipLimit(1);
if (Inspector.Button("Flip Z", "Flip points along local Z axis", script, GUILayout.Width(100))) FlipLimit(2);
GUILayout.Label("Flip everything along axis");
EditorGUILayout.EndHorizontal();
// Rotating
EditorGUILayout.BeginHorizontal();
if (Inspector.Button("Rotate X", "Rotate points along X axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.right);
if (Inspector.Button("Rotate Y", "Rotate points along Y axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.up);
if (Inspector.Button("Rotate Z", "Rotate points along Z axis by Degrees", script, GUILayout.Width(100))) RotatePoints(degrees, Vector3.forward);
degrees = EditorGUILayout.FloatField("Degrees", degrees, GUILayout.Width(200));
EditorGUILayout.EndHorizontal();
// Smooth/Optimize
EditorGUILayout.BeginHorizontal();
if (Inspector.Button("Smooth", "Double the points", script)) Smooth();
if (Inspector.Button("Optimize", "Delete every second point", script)) Optimize();
EditorGUILayout.EndHorizontal();
GUILayout.EndArea();
Handles.EndGUI();
// Rebuild reach cones
script.BuildReachCones();
// Draw a white transparent sphere
DrawRotationSphere(script.transform.position);
// Draw Axis
DrawArrow(script.transform.position, Direction(script.axis), colorDefault, "Axis", 0.02f);
// Display limit points
for (int i = 0; i < script.points.Length; i++)
{
Color color = GetColor(i); // Paint the point in green or red if it belongs to an invalid reach cone
Handles.color = color;
GUI.color = color;
// Line from the center to the point and the label
Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.points[i].point));
Handles.Label(script.transform.position + Direction(script.points[i].point + new Vector3(-0.02f, 0, 0)), " " + i.ToString());
// Selecting points
Handles.color = colorHandles;
if (Inspector.DotButton(script.transform.position + Direction(script.points[i].point), script.transform.rotation, 0.02f, 0.02f))
{
selectedPoint = i;
}
Handles.color = Color.white;
GUI.color = Color.white;
// Limit point GUI
if (i == selectedPoint)
{
Handles.BeginGUI();
//GUILayout.BeginArea(new Rect(Screen.width - 240, Screen.height - 180, 230, 130), "Limit Point " + i.ToString(), "Window");
GUILayout.BeginArea(new Rect(10, 160, 230, 130), "Limit Point " + i.ToString(), "Window");
if (Inspector.Button("Delete", "Delete this point", script))
{
if (script.points.Length > 3)
{
// Using the deletePoint index here because we dont want to delete points from the array that we are iterating
deletePoint = i;
}
else if (!Warning.logged) script.LogWarning("Polygonal Rotation Limit should have at least 3 limit points");
}
if (Inspector.Button("Add Point", "Add a new point next to this one", script))
{
addPoint = i;
}
// Store point for undo
Vector3 oldPoint = script.points[i].point;
// Manual input for the point position
Inspector.AddVector3(ref script.points[i].point, "Point", script, GUILayout.Width(210));
EditorGUILayout.Space();
// Tangent weight
Inspector.AddFloat(ref script.points[i].tangentWeight, "Tangent Weight", "Weight of this point's tangent. Used in smoothing.", script, -Mathf.Infinity, Mathf.Infinity, GUILayout.Width(150));
GUILayout.EndArea();
Handles.EndGUI();
// Moving Points
Vector3 pointWorld = Handles.PositionHandle(script.transform.position + Direction(script.points[i].point), Quaternion.identity);
Vector3 newPoint = InverseDirection(pointWorld - script.transform.position);
if (newPoint != script.points[i].point)
{
if (!Application.isPlaying) Undo.RecordObject(script, "Move Limit Point");
script.points[i].point = newPoint;
}
// Symmetry
if (symmetry != Symmetry.Off && script.points.Length > 3 && oldPoint != script.points[i].point)
{
RotationLimitPolygonal.LimitPoint symmetryPoint = GetClosestPoint(Symmetrize(oldPoint, symmetry));
if (symmetryPoint != script.points[i])
{
symmetryPoint.point = Symmetrize(script.points[i].point, symmetry);
}
}
}
// Normalize the point
script.points[i].point = script.points[i].point.normalized;
}
// Display smoothed polygon
for (int i = 0; i < script.P.Length; i++)
{
Color color = GetColor(i);
// Smoothed triangles are transparent
Handles.color = new Color(color.r, color.g, color.b, 0.25f);
Handles.DrawLine(script.transform.position, script.transform.position + Direction(script.P[i]));
Handles.color = color;
if (i < script.P.Length - 1) Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[i + 1]));
else Handles.DrawLine(script.transform.position + Direction(script.P[i]), script.transform.position + Direction(script.P[0]));
Handles.color = Color.white;
}
// Deleting points
if (deletePoint != -1)
{
DeletePoint(deletePoint);
selectedPoint = -1;
deletePoint = -1;
}
// Adding points
if (addPoint != -1)
{
AddPoint(addPoint);
addPoint = -1;
}
if (GUI.changed) EditorUtility.SetDirty(script);
}
private Color GetColor(int i)
{
// Paint the polygon in red if the reach cone is invalid
return script.reachCones[i].isValid ? colorDefault : colorInvalid;
}
/*
* Doubles the number of Limit Points
* */
private void Smooth()
{
int length = script.points.Length;
for (int i = 0; i < length; i++)
{
AddPoint(i + i);
}
}
/*
* Reduces the number of Limit Points
* */
private void Optimize()
{
for (int i = 1; i < script.points.Length; i++)
{
if (script.points.Length > 3) DeletePoint(i);
}
}
/*
* Flips the rotation limit along the axis
* */
private void FlipLimit(int axis)
{
script.axis[axis] = -script.axis[axis];
foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points) limitPoint.point[axis] = -limitPoint.point[axis];
Array.Reverse(script.points);
script.BuildReachCones();
}
private void RotatePoints(float degrees, Vector3 axis)
{
foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points) limitPoint.point = Quaternion.AngleAxis(degrees, axis) * limitPoint.point;
script.BuildReachCones();
}
/*
* Converting directions from local space to world space
* */
private Vector3 Direction(Vector3 v)
{
if (script.transform.parent == null) return script.defaultLocalRotation * v;
return script.transform.parent.rotation * (script.defaultLocalRotation * v);
}
/*
* Inverse of Direction(Vector3 v)
* */
private Vector3 InverseDirection(Vector3 v)
{
if (script.transform.parent == null) return Quaternion.Inverse(script.defaultLocalRotation) * v;
return Quaternion.Inverse(script.defaultLocalRotation) * Quaternion.Inverse(script.transform.parent.rotation) * v;
}
/*
* Removing Limit Points
* */
private void DeletePoint(int p)
{
RotationLimitPolygonal.LimitPoint[] newPoints = new RotationLimitPolygonal.LimitPoint[0];
for (int i = 0; i < script.points.Length; i++)
{
if (i != p)
{
Array.Resize(ref newPoints, newPoints.Length + 1);
newPoints[newPoints.Length - 1] = script.points[i];
}
}
script.points = newPoints;
script.BuildReachCones();
}
/*
* Creating new Limit Points
* */
private void AddPoint(int p)
{
RotationLimitPolygonal.LimitPoint[] newPoints = new RotationLimitPolygonal.LimitPoint[script.points.Length + 1];
for (int i = 0; i < p + 1; i++) newPoints[i] = script.points[i];
newPoints[p + 1] = new RotationLimitPolygonal.LimitPoint();
Vector3 nextPoint = Vector3.forward;
if (p < script.points.Length - 1) nextPoint = script.points[p + 1].point;
else nextPoint = script.points[0].point;
newPoints[p + 1].point = Vector3.Lerp(script.points[p].point, nextPoint, 0.5f);
for (int i = p + 2; i < newPoints.Length; i++) newPoints[i] = script.points[i - 1];
script.points = newPoints;
script.BuildReachCones();
}
/*
* Clone properties from another RotationLimitPolygonal
* */
private void CloneLimit()
{
if (clone == null) return;
if (clone == script)
{
script.LogWarning("Can't clone from self.");
return;
}
script.axis = clone.axis;
script.twistLimit = clone.twistLimit;
script.smoothIterations = clone.smoothIterations;
script.points = new RotationLimitPolygonal.LimitPoint[clone.points.Length];
for (int i = 0; i < script.points.Length; i++)
{
script.points[i] = (RotationLimitPolygonal.LimitPoint)CloneObject(clone.points[i]);
}
script.BuildReachCones();
}
private static object CloneObject(object o)
{
Type t = o.GetType();
object clone = Activator.CreateInstance(t);
foreach (FieldInfo fi in t.GetFields())
{
fi.SetValue(clone, fi.GetValue(o));
}
return clone;
}
/*
* Flipping vectors for symmetry
* */
private static Vector3 Symmetrize(Vector3 v, Symmetry symmetry)
{
switch (symmetry)
{
case Symmetry.X: return new Vector3(-v.x, v.y, v.z);
case Symmetry.Y: return new Vector3(v.x, -v.y, v.z);
case Symmetry.Z: return new Vector3(v.x, v.y, -v.z);
default: return v;
}
}
/*
* Returns closest point to a position. Used for symmetric editing
* */
private RotationLimitPolygonal.LimitPoint GetClosestPoint(Vector3 v)
{
float closestDistace = Mathf.Infinity;
RotationLimitPolygonal.LimitPoint closestPoint = null;
foreach (RotationLimitPolygonal.LimitPoint limitPoint in script.points)
{
if (limitPoint.point == v) return limitPoint;
float d = Vector3.Distance(limitPoint.point, v);
if (d < closestDistace)
{
closestPoint = limitPoint;
closestDistace = d;
}
}
return closestPoint;
}
#endregion Scene
}
}