1241 lines
44 KiB
C#
1241 lines
44 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using UnityEditor.EditorTools;
|
|
using System;
|
|
using UnityEditor.Overlays;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Obi
|
|
{
|
|
[EditorTool("Obi Path Editor Tool",typeof(ObiRopeBase))]
|
|
public class ObiPathEditor : EditorTool
|
|
{
|
|
|
|
[Overlay(typeof(SceneView), "Obi Path Editor", "Obi Path Editor", "Obi Path Editor", true)]
|
|
[Icon("Assets/Obi/Editor/Resources/EditCurves.psd")]
|
|
class PathEditorOverlay : Overlay, ITransientOverlay
|
|
{
|
|
public static ObiPathEditor editor;
|
|
|
|
public override VisualElement CreatePanelContent()
|
|
{
|
|
var root = new VisualElement();
|
|
root.Add(new IMGUIContainer(editor.DrawToolPanel));
|
|
return root;
|
|
}
|
|
|
|
// Use the visible property to hide or show this instance from within the class.
|
|
public bool visible
|
|
{
|
|
get
|
|
{
|
|
return ToolManager.activeToolType == typeof(ObiPathEditor);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum PathEditorTool
|
|
{
|
|
TranslatePoints,
|
|
RotatePoints,
|
|
ScalePoints,
|
|
OrientPoints,
|
|
InsertPoints,
|
|
RemovePoints
|
|
}
|
|
|
|
ObiPath path;
|
|
|
|
Quaternion prevRot = Quaternion.identity;
|
|
Vector3 prevScale = Vector3.one;
|
|
|
|
PathEditorTool currentTool = PathEditorTool.TranslatePoints;
|
|
bool showTangentHandles = true;
|
|
bool showThicknessHandles = true;
|
|
|
|
public bool needsRepaint = false;
|
|
|
|
protected bool[] selectedStatus;
|
|
protected int lastSelected = 0;
|
|
protected int selectedCount = 0;
|
|
protected Vector3 selectionAverage;
|
|
protected bool useOrientation = false;
|
|
|
|
protected static Color handleColor = new Color(1, 0.55f, 0.1f);
|
|
protected GUIContent m_IconContent;
|
|
|
|
public override GUIContent toolbarIcon
|
|
{
|
|
get
|
|
{
|
|
if (m_IconContent == null)
|
|
{
|
|
m_IconContent = new GUIContent()
|
|
{
|
|
image = Resources.Load<Texture2D>("EditCurves"),
|
|
text = "Obi Path Editor Tool",
|
|
tooltip = "Obi Path Editor Tool"
|
|
};
|
|
}
|
|
return m_IconContent;
|
|
}
|
|
}
|
|
|
|
ObiRopeBlueprintBase blueprint
|
|
{
|
|
get { return (target as ObiRopeBase).sharedBlueprint as ObiRopeBlueprintBase; }
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
this.useOrientation = target is ObiRod;
|
|
selectedStatus = new bool[0];
|
|
PathEditorOverlay.editor = this;
|
|
}
|
|
|
|
public void ResizeCPArrays()
|
|
{
|
|
Array.Resize(ref selectedStatus, path.ControlPointCount);
|
|
}
|
|
|
|
public override void OnToolGUI(EditorWindow window)
|
|
{
|
|
needsRepaint = false;
|
|
|
|
float thicknessScale = blueprint.thickness;
|
|
this.path = (target as ObiRopeBase).path;
|
|
var matrix = (target as ObiRopeBase).transform.localToWorldMatrix;
|
|
|
|
ResizeCPArrays();
|
|
|
|
HandleUtility.AddDefaultControl(GUIUtility.GetControlID("PathEditor".GetHashCode(), FocusType.Passive));
|
|
|
|
Matrix4x4 prevMatrix = Handles.matrix;
|
|
Handles.matrix = matrix;
|
|
|
|
// Draw control points:
|
|
Handles.color = handleColor;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
needsRepaint |= DrawControlPoint(i);
|
|
}
|
|
|
|
// Count selected and calculate average position:
|
|
selectionAverage = GetControlPointAverage(out lastSelected, out selectedCount);
|
|
|
|
// Draw cp tool handles:
|
|
needsRepaint |= SplineCPTools(matrix);
|
|
|
|
if (showThicknessHandles)
|
|
needsRepaint |= DoThicknessHandles(thicknessScale);
|
|
|
|
// Control point selection handle:
|
|
needsRepaint |= ObiPathHandles.SplineCPSelector(path, selectedStatus);
|
|
|
|
Handles.matrix = prevMatrix;
|
|
|
|
// During edit mode, allow to add/remove control points.
|
|
if (currentTool == PathEditorTool.InsertPoints)
|
|
AddControlPointsMode(matrix);
|
|
|
|
if (currentTool == PathEditorTool.RemovePoints)
|
|
RemoveControlPointsMode(matrix);
|
|
|
|
if (needsRepaint)
|
|
window.Repaint();
|
|
|
|
}
|
|
|
|
private void AddControlPointsMode(Matrix4x4 matrix)
|
|
{
|
|
|
|
float mu = ScreenPointToCurveMu(path, Event.current.mousePosition, matrix);
|
|
|
|
Vector3 pointOnSpline = matrix.MultiplyPoint3x4(path.points.GetPositionAtMu(path.Closed, mu));
|
|
|
|
float size = HandleUtility.GetHandleSize(pointOnSpline) * 0.12f;
|
|
|
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
|
Handles.color = Color.green;
|
|
Handles.DrawDottedLine(pointOnSpline, ray.origin, 4);
|
|
Handles.SphereHandleCap(0, pointOnSpline, Quaternion.identity, size, Event.current.type);
|
|
|
|
|
|
if (Event.current.type == EventType.MouseDown && Event.current.modifiers == EventModifiers.None)
|
|
{
|
|
Undo.RecordObject(blueprint, "Add");
|
|
|
|
int newIndex = path.InsertControlPoint(mu);
|
|
if (newIndex >= 0)
|
|
{
|
|
ResizeCPArrays();
|
|
for (int i = 0; i < selectedStatus.Length; ++i)
|
|
selectedStatus[i] = false;
|
|
selectedStatus[newIndex] = true;
|
|
}
|
|
|
|
path.FlushEvents();
|
|
Event.current.Use();
|
|
}
|
|
|
|
// Repaint the scene, so that the add control point helpers are updated every frame.
|
|
SceneView.RepaintAll();
|
|
|
|
}
|
|
|
|
private void RemoveControlPointsMode(Matrix4x4 matrix)
|
|
{
|
|
|
|
float mu = ScreenPointToCurveMu(path, Event.current.mousePosition, matrix);
|
|
|
|
Vector3 pointOnSpline = matrix.MultiplyPoint3x4(path.points.GetPositionAtMu(path.Closed, mu));
|
|
|
|
float size = HandleUtility.GetHandleSize(pointOnSpline) * 0.12f;
|
|
|
|
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
|
|
|
|
Handles.color = Color.red;
|
|
Handles.DrawDottedLine(pointOnSpline, ray.origin, 4);
|
|
|
|
int index = path.GetClosestControlPointIndex(mu);
|
|
Handles.SphereHandleCap(0, matrix.MultiplyPoint3x4(path.points[index].position), Quaternion.identity, size, Event.current.type);
|
|
|
|
if (Event.current.type == EventType.MouseDown && Event.current.modifiers == EventModifiers.None && index >= 0 && path.ControlPointCount > 2)
|
|
{
|
|
Undo.RecordObject(blueprint, "Remove");
|
|
|
|
path.RemoveControlPoint(index);
|
|
ResizeCPArrays();
|
|
for (int i = 0; i < selectedStatus.Length; ++i)
|
|
selectedStatus[i] = false;
|
|
|
|
path.FlushEvents();
|
|
Event.current.Use();
|
|
}
|
|
|
|
// Repaint the scene, so that the add control point helpers are updated every frame.
|
|
SceneView.RepaintAll();
|
|
|
|
}
|
|
|
|
protected bool DrawControlPoint(int i)
|
|
{
|
|
bool repaint = false;
|
|
var wp = path.points[i];
|
|
float size = HandleUtility.GetHandleSize(wp.position) * 0.04f;
|
|
|
|
if (selectedStatus[i] && showTangentHandles)
|
|
{
|
|
|
|
Handles.color = handleColor;
|
|
|
|
if (!(i == 0 && !path.Closed))
|
|
{
|
|
Vector3 tangentPosition = wp.inTangentEndpoint;
|
|
|
|
if (Event.current.type == EventType.Repaint)
|
|
Handles.DrawDottedLine(tangentPosition, wp.position, 2);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Handles.DotHandleCap(0, tangentPosition, Quaternion.identity, size, Event.current.type);
|
|
Vector3 newTangent = Handles.PositionHandle(tangentPosition, Quaternion.identity);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Modify tangent");
|
|
wp.SetInTangentEndpoint(newTangent);
|
|
path.points[i] = wp;
|
|
path.FlushEvents();
|
|
repaint = true;
|
|
}
|
|
}
|
|
|
|
if (!(i == path.ControlPointCount - 1 && !path.Closed))
|
|
{
|
|
Vector3 tangentPosition = wp.outTangentEndpoint;
|
|
|
|
if (Event.current.type == EventType.Repaint)
|
|
Handles.DrawDottedLine(tangentPosition, wp.position, 2);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Handles.DotHandleCap(0, tangentPosition, Quaternion.identity, size, Event.current.type);
|
|
Vector3 newTangent = Handles.PositionHandle(tangentPosition, Quaternion.identity);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Modify tangent");
|
|
wp.SetOutTangentEndpoint(newTangent);
|
|
path.points[i] = wp;
|
|
path.FlushEvents();
|
|
repaint = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Event.current.type == EventType.Repaint)
|
|
{
|
|
|
|
Handles.color = selectedStatus[i] ? handleColor : Color.white;
|
|
Vector3 pos = wp.position;
|
|
|
|
if (currentTool == PathEditorTool.OrientPoints)
|
|
{
|
|
Handles.ArrowHandleCap(0, pos, Quaternion.LookRotation(path.normals[i]), HandleUtility.GetHandleSize(pos), EventType.Repaint);
|
|
}
|
|
|
|
Handles.SphereHandleCap(0, pos, Quaternion.identity, size * 3, EventType.Repaint);
|
|
|
|
}
|
|
return repaint;
|
|
}
|
|
|
|
protected Vector3 GetControlPointAverage(out int lastSelected, out int selectedCount)
|
|
{
|
|
|
|
lastSelected = -1;
|
|
selectedCount = 0;
|
|
Vector3 averagePos = Vector3.zero;
|
|
|
|
// Find center of all selected control points:
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
|
|
averagePos += path.points[i].position;
|
|
selectedCount++;
|
|
lastSelected = i;
|
|
|
|
}
|
|
}
|
|
if (selectedCount > 0)
|
|
averagePos /= selectedCount;
|
|
return averagePos;
|
|
|
|
}
|
|
|
|
protected bool SplineCPTools(Matrix4x4 matrix)
|
|
{
|
|
bool repaint = false;
|
|
|
|
// Calculate handle rotation, for local or world pivot modes.
|
|
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ? Quaternion.identity : Quaternion.Inverse(matrix.rotation);
|
|
|
|
// Reset initial handle rotation/orientation after using a tool:
|
|
if (GUIUtility.hotControl == 0)
|
|
{
|
|
|
|
prevRot = handleRotation;
|
|
prevScale = Vector3.one;
|
|
|
|
if (selectedCount == 1 && Tools.pivotRotation == PivotRotation.Local && currentTool == PathEditorTool.OrientPoints)
|
|
{
|
|
//prevRot = Quaternion.LookRotation(GetNormal(lastSelected));
|
|
}
|
|
}
|
|
|
|
// Transform handles:
|
|
if (selectedCount > 0)
|
|
{
|
|
|
|
if (useOrientation && currentTool == PathEditorTool.OrientPoints)
|
|
{
|
|
repaint |= OrientTool(selectionAverage, handleRotation);
|
|
}
|
|
else
|
|
{
|
|
switch (currentTool)
|
|
{
|
|
case PathEditorTool.TranslatePoints:
|
|
{
|
|
repaint |= MoveTool(selectionAverage, handleRotation);
|
|
}
|
|
break;
|
|
|
|
case PathEditorTool.ScalePoints:
|
|
{
|
|
repaint |= ScaleTool(selectionAverage, handleRotation);
|
|
}
|
|
break;
|
|
|
|
case PathEditorTool.RotatePoints:
|
|
{
|
|
repaint |= RotateTool(selectionAverage, handleRotation);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return repaint;
|
|
}
|
|
|
|
protected bool MoveTool(Vector3 handlePosition, Quaternion handleRotation)
|
|
{
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 newPos = Handles.PositionHandle(handlePosition, handleRotation);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Move control point");
|
|
|
|
Vector3 delta = newPos - handlePosition;
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
wp.Transform(delta, Quaternion.identity, Vector3.one);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
|
|
path.FlushEvents();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected bool ScaleTool(Vector3 handlePosition, Quaternion handleRotation)
|
|
{
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 scale = Handles.ScaleHandle(prevScale, handlePosition, handleRotation, HandleUtility.GetHandleSize(handlePosition));
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Vector3 deltaScale = new Vector3(scale.x / prevScale.x, scale.y / prevScale.y, scale.z / prevScale.z);
|
|
prevScale = scale;
|
|
|
|
Undo.RecordObject(blueprint, "Scale control point");
|
|
|
|
if (Tools.pivotMode == PivotMode.Center && selectedCount > 1)
|
|
{
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
Vector3 newPos = handlePosition + Vector3.Scale(wp.position - handlePosition, deltaScale);
|
|
wp.Transform(newPos - wp.position, Quaternion.identity, Vector3.one);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Scale all handles of selected control points relative to their control point:
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
wp.Transform(Vector3.zero, Quaternion.identity, deltaScale);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
}
|
|
|
|
path.FlushEvents();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected bool RotateTool(Vector3 handlePosition, Quaternion handleRotation)
|
|
{
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
// TODO: investigate weird rotation gizmo:
|
|
Quaternion newRotation = Handles.RotationHandle(prevRot, handlePosition);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Quaternion delta = newRotation * Quaternion.Inverse(prevRot);
|
|
prevRot = newRotation;
|
|
|
|
Undo.RecordObject(blueprint, "Rotate control point");
|
|
|
|
if (Tools.pivotMode == PivotMode.Center && selectedCount > 1)
|
|
{
|
|
|
|
// Rotate all selected control points around their average:
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
Vector3 newPos = handlePosition + delta * (wp.position - handlePosition);
|
|
wp.Transform(newPos - wp.position, Quaternion.identity, Vector3.one);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
// Rotate all handles of selected control points around their control point:
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
wp.Transform(Vector3.zero, delta, Vector3.one);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
}
|
|
|
|
path.FlushEvents();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected bool OrientTool(Vector3 averagePos, Quaternion pivotRotation)
|
|
{
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Quaternion newRotation = Handles.RotationHandle(prevRot, averagePos);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Quaternion delta = newRotation * Quaternion.Inverse(prevRot);
|
|
prevRot = newRotation;
|
|
|
|
Undo.RecordObject(blueprint, "Orient control point");
|
|
|
|
// Rotate all selected control points around their average:
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
path.normals[i] = delta * path.normals[i];
|
|
}
|
|
}
|
|
|
|
path.FlushEvents();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
protected bool DoThicknessHandles(float scale)
|
|
{
|
|
Color oldColor = Handles.color;
|
|
Handles.color = handleColor;
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
Vector3 position = path.points[i].position;
|
|
|
|
var tangent = path.points.GetTangent(i);
|
|
if (!tangent.Equals(Vector3.zero))
|
|
{
|
|
Quaternion orientation = Quaternion.LookRotation(tangent);
|
|
|
|
float offset = 0.05f;
|
|
float thickness = (path.thicknesses[i] * scale) + offset;
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
thickness = DoRadiusHandle(orientation, position, thickness);
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Change control point thickness");
|
|
path.thicknesses[i] = Mathf.Max(0, (thickness - offset) / scale);
|
|
path.FlushEvents();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Handles.color = oldColor;
|
|
|
|
return false;
|
|
}
|
|
|
|
public void DrawToolPanel()
|
|
{
|
|
|
|
DrawToolButtons();
|
|
|
|
DrawControlPointInspector();
|
|
|
|
}
|
|
|
|
private void DrawToolButtons()
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.TranslatePoints, new GUIContent(Resources.Load<Texture2D>("TranslateControlPoint"), "Translate CPs"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.TranslatePoints;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.RotatePoints, new GUIContent(Resources.Load<Texture2D>("RotateControlPoint"), "Rotate CPs"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.RotatePoints;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.ScalePoints, new GUIContent(Resources.Load<Texture2D>("ScaleControlPoint"), "Scale CPs"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.ScalePoints;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.InsertPoints, new GUIContent(Resources.Load<Texture2D>("AddControlPoint"), "Add CPs"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.InsertPoints;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.RemovePoints, new GUIContent(Resources.Load<Texture2D>("RemoveControlPoint"), "Remove CPs"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.RemovePoints;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
bool closed = GUILayout.Toggle(path.Closed, new GUIContent(Resources.Load<Texture2D>("OpenCloseCurve"), "Open/Close the path"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Open/close path");
|
|
path.Closed = closed;
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
if (useOrientation)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
GUILayout.Toggle(currentTool == PathEditorTool.OrientPoints, new GUIContent(Resources.Load<Texture2D>("OrientControlPoint"), "Orientation tool"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
currentTool = PathEditorTool.OrientPoints;
|
|
}
|
|
}
|
|
|
|
showTangentHandles = GUILayout.Toggle(showTangentHandles, new GUIContent(Resources.Load<Texture2D>("ShowTangentHandles"), "Show tangent handles"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
showThicknessHandles = GUILayout.Toggle(showThicknessHandles, new GUIContent(Resources.Load<Texture2D>("ShowThicknessHandles"), "Show thickness handles"), "Button", GUILayout.MaxHeight(24), GUILayout.Width(38));
|
|
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void DrawPositionField(Rect rect, string label, int index)
|
|
{
|
|
EditorGUI.showMixedValue = false;
|
|
float pos = 0;
|
|
bool firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
pos = path.points[i].position[index];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(pos,path.points[i].position[index]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
|
EditorGUIUtility.labelWidth = 10;
|
|
pos = EditorGUI.FloatField(rect, label, pos);
|
|
EditorGUIUtility.labelWidth = oldLabelWidth;
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control points position");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
wp.position[index] = pos;
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
}
|
|
|
|
private void DrawInTangentField(Rect rect, string label, int index)
|
|
{
|
|
EditorGUI.showMixedValue = false;
|
|
float pos = 0;
|
|
bool firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
pos = path.points[i].inTangent[index];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(pos, path.points[i].inTangent[index]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
|
EditorGUIUtility.labelWidth = 10;
|
|
pos = EditorGUI.FloatField(rect, label, pos);
|
|
EditorGUIUtility.labelWidth = oldLabelWidth;
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control points tangent");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
var newInTangent = wp.inTangent;
|
|
newInTangent[index] = pos;
|
|
wp.SetInTangent(newInTangent);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
}
|
|
|
|
private void DrawOutTangentField(Rect rect, string label, int index)
|
|
{
|
|
EditorGUI.showMixedValue = false;
|
|
float pos = 0;
|
|
bool firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
pos = path.points[i].outTangent[index];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(pos, path.points[i].outTangent[index]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
|
EditorGUIUtility.labelWidth = 10;
|
|
pos = EditorGUI.FloatField(rect, label, pos);
|
|
EditorGUIUtility.labelWidth = oldLabelWidth;
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control points tangent");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
var newOutTangent = wp.outTangent;
|
|
newOutTangent[index] = pos;
|
|
wp.SetOutTangent(newOutTangent);
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
}
|
|
|
|
private void DrawControlPointInspector()
|
|
{
|
|
|
|
GUI.enabled = selectedCount > 0;
|
|
|
|
bool wideMode = EditorGUIUtility.wideMode;
|
|
EditorGUIUtility.wideMode = true;
|
|
EditorGUIUtility.labelWidth = 100;
|
|
|
|
EditorGUILayout.BeginVertical();
|
|
|
|
GUILayout.Box("", ObiEditorUtils.GetSeparatorLineStyle());
|
|
|
|
// position:
|
|
var rect = EditorGUILayout.GetControlRect();
|
|
rect = EditorGUI.PrefixLabel(rect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Position"));
|
|
rect.width /= 3.0f;
|
|
DrawPositionField(rect,"X",0); rect.x += rect.width;
|
|
DrawPositionField(rect,"Y",1); rect.x += rect.width;
|
|
DrawPositionField(rect,"Z",2); rect.x += rect.width;
|
|
|
|
// in tangent:
|
|
rect = EditorGUILayout.GetControlRect();
|
|
rect = EditorGUI.PrefixLabel(rect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("In Tangent"));
|
|
rect.width /= 3.0f;
|
|
DrawInTangentField(rect, "X", 0); rect.x += rect.width;
|
|
DrawInTangentField(rect, "Y", 1); rect.x += rect.width;
|
|
DrawInTangentField(rect, "Z", 2); rect.x += rect.width;
|
|
|
|
// out tangent:
|
|
rect = EditorGUILayout.GetControlRect();
|
|
rect = EditorGUI.PrefixLabel(rect, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Out Tangent"));
|
|
rect.width /= 3.0f;
|
|
DrawOutTangentField(rect, "X", 0); rect.x += rect.width;
|
|
DrawOutTangentField(rect, "Y", 1); rect.x += rect.width;
|
|
DrawOutTangentField(rect, "Z", 2); rect.x += rect.width;
|
|
|
|
// tangent mode:
|
|
EditorGUI.showMixedValue = false;
|
|
var mode = ObiWingedPoint.TangentMode.Free;
|
|
bool firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
mode = path.points[i].tangentMode;
|
|
firstSelected = false;
|
|
}
|
|
else if (mode != path.points[i].tangentMode)
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var newMode = (ObiWingedPoint.TangentMode)EditorGUILayout.EnumPopup("Tangent mode", mode, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control points mode");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
var wp = path.points[i];
|
|
wp.tangentMode = newMode;
|
|
path.points[i] = wp;
|
|
}
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
// thickness:
|
|
EditorGUI.showMixedValue = false;
|
|
float thickness = 0;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
thickness = path.thicknesses[i];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(thickness, path.thicknesses[i]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
thickness = EditorGUILayout.FloatField("Thickness", thickness, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control point thickness");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.thicknesses[i] = Mathf.Max(0, thickness);
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
// mass:
|
|
EditorGUI.showMixedValue = false;
|
|
float mass = 0;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
mass = path.masses[i];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(mass, path.masses[i]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
mass = EditorGUILayout.FloatField("Mass", mass, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control point mass");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.masses[i] = mass;
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
if (useOrientation)
|
|
{
|
|
// rotational mass:
|
|
EditorGUI.showMixedValue = false;
|
|
float rotationalMass = 0;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
rotationalMass = path.rotationalMasses[i];
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(rotationalMass, path.rotationalMasses[i]))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
rotationalMass = EditorGUILayout.FloatField("Rotational mass", rotationalMass, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control point rotational mass");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.rotationalMasses[i] = rotationalMass;
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
}
|
|
|
|
// category:
|
|
EditorGUI.showMixedValue = false;
|
|
int category = 0;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
category = ObiUtils.GetCategoryFromFilter(path.filters[i]);
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(category, ObiUtils.GetCategoryFromFilter(path.filters[i])))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
category = EditorGUILayout.Popup("Category", category, ObiUtils.categoryNames, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Change control point category");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.filters[i] = ObiUtils.MakeFilter(ObiUtils.GetMaskFromFilter(path.filters[i]),category);
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
// mask:
|
|
EditorGUI.showMixedValue = false;
|
|
int mask = 0;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
mask = ObiUtils.GetMaskFromFilter(path.filters[i]);
|
|
firstSelected = false;
|
|
}
|
|
else if (!Mathf.Approximately(mask, ObiUtils.GetMaskFromFilter(path.filters[i])))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
mask = EditorGUILayout.MaskField("Collides with", mask, ObiUtils.categoryNames, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
Undo.RecordObject(blueprint, "Change control point mask");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.filters[i] = ObiUtils.MakeFilter(mask,ObiUtils.GetCategoryFromFilter(path.filters[i]));
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
// color:
|
|
EditorGUI.showMixedValue = false;
|
|
Color color = Color.white;
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
color = path.colors[i];
|
|
firstSelected = false;
|
|
}
|
|
else if (color != path.colors[i])
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
color = EditorGUILayout.ColorField(new GUIContent("Color"), color, true, true, true, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control point color");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.colors[i] = color;
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
// name:
|
|
EditorGUI.showMixedValue = false;
|
|
string cpname = "";
|
|
firstSelected = true;
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
{
|
|
if (firstSelected)
|
|
{
|
|
cpname = path.GetName(i);
|
|
firstSelected = false;
|
|
}
|
|
else if (cpname != path.GetName(i))
|
|
{
|
|
EditorGUI.showMixedValue = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
cpname = EditorGUILayout.DelayedTextField("Name", cpname, GUILayout.MinWidth(94));
|
|
EditorGUI.showMixedValue = false;
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
|
|
Undo.RecordObject(blueprint, "Change control point name");
|
|
|
|
for (int i = 0; i < path.ControlPointCount; ++i)
|
|
{
|
|
if (selectedStatus[i])
|
|
path.SetName(i, cpname);
|
|
}
|
|
path.FlushEvents();
|
|
needsRepaint = true;
|
|
}
|
|
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
EditorGUIUtility.wideMode = wideMode;
|
|
|
|
GUI.enabled = true;
|
|
}
|
|
|
|
internal static float DoRadiusHandle(Quaternion rotation, Vector3 position, float radius)
|
|
{
|
|
Vector3[] vector3Array;
|
|
|
|
Vector3 camToPosition;
|
|
if (Camera.current.orthographic)
|
|
{
|
|
camToPosition = Camera.current.transform.forward;
|
|
Handles.DrawWireDisc(position, camToPosition, radius);
|
|
|
|
vector3Array = new Vector3[4]
|
|
{
|
|
Camera.current.transform.right,
|
|
Camera.current.transform.up,
|
|
-Camera.current.transform.right,
|
|
-Camera.current.transform.up,
|
|
};
|
|
|
|
}
|
|
else
|
|
{
|
|
camToPosition = position - Camera.current.transform.position;
|
|
Handles.DrawWireDisc(position, rotation * Vector3.forward, radius);
|
|
|
|
vector3Array = new Vector3[4]
|
|
{
|
|
rotation * Vector3.right,
|
|
rotation * Vector3.up,
|
|
rotation * -Vector3.right,
|
|
rotation * -Vector3.up,
|
|
};
|
|
}
|
|
|
|
for (int index = 0; index < 4; ++index)
|
|
{
|
|
int controlId = GUIUtility.GetControlID("ObiPathThicknessHandle".GetHashCode(), FocusType.Passive);
|
|
Vector3 position1 = position + radius * vector3Array[index];
|
|
bool changed = GUI.changed;
|
|
GUI.changed = false;
|
|
Vector3 a = Handles.Slider(controlId, position1, vector3Array[index], HandleUtility.GetHandleSize(position1) * 0.03f, Handles.DotHandleCap, 0.0f);
|
|
if (GUI.changed)
|
|
radius = Vector3.Distance(a, position);
|
|
GUI.changed |= changed;
|
|
}
|
|
|
|
return radius;
|
|
}
|
|
|
|
public static float ScreenPointToCurveMu(ObiPath path, Vector2 screenPoint, Matrix4x4 referenceFrame, int samples = 30)
|
|
{
|
|
|
|
if (path.ControlPointCount >= 2)
|
|
{
|
|
|
|
samples = Mathf.Max(1, samples);
|
|
float step = 1 / (float)samples;
|
|
|
|
float closestMu = 0;
|
|
float minDistance = float.MaxValue;
|
|
|
|
for (int k = 0; k < path.GetSpanCount(); ++k)
|
|
{
|
|
int nextCP = (k + 1) % path.ControlPointCount;
|
|
|
|
var wp1 = path.points[k];
|
|
var wp2 = path.points[nextCP];
|
|
|
|
Vector3 _p = referenceFrame.MultiplyPoint3x4(wp1.position);
|
|
Vector3 p = referenceFrame.MultiplyPoint3x4(wp1.outTangentEndpoint);
|
|
Vector3 p_ = referenceFrame.MultiplyPoint3x4(wp2.inTangentEndpoint);
|
|
Vector3 p__ = referenceFrame.MultiplyPoint3x4(wp2.position);
|
|
|
|
Vector2 lastPoint = HandleUtility.WorldToGUIPoint(path.m_Points.Evaluate(_p, p, p_, p__, 0));
|
|
for (int i = 1; i <= samples; ++i)
|
|
{
|
|
|
|
Vector2 currentPoint = HandleUtility.WorldToGUIPoint(path.m_Points.Evaluate(_p, p, p_, p__, i * step));
|
|
|
|
float mu;
|
|
float distance = Vector2.SqrMagnitude((Vector2)ObiUtils.ProjectPointLine(lastPoint, currentPoint, screenPoint, out mu) - screenPoint);
|
|
|
|
if (distance < minDistance)
|
|
{
|
|
minDistance = distance;
|
|
closestMu = (k + (i - 1) * step + mu / samples) / (float)path.GetSpanCount();
|
|
}
|
|
lastPoint = currentPoint;
|
|
}
|
|
|
|
}
|
|
|
|
return closestMu;
|
|
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("Curve needs at least 2 control points to be defined.");
|
|
}
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
} |