WuhuIslandTesting/Library/PackageCache/com.unity.splines@1.0.1/Editor/Tools/KnotPlacementTool.cs
2025-01-07 02:06:59 +01:00

364 lines
15 KiB
C#

using System.Collections.Generic;
using UnityEditor.EditorTools;
using UnityEditor.ShortcutManagement;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Mathematics;
#if !UNITY_2020_2_OR_NEWER
using ToolManager = UnityEditor.EditorTools.EditorTools;
#endif
namespace UnityEditor.Splines
{
[EditorTool("Place Spline Knots", typeof(ISplineProvider), typeof(SplineToolContext))]
sealed class KnotPlacementTool : SplineTool
{
public override bool gridSnapEnabled => true;
enum State
{
KnotPlacement,
TangentPlacement,
SplineClosure
}
const string k_DistanceAboveSurfacePrefKey = "KnotPlacementTool_DistanceAboveSurface";
static float? s_DistanceAboveSurface;
public override GUIContent toolbarIcon => PathIcons.knotPlacementTool;
internal override SplineHandlesOptions handlesOptions
{
get
{
switch (m_State)
{
case State.SplineClosure:
case State.TangentPlacement:
return SplineHandlesOptions.ShowTangents;
default:
return SplineHandlesOptions.KnotInsert | SplineHandlesOptions.ShowTangents;
}
}
}
readonly List<IEditableSpline> m_Splines = new List<IEditableSpline>();
int m_ActiveSplineIndex;
State m_State;
Vector3 m_LastSurfacePoint;
Vector3 m_LastSurfaceNormal;
Vector3 m_CustomTangentOut;
Plane m_KnotPlane;
int m_StartId;
int m_EndId;
int m_AddKnotId;
int m_ClosingKnotId;
public override void OnActivated()
{
base.OnActivated();
m_State = State.KnotPlacement;
SplineToolContext.UseCustomSplineHandles(true);
Selection.selectionChanged -= OnSelectionChanged;
Undo.undoRedoPerformed -= OnSelectionChanged;
}
public override void OnWillBeDeactivated()
{
base.OnWillBeDeactivated();
SplineToolContext.UseCustomSplineHandles(false);
Selection.selectionChanged -= OnSelectionChanged;
Undo.undoRedoPerformed -= OnSelectionChanged;
}
void OnSelectionChanged()
{
m_ActiveSplineIndex = -1;
}
public override void OnToolGUI(EditorWindow window)
{
Event evt = Event.current;
GetSelectedSplines(m_Splines);
var spline = GetActiveSpline();
m_StartId = GUIUtility.GetControlID(FocusType.Passive);
m_AddKnotId = GUIUtility.GetControlID(FocusType.Passive);
m_ClosingKnotId = GUIUtility.GetControlID(FocusType.Passive);
var nearestControlIsSpline = HandleUtility.nearestControl == m_AddKnotId
|| HandleUtility.nearestControl == m_ClosingKnotId
//If the spline is closed and the nearest control is not one that is define after in the splines
|| spline.closed && (HandleUtility.nearestControl < m_StartId || HandleUtility.nearestControl > m_EndId);
var isMouseInWindow = new Rect(Vector2.zero, window.position.size).Contains(Event.current.mousePosition);
bool canCloseActiveSpline = false;
for(int i = 0; i < m_Splines.Count; ++i)
{
var active = SplineHandles.DrawSplineHandles(
m_Splines[i],
handlesOptions,
i == m_ActiveSplineIndex && nearestControlIsSpline || !isMouseInWindow);
if(active)
canCloseActiveSpline = m_Splines[i] == spline;
}
m_EndId = GUIUtility.GetControlID(FocusType.Passive);
DoClosingKnot(spline, canCloseActiveSpline);
if (!spline.closed)
DoKnotSurfaceAddHandle(m_AddKnotId, spline, isMouseInWindow);
if(spline.closed && evt.type == EventType.MouseMove)
HandleUtility.Repaint();
SplineConversionUtility.ApplyEditableSplinesIfDirty(targets);
if (evt.type == EventType.KeyDown && (evt.keyCode == KeyCode.Escape || evt.keyCode == KeyCode.Return))
{
ClearTangentPlacementData();
ToolManager.SetActiveTool<SplineMoveTool>();
}
}
void ClearTangentPlacementData()
{
m_State = State.KnotPlacement;
m_LastSurfacePoint = Vector3.zero;
m_LastSurfaceNormal = Vector3.zero;
m_CustomTangentOut = Vector3.zero;
}
void DoClosingKnot(IEditableSpline spline, bool active)
{
if (m_State != State.TangentPlacement && spline.knotCount >= 3 && spline.canBeClosed && !spline.closed)
{
if (HandleUtility.nearestControl == m_ClosingKnotId || m_State == State.SplineClosure)
{
Event evt = Event.current;
switch (evt.GetTypeForControl(m_ClosingKnotId))
{
case EventType.Repaint:
if (!Tools.viewToolActive)
{
var firstKnot = spline.GetKnot(0);
if (firstKnot.tangentCount > 1)
{
var tangentOut = firstKnot.GetTangent(1).direction;
DrawPreviewCurveForNewEndKnot(spline, firstKnot.position, tangentOut, m_LastSurfaceNormal, m_ClosingKnotId, true);
}
else
{
var tangentOut = float3.zero;
DrawPreviewCurveForNewEndKnot(spline, firstKnot.position, tangentOut, m_LastSurfaceNormal, m_ClosingKnotId, true);
}
}
break;
case EventType.MouseDown:
m_State = State.SplineClosure;
break;
}
}
EditableKnot knot = spline.GetKnot(0);
if (SplineHandles.ButtonHandle(m_ClosingKnotId, knot, active))
{
EditableSplineUtility.CloseSpline(spline);
m_State = State.KnotPlacement;
}
}
}
void DoKnotSurfaceAddHandle(int controlID, IEditableSpline spline, bool isMouseInWindow)
{
if (spline == null)
return;
Event evt = Event.current;
switch (evt.GetTypeForControl(controlID))
{
case EventType.Layout:
if (!Tools.viewToolActive)
HandleUtility.AddDefaultControl(controlID);
break;
case EventType.Repaint:
if (HandleUtility.nearestControl == controlID && !Tools.viewToolActive)
{
// Draw curve preview if we're placing tangents. Otherwise, draw it only if the cursor's ray is intersecting with a surface.
if(m_State == State.TangentPlacement ||
( m_State == State.KnotPlacement &&
isMouseInWindow &&
SplineHandleUtility.GetPointOnSurfaces(evt.mousePosition, out m_LastSurfacePoint, out m_LastSurfaceNormal) ))
{
//todo enable this after PR lands
//#if UNITY_2022_2_OR_NEWER
//if(EditorSnapSettings.incrementalSnapActive)
// m_LastSurfacePoint = SplineHandleUtility.DoIncrementSnap(m_LastSurfacePoint, spline.GetKnot(spline.knotCount - 1).position);
//#endif
DrawPreviewCurveForNewEndKnot(spline, m_LastSurfacePoint, m_CustomTangentOut, m_LastSurfaceNormal, controlID);
}
}
break;
case EventType.MouseDown:
if (HandleUtility.nearestControl == controlID && GUIUtility.hotControl == 0 && evt.button == 0)
{
GUIUtility.hotControl = controlID;
evt.Use();
if (SplineHandleUtility.GetPointOnSurfaces(evt.mousePosition, out m_LastSurfacePoint, out m_LastSurfaceNormal))
{
//todo enable this after PR lands
//#if UNITY_2022_2_OR_NEWER
// if(EditorSnapSettings.incrementalSnapActive)
// m_LastSurfacePoint = SplineHandleUtility.DoIncrementSnap(m_LastSurfacePoint, spline.GetKnot(spline.knotCount - 1).position);
//#endif
m_KnotPlane = new Plane(m_LastSurfaceNormal, m_LastSurfacePoint);
if (spline.tangentsPerKnot > 0)
m_State = State.TangentPlacement;
}
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
evt.Use();
if (evt.button == 0)
{
GUIUtility.hotControl = 0;
if (SplineHandleUtility.GetPointOnSurfaces(evt.mousePosition, out Vector3 _, out Vector3 _))
{
m_LastSurfacePoint = SplineHandleUtility.RoundBasedOnMinimumDifference(m_LastSurfacePoint);
//todo enable this after PR lands
//#if UNITY_2022_2_OR_NEWER
// if(EditorSnapSettings.incrementalSnapActive)
// m_LastSurfacePoint = SplineHandleUtility.DoIncrementSnap(m_LastSurfacePoint, spline.GetKnot(spline.knotCount - 1).position);
//#endif
// Check component count to ensure that we only move the transform of a newly created
// spline. I.e., we don't want to move a GameObject that has other components like
// a MeshRenderer, for example.
if (spline.knotCount < 1
&& spline.conversionTarget is Component component
&& component.gameObject.GetComponents<Component>().Length == 2)
component.transform.position = m_LastSurfacePoint;
EditableSplineUtility.AddPointToEnd(spline, m_LastSurfacePoint, m_LastSurfaceNormal, m_CustomTangentOut);
}
ClearTangentPlacementData();
}
}
break;
case EventType.MouseMove:
if (HandleUtility.nearestControl == controlID)
HandleUtility.Repaint();
break;
case EventType.MouseDrag:
if (m_State == State.TangentPlacement && GUIUtility.hotControl == controlID && evt.button == 0)
{
evt.Use();
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
if (m_KnotPlane.Raycast(ray, out float distance))
m_CustomTangentOut = (ray.origin + ray.direction * distance) - m_LastSurfacePoint;
}
break;
}
}
void DrawPreviewCurveForNewEndKnot(IEditableSpline spline, float3 point, float3 tangentOut, float3 normal, int knotHandleId, bool isClosingCurve = false)
{
var previewCurve = spline.GetPreviewCurveForEndKnot(point, normal, tangentOut);
if (spline.knotCount > 0)
SplineHandles.CurveHandleCap(previewCurve, -1, EventType.Repaint, m_State != State.TangentPlacement);
if (!isClosingCurve)
{
for(int i = 0; i < previewCurve.b.tangentCount; ++i)
{
var tangent = previewCurve.b.GetTangent(i);
if(math.length(tangent.localPosition) > 0)
SplineHandles.DrawTangentHandle(tangent);
}
// In addition, display the normally hidden tangent out of the last knot.
// It gives an impression of an issue when it's hidden but the preview knot shows both tangents.
if (spline.knotCount > 0 && previewCurve.a.tangentCount > 0)
SplineHandles.DrawTangentHandle(previewCurve.a.GetTangent(previewCurve.a.tangentCount - 1));
SplineHandles.DrawKnotHandle(m_LastSurfacePoint, previewCurve.b.rotation, false, knotHandleId);
}
else
{
var firstKnot = spline.GetKnot(0);
if (firstKnot.tangentCount > 1)
{
SplineHandles.DrawTangentHandle(firstKnot.GetTangent(0));
if (spline.knotCount > 0 && previewCurve.a.tangentCount > 0)
SplineHandles.DrawTangentHandle(previewCurve.a.GetTangent(previewCurve.a.tangentCount - 1));
}
}
}
void GetSelectedSplines(List<IEditableSpline> results)
{
results.Clear();
foreach (var t in targets)
{
IReadOnlyList<IEditableSpline> paths = EditableSplineManager.GetEditableSplines(t);
if (paths == null)
continue;
for (int i = 0; i < paths.Count; ++i)
{
results.AddRange(paths);
}
}
}
void CycleActiveSpline()
{
m_ActiveSplineIndex = (m_ActiveSplineIndex + 1) % m_Splines.Count;
SceneView.RepaintAll();
}
protected override IEditableSpline GetActiveSpline()
{
IEditableSpline spline;
if(m_ActiveSplineIndex == -1)
{
spline = base.GetActiveSpline();
m_ActiveSplineIndex = m_Splines.IndexOf(spline);
}
else
spline = m_Splines[m_ActiveSplineIndex];
return spline;
}
[Shortcut("Splines/Cycle Active Spline", typeof(SceneView), KeyCode.S)]
static void ShortcutCycleActiveSpline(ShortcutArguments args)
{
if(m_ActiveTool is KnotPlacementTool tool)
tool.CycleActiveSpline();
}
}
}