_xiaofang/xiaofang/Assets/Obi/Editor/RopeAndRod/ObiPathEditor.cs
杨号敬 bcc74f0465 add
2024-12-18 02:18:45 +08:00

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;
}
}
}