initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,364 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02595a50b334fc44ebcc04de58a0587d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,69 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineMoveTool))]
|
||||
class SplineMoveToolSettings : SplineToolSettings { }
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
[EditorTool("Spline Move Tool", typeof(ISplineProvider), typeof(SplineToolContext))]
|
||||
#else
|
||||
[EditorTool("Spline Move Tool", typeof(ISplineProvider))]
|
||||
#endif
|
||||
sealed class SplineMoveTool : SplineTool
|
||||
{
|
||||
public override bool gridSnapEnabled
|
||||
{
|
||||
get => handleOrientation == HandleOrientation.Global;
|
||||
}
|
||||
|
||||
public override GUIContent toolbarIcon => PathIcons.splineMoveTool;
|
||||
|
||||
internal override SplineHandlesOptions handlesOptions => SplineHandlesOptions.ManipulationDefault;
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
if (handleOrientation == HandleOrientation.Element || handleOrientation == HandleOrientation.Parent)
|
||||
TransformOperation.pivotFreeze |= TransformOperation.PivotFreeze.Rotation;
|
||||
|
||||
// In rotation sync center mode, pivot has to be allowed to move away
|
||||
// from the selection center. Therefore we freeze pivot's position
|
||||
// and force the position later on based on handle's translation delta.
|
||||
if (Tools.pivotMode == PivotMode.Center)
|
||||
TransformOperation.pivotFreeze |= TransformOperation.PivotFreeze.Position;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.MouseUp)
|
||||
{
|
||||
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.None;
|
||||
TransformOperation.UpdatePivotPosition();
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
TransformOperation.UpdatePivotPosition();
|
||||
|
||||
if (TransformOperation.canManipulate)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var newPos = Handles.DoPositionHandle(TransformOperation.pivotPosition, TransformOperation.handleRotation);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
TransformOperation.ApplyTranslation(newPos - TransformOperation.pivotPosition);
|
||||
|
||||
if (Tools.pivotMode == PivotMode.Center)
|
||||
TransformOperation.ForcePivotPosition(newPos);
|
||||
}
|
||||
}
|
||||
|
||||
SplineConversionUtility.ApplyEditableSplinesIfDirty(targets);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 44205bfb94ccacc4ea6744b8bfc057d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,62 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineRotateTool))]
|
||||
class SplineRotateToolSettings : SplineToolSettings { }
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
[EditorTool("Spline Rotate", typeof(ISplineProvider), typeof(SplineToolContext))]
|
||||
#else
|
||||
[EditorTool("Spline Rotate", typeof(ISplineProvider))]
|
||||
#endif
|
||||
sealed class SplineRotateTool : SplineTool
|
||||
{
|
||||
public override GUIContent toolbarIcon => PathIcons.splineRotateTool;
|
||||
|
||||
internal override SplineHandlesOptions handlesOptions => SplineHandlesOptions.ManipulationDefault;
|
||||
|
||||
Quaternion m_CurrentRotation = Quaternion.identity;
|
||||
Vector3 m_RotationCenter = Vector3.zero;
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
if (handleOrientation == HandleOrientation.Element || handleOrientation == HandleOrientation.Parent)
|
||||
TransformOperation.pivotFreeze |= TransformOperation.PivotFreeze.Rotation;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.MouseUp)
|
||||
{
|
||||
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.None;
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
TransformOperation.UpdatePivotPosition(true);
|
||||
|
||||
if(TransformOperation.canManipulate)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var rotation = Handles.DoRotationHandle(m_CurrentRotation, m_RotationCenter);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
TransformOperation.ApplyRotation(rotation * Quaternion.Inverse(m_CurrentRotation), m_RotationCenter);
|
||||
m_CurrentRotation = rotation;
|
||||
}
|
||||
|
||||
if(GUIUtility.hotControl == 0)
|
||||
{
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
m_CurrentRotation = TransformOperation.handleRotation;
|
||||
m_RotationCenter = TransformOperation.pivotPosition;
|
||||
}
|
||||
}
|
||||
|
||||
SplineConversionUtility.ApplyEditableSplinesIfDirty(targets);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8b243853b51eeb14694e302b57cb8970
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,59 @@
|
|||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomEditor(typeof(SplineScaleTool))]
|
||||
class SplineScaleToolSettings : SplineToolSettings { }
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
[EditorTool("Spline Scale Tool", typeof(ISplineProvider), typeof(SplineToolContext))]
|
||||
#else
|
||||
[EditorTool("Spline Scale Tool", typeof(ISplineProvider))]
|
||||
#endif
|
||||
sealed class SplineScaleTool : SplineTool
|
||||
{
|
||||
public override GUIContent toolbarIcon => PathIcons.splineScaleTool;
|
||||
|
||||
internal override SplineHandlesOptions handlesOptions => SplineHandlesOptions.ManipulationDefault;
|
||||
|
||||
Vector3 m_currentScale = Vector3.one;
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
if (Event.current.type == EventType.MouseDrag)
|
||||
{
|
||||
if (handleOrientation == HandleOrientation.Element || handleOrientation == HandleOrientation.Parent)
|
||||
TransformOperation.pivotFreeze |= TransformOperation.PivotFreeze.Rotation;
|
||||
}
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
TransformOperation.UpdatePivotPosition(true);
|
||||
|
||||
if (Event.current.type == EventType.MouseDown)
|
||||
{
|
||||
TransformOperation.RecordMouseDownState();
|
||||
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.Position;
|
||||
}
|
||||
if(Event.current.type == EventType.MouseUp)
|
||||
{
|
||||
m_currentScale = Vector3.one;
|
||||
TransformOperation.ClearMouseDownState();
|
||||
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.None;
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
}
|
||||
|
||||
if(TransformOperation.canManipulate)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
m_currentScale = Handles.DoScaleHandle(m_currentScale, TransformOperation.pivotPosition,
|
||||
TransformOperation.handleRotation, HandleUtility.GetHandleSize(TransformOperation.pivotPosition));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
TransformOperation.ApplyScale(m_currentScale);
|
||||
}
|
||||
SplineConversionUtility.ApplyEditableSplinesIfDirty(targets);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1c640147ed7cf254190b8caf8fe0f6f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,323 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine.Splines;
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.Overlays;
|
||||
#else
|
||||
using System.Reflection;
|
||||
using UnityEditor.Toolbars;
|
||||
using UnityEngine.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how the handles are oriented.
|
||||
/// </summary>
|
||||
enum HandleOrientation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool handles are in the active object's rotation.
|
||||
/// </summary>
|
||||
Local = 0,
|
||||
/// <summary>
|
||||
/// Tool handles are in global rotation.
|
||||
/// </summary>
|
||||
Global = 1,
|
||||
/// <summary>
|
||||
/// Tool handles are in active element's parent's rotation.
|
||||
/// </summary>
|
||||
Parent = 2,
|
||||
/// <summary>
|
||||
/// Tool handles are in active element's rotation.
|
||||
/// </summary>
|
||||
Element = 3
|
||||
}
|
||||
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
abstract class SplineToolSettings : UnityEditor.Editor, ICreateToolbar
|
||||
#else
|
||||
abstract class SplineToolSettings : UnityEditor.Editor
|
||||
#endif
|
||||
{
|
||||
public virtual IEnumerable<string> toolbarElements
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "Tool Settings/Pivot Mode";
|
||||
yield return "Spline Tool Settings/Handle Rotation";
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
const string k_ElementClassName = "unity-editor-toolbar-element";
|
||||
const string k_StyleSheetsPath = "StyleSheets/Toolbars/";
|
||||
|
||||
static VisualElement CreateToolbar()
|
||||
{
|
||||
var target = new VisualElement();
|
||||
var path = k_StyleSheetsPath + "EditorToolbar";
|
||||
|
||||
var common = EditorGUIUtility.Load($"{path}Common.uss") as StyleSheet;
|
||||
if (common != null)
|
||||
target.styleSheets.Add(common);
|
||||
|
||||
var themeSpecificName = EditorGUIUtility.isProSkin ? "Dark" : "Light";
|
||||
var themeSpecific = EditorGUIUtility.Load($"{path}{themeSpecificName}.uss") as StyleSheet;
|
||||
if (themeSpecific != null)
|
||||
target.styleSheets.Add(themeSpecific);
|
||||
|
||||
target.AddToClassList("unity-toolbar-overlay");
|
||||
target.style.flexDirection = FlexDirection.Row;
|
||||
return target;
|
||||
}
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = CreateToolbar();
|
||||
|
||||
var elements = TypeCache.GetTypesWithAttribute(typeof(EditorToolbarElementAttribute));
|
||||
|
||||
foreach (var element in toolbarElements)
|
||||
{
|
||||
var type = elements.FirstOrDefault(x =>
|
||||
{
|
||||
var attrib = x.GetCustomAttribute<EditorToolbarElementAttribute>();
|
||||
return attrib != null && attrib.id == element;
|
||||
});
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
const BindingFlags flags = BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.CreateInstance;
|
||||
|
||||
var ve = (VisualElement)Activator.CreateInstance(type, flags, null, null, null, null);
|
||||
ve.AddToClassList(k_ElementClassName);
|
||||
root.Add(ve);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Failed creating toolbar element from ID \"{element}\".\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorToolbarUtility.SetupChildrenAsButtonStrip(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class from which all Spline tools inherit.
|
||||
/// Inherit SplineTool to author tools that behave like native spline tools. This class implements some common
|
||||
/// functionality and shortcuts specific to spline authoring.
|
||||
/// </summary>
|
||||
abstract class SplineTool : EditorTool
|
||||
{
|
||||
internal virtual SplineHandlesOptions handlesOptions => SplineHandlesOptions.None;
|
||||
|
||||
static UserSetting<HandleOrientation> m_HandleOrientation = new UserSetting<HandleOrientation>(PathSettings.instance, "SplineTool.HandleOrientation", HandleOrientation.Global, SettingsScope.User);
|
||||
|
||||
public static HandleOrientation handleOrientation
|
||||
{
|
||||
get => m_HandleOrientation;
|
||||
set
|
||||
{
|
||||
if (m_HandleOrientation != value)
|
||||
{
|
||||
m_HandleOrientation.SetValue(value, true);
|
||||
if(m_HandleOrientation == HandleOrientation.Local || m_HandleOrientation == HandleOrientation.Global)
|
||||
Tools.pivotRotation = (PivotRotation)m_HandleOrientation.value;
|
||||
else // If setting HandleOrientation to something else, then set the PivotRotation to global, done for GridSnapping button activation
|
||||
{
|
||||
Tools.pivotRotationChanged -= OnPivotRotationChanged;
|
||||
Tools.pivotRotation = PivotRotation.Local;
|
||||
Tools.pivotRotationChanged += OnPivotRotationChanged;
|
||||
}
|
||||
handleOrientationChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static event Action handleOrientationChanged;
|
||||
|
||||
// Workaround for lack of access to ShortcutContext. Use this to pass shortcut actions to tool instances.
|
||||
protected static SplineTool m_ActiveTool;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after this EditorTool becomes the active tool.
|
||||
/// </summary>
|
||||
public override void OnActivated()
|
||||
{
|
||||
SplineToolContext.SetHandlesOptions(handlesOptions);
|
||||
SplineSelection.changed += OnSplineSelectionChanged;
|
||||
Spline.afterSplineWasModified += AfterSplineWasModified;
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
Tools.pivotRotationChanged += OnPivotRotationChanged;
|
||||
Tools.pivotModeChanged += OnPivotModeChanged;
|
||||
TransformOperation.UpdateSelection(targets);
|
||||
handleOrientationChanged += OnHandleOrientationChanged;
|
||||
m_ActiveTool = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before this EditorTool stops being the active tool.
|
||||
/// </summary>
|
||||
public override void OnWillBeDeactivated()
|
||||
{
|
||||
SplineToolContext.SetHandlesOptions(SplineHandlesOptions.None);
|
||||
SplineSelection.changed -= OnSplineSelectionChanged;
|
||||
Spline.afterSplineWasModified -= AfterSplineWasModified;
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
Tools.pivotRotationChanged -= OnPivotRotationChanged;
|
||||
Tools.pivotModeChanged -= OnPivotModeChanged;
|
||||
handleOrientationChanged -= OnHandleOrientationChanged;
|
||||
m_ActiveTool = null;
|
||||
}
|
||||
|
||||
protected virtual void OnHandleOrientationChanged()
|
||||
{
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
}
|
||||
|
||||
static void OnPivotRotationChanged()
|
||||
{
|
||||
handleOrientation = (HandleOrientation)Tools.pivotRotation;
|
||||
}
|
||||
|
||||
protected virtual void OnPivotModeChanged()
|
||||
{
|
||||
TransformOperation.UpdatePivotPosition();
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
}
|
||||
|
||||
void AfterSplineWasModified(Spline spline) => UpdateSelection();
|
||||
void UndoRedoPerformed() => UpdateSelection();
|
||||
|
||||
void OnSplineSelectionChanged()
|
||||
{
|
||||
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.None;
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
TransformOperation.UpdatePivotPosition();
|
||||
|
||||
UpdateSelection();
|
||||
}
|
||||
|
||||
void UpdateSelection()
|
||||
{
|
||||
TransformOperation.UpdateSelection(targets);
|
||||
}
|
||||
|
||||
void CycleTangentMode()
|
||||
{
|
||||
var elementSelection = TransformOperation.elementSelection;
|
||||
foreach (var element in elementSelection)
|
||||
{
|
||||
if (element is EditableTangent tangent)
|
||||
{
|
||||
//Do nothing on the tangent if the knot is also in the selection
|
||||
if (elementSelection.Contains(tangent.owner))
|
||||
continue;
|
||||
|
||||
var oppositeTangentSelected = false;
|
||||
if (tangent.owner is BezierEditableKnot owner)
|
||||
{
|
||||
if(owner.TryGetOppositeTangent(tangent, out var oppositeTangent))
|
||||
{
|
||||
if (elementSelection.Contains(oppositeTangent))
|
||||
oppositeTangentSelected = true;
|
||||
}
|
||||
|
||||
if (!oppositeTangentSelected)
|
||||
{
|
||||
if (owner.mode == BezierEditableKnot.Mode.Broken)
|
||||
{
|
||||
// Mirror otherTangent against the active tangent prior to SetMode call.
|
||||
// As SetMode always mirrors tangentOut against tangentIn, this prevents an active selection's
|
||||
// tangentOut from shrinking or becoming zero tangent unexpectedly.
|
||||
for (int i = 0; i < owner.tangentCount; ++i)
|
||||
{
|
||||
var otherTangent = owner.GetTangent(i);
|
||||
if (otherTangent != tangent)
|
||||
otherTangent.SetLocalPositionNoNotify(-tangent.localPosition);
|
||||
}
|
||||
|
||||
owner.SetMode(BezierEditableKnot.Mode.Mirrored);
|
||||
}
|
||||
else if (owner.mode == BezierEditableKnot.Mode.Mirrored)
|
||||
owner.SetMode(BezierEditableKnot.Mode.Continuous);
|
||||
else if (owner.mode == BezierEditableKnot.Mode.Continuous)
|
||||
owner.SetMode(BezierEditableKnot.Mode.Broken);
|
||||
|
||||
owner.TangentChanged(tangent, owner.mode);
|
||||
TransformOperation.UpdateHandleRotation();
|
||||
|
||||
// Ensures the tangent mode indicators refresh
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the currently selected active spline.
|
||||
/// </summary>
|
||||
/// <returns>The active spline.</returns>
|
||||
protected virtual IEditableSpline GetActiveSpline()
|
||||
{
|
||||
IReadOnlyList<IEditableSpline> paths = EditableSplineUtility.GetSelectedSpline(target);
|
||||
if (paths == null || paths.Count == 0)
|
||||
return null;
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
[Shortcut("Splines/Cycle Tangent Mode", typeof(SceneView), KeyCode.C)]
|
||||
static void ShortcutCycleTangentMode(ShortcutArguments args)
|
||||
{
|
||||
if(m_ActiveTool != null)
|
||||
m_ActiveTool.CycleTangentMode();
|
||||
}
|
||||
|
||||
[Shortcut("Splines/Toggle Manipulation Space", typeof(SceneView), KeyCode.X)]
|
||||
static void ShortcutCycleHandleOrientation(ShortcutArguments args)
|
||||
{
|
||||
/* We're doing a switch here (instead of handleOrientation+1 and wrapping) because HandleOrientation.Global/Local values map
|
||||
to PivotRotation.Global/Local (as they should), but PivotRotation.Global = 1 when it's actually the first option and PivotRotation.Local = 0 when it's the second option. */
|
||||
switch (handleOrientation)
|
||||
{
|
||||
case HandleOrientation.Element:
|
||||
handleOrientation = HandleOrientation.Global;
|
||||
break;
|
||||
|
||||
case HandleOrientation.Global:
|
||||
handleOrientation = HandleOrientation.Local;
|
||||
break;
|
||||
|
||||
case HandleOrientation.Local:
|
||||
handleOrientation = HandleOrientation.Parent;
|
||||
break;
|
||||
|
||||
case HandleOrientation.Parent:
|
||||
handleOrientation = HandleOrientation.Element;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.LogError($"{handleOrientation} handle orientation not supported!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c913d3391dba90d4c8036f589722a8f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,242 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Flags]
|
||||
enum SplineHandlesOptions
|
||||
{
|
||||
ShowTangents = 1 << 0,
|
||||
SelectableKnots = 1 << 1,
|
||||
SelectableTangents = 1 << 2,
|
||||
KnotInsert = 1 << 3,
|
||||
|
||||
SelectableElements = SelectableKnots | SelectableTangents,
|
||||
None = 0,
|
||||
ManipulationDefault = ShowTangents | SelectableElements
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a tool context for editing splines. When authoring tools for splines, pass the SplineToolContext type
|
||||
/// to the EditorToolAttribute.editorToolContext parameter to register as a spline tool.
|
||||
/// </summary>
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
[EditorToolContext("Spline", typeof(ISplineProvider)), Icon(k_IconPath)]
|
||||
#else
|
||||
[EditorToolContext("Spline", typeof(ISplineProvider))]
|
||||
#endif
|
||||
public sealed class SplineToolContext : EditorToolContext
|
||||
{
|
||||
const string k_IconPath = "Packages/com.unity.splines/Editor/Resources/Icons/SplineContext.png";
|
||||
|
||||
static SplineHandlesOptions s_SplineHandlesOptions = SplineHandlesOptions.None;
|
||||
static bool s_UseCustomSplineHandles = false;
|
||||
static bool s_IsSplineTool = true;
|
||||
|
||||
readonly SplineElementRectSelector m_RectSelector = new SplineElementRectSelector();
|
||||
readonly List<IEditableSpline> m_Splines = new List<IEditableSpline>();
|
||||
readonly List<EditableKnot> m_KnotBuffer = new List<EditableKnot>();
|
||||
|
||||
bool m_WasActiveAfterDeserialize;
|
||||
|
||||
internal static void SetHandlesOptions(SplineHandlesOptions options)
|
||||
{
|
||||
s_SplineHandlesOptions = options;
|
||||
}
|
||||
|
||||
internal static void UseCustomSplineHandles(bool useCustomSplineHandle)
|
||||
{
|
||||
s_UseCustomSplineHandles = useCustomSplineHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the matching EditorTool type for the specified Tool given the context.
|
||||
/// </summary>
|
||||
/// <param name="tool">The Tool to resolve to an EditorTool type.</param>
|
||||
/// <returns> An EditorTool type for the requested Tool.</returns>
|
||||
protected override Type GetEditorToolType(Tool tool)
|
||||
{
|
||||
if (tool == Tool.Move)
|
||||
return typeof(SplineMoveTool);
|
||||
if (tool == Tool.Rotate)
|
||||
return typeof(SplineRotateTool);
|
||||
if (tool == Tool.Scale)
|
||||
return typeof(SplineScaleTool);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked for each window where this context is active. The spline context uses this method to implement
|
||||
/// common functionality for working with splines, ex gizmo drawing and selection.
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
if(!s_IsSplineTool)
|
||||
return;
|
||||
|
||||
m_RectSelector.OnGUI(m_Splines);
|
||||
|
||||
if(!s_UseCustomSplineHandles)
|
||||
SplineHandles.DrawSplineHandles(m_Splines, s_SplineHandlesOptions);
|
||||
|
||||
HandleSelectionFraming();
|
||||
HandleSelectAll();
|
||||
HandleDeleteSelectedKnots();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
AssemblyReloadEvents.afterAssemblyReload += OnAfterDomainReload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked after this EditorToolContext becomes the active tool context.
|
||||
/// </summary>
|
||||
public override void OnActivated()
|
||||
{
|
||||
// Sync handleOrientation to Tools.pivotRotation only if we're switching from a different context.
|
||||
// This ensures that Parent/Element handleOrientation is retained after domain reload.
|
||||
if(!m_WasActiveAfterDeserialize)
|
||||
SplineTool.handleOrientation = (HandleOrientation)Tools.pivotRotation;
|
||||
else
|
||||
m_WasActiveAfterDeserialize = false;
|
||||
|
||||
OnSelectionChanged();
|
||||
ToolManager.activeToolChanged += OnActiveToolChanged;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
Spline.afterSplineWasModified += OnSplineWasModified;
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
SplineConversionUtility.splinesUpdated += SceneView.RepaintAll;
|
||||
SplineConversionUtility.UpdateEditableSplinesForTargets(targets);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before this EditorToolContext stops being the active tool context.
|
||||
/// </summary>
|
||||
public override void OnWillBeDeactivated()
|
||||
{
|
||||
ToolManager.activeToolChanged -= OnActiveToolChanged;
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
Spline.afterSplineWasModified -= OnSplineWasModified;
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
SplineConversionUtility.splinesUpdated -= SceneView.RepaintAll;
|
||||
|
||||
EditableSplineManager.FreeEntireCache();
|
||||
SplineSelection.ClearNoUndo(false);
|
||||
}
|
||||
|
||||
void OnActiveToolChanged()
|
||||
{
|
||||
if(ToolManager.activeToolType == typeof(KnotPlacementTool))
|
||||
SplineSelection.Clear();
|
||||
|
||||
s_IsSplineTool = typeof(SplineTool).IsAssignableFrom(ToolManager.activeToolType);
|
||||
}
|
||||
|
||||
void OnSplineWasModified(Spline spline) => UpdateSelection();
|
||||
void OnSelectionChanged() => UpdateSelection();
|
||||
void UndoRedoPerformed() => UpdateSelection();
|
||||
|
||||
void UpdateSelection()
|
||||
{
|
||||
EditableSplineUtility.GetSelectedSplines(targets, m_Splines);
|
||||
EditableSplineManager.UpdateSelection(targets);
|
||||
SplineSelection.UpdateObjectSelection(targets);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void HandleDeleteSelectedKnots()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Delete)
|
||||
{
|
||||
SplineSelection.GetSelectedKnots(m_KnotBuffer);
|
||||
|
||||
//Sort knots index so removing them doesn't cause the rest of the indices to be invalid
|
||||
m_KnotBuffer.Sort((a, b) => a.index.CompareTo(b.index));
|
||||
for (int i = m_KnotBuffer.Count - 1; i >= 0; --i)
|
||||
{
|
||||
EditableKnot knot = m_KnotBuffer[i];
|
||||
knot.spline.RemoveKnotAt(knot.index);
|
||||
}
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleSelectionFraming()
|
||||
{
|
||||
if (TransformOperation.canManipulate)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (evt.commandName.Equals("FrameSelected"))
|
||||
{
|
||||
var execute = evt.type == EventType.ExecuteCommand;
|
||||
|
||||
if (evt.type == EventType.ValidateCommand || execute)
|
||||
{
|
||||
if (execute)
|
||||
{
|
||||
var selectionBounds = TransformOperation.GetSelectionBounds(false);
|
||||
selectionBounds.Encapsulate(TransformOperation.pivotPosition);
|
||||
|
||||
var size = selectionBounds.size;
|
||||
if (selectionBounds.size.x < 1f)
|
||||
size.x = 1f;
|
||||
if (selectionBounds.size.y < 1f)
|
||||
size.y = 1f;
|
||||
if (selectionBounds.size.z < 1f)
|
||||
size.z = 1f;
|
||||
selectionBounds.size = size;
|
||||
|
||||
SceneView.lastActiveSceneView.Frame(selectionBounds, false);
|
||||
}
|
||||
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleSelectAll()
|
||||
{
|
||||
Event evt = Event.current;
|
||||
if (evt.commandName.Equals("SelectAll"))
|
||||
{
|
||||
var execute = evt.type == EventType.ExecuteCommand;
|
||||
|
||||
if (evt.type == EventType.ValidateCommand || execute)
|
||||
{
|
||||
var allElements = new List<ISplineElement>();
|
||||
foreach (var spline in m_Splines)
|
||||
{
|
||||
for (int knotIdx = 0; knotIdx < spline.knotCount; ++knotIdx)
|
||||
{
|
||||
var knot = spline.GetKnot(knotIdx);
|
||||
allElements.Add(knot);
|
||||
for (int tangentIdx = 0; tangentIdx < knot.tangentCount; ++tangentIdx)
|
||||
{
|
||||
var tangent = knot.GetTangent(tangentIdx);
|
||||
if (SplineSelectionUtility.IsSelectable(spline, knotIdx, tangent))
|
||||
allElements.Add(tangent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SplineSelection.Add(allElements);
|
||||
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnAfterDomainReload()
|
||||
{
|
||||
m_WasActiveAfterDeserialize = ToolManager.activeContextType == typeof(SplineToolContext);
|
||||
AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: da047b2433894bf182b9e42abb971f5f
|
||||
timeCreated: 1618944106
|
|
@ -0,0 +1,535 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class TransformOperation
|
||||
{
|
||||
[Flags]
|
||||
public enum PivotFreeze
|
||||
{
|
||||
None = 0,
|
||||
Position = 1,
|
||||
Rotation = 2,
|
||||
All = Position | Rotation
|
||||
}
|
||||
|
||||
struct TransformData
|
||||
{
|
||||
internal float3 position;
|
||||
internal float3 inTangentDirection;
|
||||
internal float3 outTangentDirection;
|
||||
|
||||
internal static TransformData GetData(ISplineElement element)
|
||||
{
|
||||
var tData = new TransformData();
|
||||
tData.position = new float3(element.position);
|
||||
if (element is BezierEditableKnot knot)
|
||||
{
|
||||
tData.inTangentDirection = knot.tangentIn.direction;
|
||||
tData.outTangentDirection = knot.tangentOut.direction;
|
||||
}
|
||||
|
||||
return tData;
|
||||
}
|
||||
}
|
||||
|
||||
struct RotationSyncData
|
||||
{
|
||||
quaternion m_RotationDelta;
|
||||
float m_MagnitudeDelta;
|
||||
float m_ScaleMultiplier; // Only used for scale operation
|
||||
bool m_Initialized;
|
||||
|
||||
public bool initialized => m_Initialized;
|
||||
public quaternion rotationDelta => m_RotationDelta;
|
||||
public float magnitudeDelta => m_MagnitudeDelta;
|
||||
public float scaleMultiplier => m_ScaleMultiplier;
|
||||
|
||||
public void Initialize(quaternion rotationDelta, float magnitudeDelta, float scaleMultiplier)
|
||||
{
|
||||
m_RotationDelta = rotationDelta;
|
||||
m_MagnitudeDelta = magnitudeDelta;
|
||||
m_ScaleMultiplier = scaleMultiplier;
|
||||
m_Initialized = true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_RotationDelta = quaternion.identity;
|
||||
m_MagnitudeDelta = 0f;
|
||||
m_ScaleMultiplier = 1f;
|
||||
m_Initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static List<ISplineElement> s_ElementSelection = new List<ISplineElement>(32);
|
||||
|
||||
public static IReadOnlyList<ISplineElement> elementSelection => s_ElementSelection;
|
||||
|
||||
static int s_ElementSelectionCount = 0;
|
||||
|
||||
public static bool canManipulate => s_ElementSelectionCount > 0;
|
||||
|
||||
public static ISplineElement currentElementSelected
|
||||
=> canManipulate ? s_ElementSelection[0] : null;
|
||||
|
||||
static Vector3 s_PivotPosition;
|
||||
public static Vector3 pivotPosition => s_PivotPosition;
|
||||
|
||||
static quaternion s_HandleRotation;
|
||||
public static quaternion handleRotation => s_HandleRotation;
|
||||
|
||||
//Caching rotation inverse for rotate and scale operations
|
||||
static quaternion s_HandleRotationInv;
|
||||
|
||||
public static PivotFreeze pivotFreeze { get; set; }
|
||||
|
||||
static TransformData[] s_MouseDownData;
|
||||
|
||||
// Used to prevent same knot being rotated multiple times during a transform operation in Rotation Sync mode.
|
||||
static HashSet<EditableKnot> s_RotatedKnotCache = new HashSet<EditableKnot>();
|
||||
static RotationSyncData s_RotationSyncData = new RotationSyncData();
|
||||
|
||||
internal static void UpdateSelection(IEnumerable<Object> selection)
|
||||
{
|
||||
SplineSelection.GetSelectedElements(selection, s_ElementSelection);
|
||||
s_ElementSelectionCount = s_ElementSelection.Count;
|
||||
if (s_ElementSelectionCount > 0)
|
||||
{
|
||||
UpdatePivotPosition();
|
||||
UpdateHandleRotation();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UpdatePivotPosition(bool useKnotPositionForTangents = false)
|
||||
{
|
||||
if ((pivotFreeze & PivotFreeze.Position) != 0)
|
||||
return;
|
||||
|
||||
switch (Tools.pivotMode)
|
||||
{
|
||||
case PivotMode.Center:
|
||||
s_PivotPosition = EditableSplineUtility.GetBounds(s_ElementSelection, useKnotPositionForTangents).center;
|
||||
break;
|
||||
|
||||
case PivotMode.Pivot:
|
||||
if (s_ElementSelectionCount == 0)
|
||||
goto default;
|
||||
|
||||
var element = s_ElementSelection[0];
|
||||
if (useKnotPositionForTangents && element is EditableTangent tangent)
|
||||
s_PivotPosition = tangent.owner.position;
|
||||
else
|
||||
s_PivotPosition = element.position;
|
||||
break;
|
||||
|
||||
default:
|
||||
s_PivotPosition = Vector3.positiveInfinity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A way to set pivot position for situations, when by design, pivot position does
|
||||
// not necessarily match the pivot of selected elements.
|
||||
internal static void ForcePivotPosition(float3 position)
|
||||
{
|
||||
s_PivotPosition = position;
|
||||
}
|
||||
|
||||
internal static void UpdateHandleRotation()
|
||||
{
|
||||
if ((pivotFreeze & PivotFreeze.Rotation) != 0)
|
||||
return;
|
||||
|
||||
var handleRotation = Tools.handleRotation;
|
||||
if (canManipulate && (SplineTool.handleOrientation == HandleOrientation.Element || SplineTool.handleOrientation == HandleOrientation.Parent))
|
||||
{
|
||||
var curElement = TransformOperation.currentElementSelected;
|
||||
|
||||
if (SplineTool.handleOrientation == HandleOrientation.Element)
|
||||
handleRotation = CalculateElementSpaceHandleRotation(curElement);
|
||||
else if (curElement is EditableTangent editableTangent)
|
||||
handleRotation = CalculateElementSpaceHandleRotation(editableTangent.owner);
|
||||
}
|
||||
|
||||
s_HandleRotation = handleRotation;
|
||||
s_HandleRotationInv = math.inverse(s_HandleRotation);
|
||||
}
|
||||
|
||||
public static void ApplyTranslation(Vector3 delta)
|
||||
{
|
||||
s_RotatedKnotCache.Clear();
|
||||
|
||||
foreach (var element in s_ElementSelection)
|
||||
{
|
||||
if (element is EditableKnot knot)
|
||||
{
|
||||
knot.position += (float3)delta;
|
||||
if (!s_RotationSyncData.initialized)
|
||||
s_RotationSyncData.Initialize(quaternion.identity, 0f, 1f);
|
||||
}
|
||||
else if (element is EditableTangent tangent)
|
||||
{
|
||||
//Do nothing on the tangent if the knot is also in the selection
|
||||
if (s_ElementSelection.Contains(tangent.owner))
|
||||
continue;
|
||||
|
||||
if (tangent.owner is BezierEditableKnot owner)
|
||||
{
|
||||
if (OppositeTangentSelected(tangent))
|
||||
owner.SetMode(BezierEditableKnot.Mode.Broken);
|
||||
|
||||
if (owner.mode == BezierEditableKnot.Mode.Broken)
|
||||
tangent.position = tangent.owner.position + tangent.direction + (float3) delta;
|
||||
else
|
||||
{
|
||||
if (s_RotatedKnotCache.Contains(tangent.owner))
|
||||
continue;
|
||||
|
||||
if (tangent.owner is BezierEditableKnot tangentOwner)
|
||||
{
|
||||
var targetDirection = tangent.direction + (float3) delta;
|
||||
|
||||
// Build rotation sync data based on active selection's transformation
|
||||
if (!s_RotationSyncData.initialized)
|
||||
{
|
||||
var rotationDelta = Quaternion.FromToRotation(tangent.direction, targetDirection);
|
||||
var magnitudeDelta = math.length(targetDirection) - math.length(tangent.direction);
|
||||
|
||||
s_RotationSyncData.Initialize(rotationDelta, magnitudeDelta, 1f);
|
||||
}
|
||||
|
||||
ApplyTangentRotationSyncTransform(tangent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_RotationSyncData.Clear();
|
||||
}
|
||||
|
||||
public static void ApplyRotation(Quaternion deltaRotation, Vector3 rotationCenter)
|
||||
{
|
||||
s_RotatedKnotCache.Clear();
|
||||
|
||||
foreach (var element in s_ElementSelection)
|
||||
{
|
||||
if (element is EditableKnot knot)
|
||||
{
|
||||
var knotRotation = knot.rotation;
|
||||
RotateKnot(knot, deltaRotation, rotationCenter);
|
||||
if (!s_RotationSyncData.initialized)
|
||||
s_RotationSyncData.Initialize(math.mul(math.inverse(knotRotation), knot.rotation), 0f, 1f);
|
||||
}
|
||||
else if (element is EditableTangent tangent && !s_ElementSelection.Contains(tangent.owner))
|
||||
{
|
||||
if (tangent.owner is BezierEditableKnot tangentOwner)
|
||||
{
|
||||
if (tangentOwner.mode == BezierEditableKnot.Mode.Broken)
|
||||
{
|
||||
if (Tools.pivotMode == PivotMode.Pivot)
|
||||
rotationCenter = tangent.owner.position;
|
||||
|
||||
var mode = tangentOwner.mode;
|
||||
|
||||
var deltaPos = math.rotate(deltaRotation, tangent.position - (float3)rotationCenter);
|
||||
tangent.position = deltaPos + (float3)rotationCenter;
|
||||
|
||||
tangentOwner.TangentChanged(tangent, mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s_RotatedKnotCache.Contains(tangent.owner))
|
||||
continue;
|
||||
|
||||
deltaRotation.ToAngleAxis(out var deltaRotationAngle, out var deltaRotationAxis);
|
||||
|
||||
if (math.abs(deltaRotationAngle) > 0f)
|
||||
{
|
||||
if (tangentOwner.mode != BezierEditableKnot.Mode.Broken)
|
||||
{
|
||||
// If we're in center pivotMode and both tangents of the same knot are in selection, enter Broken mode under these conditions:
|
||||
if (Tools.pivotMode == PivotMode.Center && OppositeTangentSelected(tangent))
|
||||
{
|
||||
var knotToCenter = (float3) rotationCenter - tangentOwner.position;
|
||||
// 1) Rotation center does not match owner knot's position
|
||||
if (!Mathf.Approximately(math.length(knotToCenter), 0f))
|
||||
{
|
||||
var similarity = Math.Abs(Vector3.Dot(math.normalize(deltaRotationAxis), math.normalize(knotToCenter)));
|
||||
// 2) Both rotation center and knot, are not on rotation delta's axis
|
||||
if (!Mathf.Approximately(similarity, 1f))
|
||||
tangentOwner.SetMode(BezierEditableKnot.Mode.Broken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build rotation sync data based on active selection's transformation
|
||||
if (!s_RotationSyncData.initialized)
|
||||
{
|
||||
if (Tools.pivotMode == PivotMode.Pivot)
|
||||
s_RotationSyncData.Initialize(deltaRotation, 0f, 1f);
|
||||
else
|
||||
{
|
||||
var deltaPos = math.rotate(deltaRotation, tangent.position - (float3) rotationCenter);
|
||||
var knotToRotationCenter = (float3) rotationCenter - tangent.owner.position;
|
||||
var targetDirection = knotToRotationCenter + deltaPos;
|
||||
var tangentNorm = math.normalize(tangent.direction);
|
||||
var axisDotTangent = math.dot(math.normalize(deltaRotationAxis), tangentNorm);
|
||||
var toRotCenterDotTangent = math.length(knotToRotationCenter) > 0f ? math.dot(math.normalize(knotToRotationCenter), tangentNorm) : 1f;
|
||||
quaternion knotRotationDelta;
|
||||
// In center pivotMode, use handle delta only if our handle delta rotation's axis
|
||||
// matches knot's active selection tangent direction and rotation center is on the tangent's axis.
|
||||
// This makes knot roll possible when element selection list only contains one or both tangents of a single knot.
|
||||
if (Mathf.Approximately(math.abs(axisDotTangent), 1f) && Mathf.Approximately(math.abs(toRotCenterDotTangent), 1f))
|
||||
knotRotationDelta = deltaRotation;
|
||||
else
|
||||
knotRotationDelta = Quaternion.FromToRotation(tangent.direction, targetDirection);
|
||||
|
||||
var scaleMultiplier = math.length(targetDirection) / math.length(tangent.direction);
|
||||
|
||||
s_RotationSyncData.Initialize(knotRotationDelta, 0f, scaleMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyTangentRotationSyncTransform(tangent, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s_RotationSyncData.Clear();
|
||||
}
|
||||
|
||||
static bool OppositeTangentSelected(EditableTangent tangent)
|
||||
{
|
||||
if (tangent.owner is BezierEditableKnot tangentOwner && tangentOwner.mode != BezierEditableKnot.Mode.Broken)
|
||||
if (tangentOwner.TryGetOppositeTangent(tangent, out var oppositeTangent) && s_ElementSelection.Contains(oppositeTangent))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void RotateKnot(EditableKnot knot, quaternion deltaRotation, float3 rotationCenter, bool allowTranslation = true)
|
||||
{
|
||||
var knotInBrokenMode = (knot is BezierEditableKnot bezierKnot && bezierKnot.mode == BezierEditableKnot.Mode.Broken);
|
||||
if (!knotInBrokenMode && s_RotatedKnotCache.Contains(knot))
|
||||
return;
|
||||
|
||||
if (allowTranslation && Tools.pivotMode == PivotMode.Center)
|
||||
{
|
||||
var dir = knot.position - rotationCenter;
|
||||
|
||||
if (SplineTool.handleOrientation == HandleOrientation.Element || SplineTool.handleOrientation == HandleOrientation.Parent)
|
||||
knot.position = math.rotate(deltaRotation, dir) + rotationCenter;
|
||||
else
|
||||
knot.position = math.rotate(s_HandleRotation, math.rotate(deltaRotation, math.rotate(s_HandleRotationInv, dir))) + rotationCenter;
|
||||
}
|
||||
|
||||
if (SplineTool.handleOrientation == HandleOrientation.Element || SplineTool.handleOrientation == HandleOrientation.Parent)
|
||||
{
|
||||
if (Tools.pivotMode == PivotMode.Center)
|
||||
knot.rotation = math.mul(deltaRotation, knot.rotation);
|
||||
else
|
||||
{
|
||||
var handlePivotModeRot = math.mul(GetCurrentSelectionKnot().rotation, math.inverse(knot.rotation));
|
||||
knot.rotation = math.mul(math.inverse(handlePivotModeRot), math.mul(deltaRotation, math.mul(handlePivotModeRot, knot.rotation)));
|
||||
}
|
||||
}
|
||||
else
|
||||
knot.rotation = math.mul(s_HandleRotation, math.mul(deltaRotation, math.mul(s_HandleRotationInv, knot.rotation)));
|
||||
|
||||
s_RotatedKnotCache.Add(knot);
|
||||
}
|
||||
|
||||
public static void ApplyScale(float3 scale)
|
||||
{
|
||||
s_RotatedKnotCache.Clear();
|
||||
ISplineElement[] scaledElements = new ISplineElement[s_ElementSelectionCount];
|
||||
|
||||
for(int elementIndex = 0; elementIndex<s_ElementSelectionCount; elementIndex++)
|
||||
{
|
||||
var element = s_ElementSelection[elementIndex];
|
||||
if (element is EditableKnot knot)
|
||||
{
|
||||
ScaleKnot(knot, elementIndex, scale);
|
||||
|
||||
if (!s_RotationSyncData.initialized)
|
||||
s_RotationSyncData.Initialize(quaternion.identity, 0f, 1f);
|
||||
}
|
||||
else if(element is EditableTangent tangent && !s_ElementSelection.Contains(tangent.owner))
|
||||
{
|
||||
if(tangent.owner is BezierEditableKnot tangentOwner)
|
||||
{
|
||||
var restoreMode = false;
|
||||
var mode = tangentOwner.mode;
|
||||
var scaleDelta = scale - new float3(1f, 1f, 1f);
|
||||
if (tangentOwner.mode != BezierEditableKnot.Mode.Broken && math.length(scaleDelta) > 0f)
|
||||
{
|
||||
// If we're in center pivotMode and both tangents of the same knot are in selection
|
||||
if (Tools.pivotMode == PivotMode.Center && OppositeTangentSelected(tangent))
|
||||
{
|
||||
var knotToCenter = (float3)pivotPosition - tangentOwner.position;
|
||||
// Enter broken mode if scale operation center does not match owner knot's position
|
||||
if (!Mathf.Approximately(math.length(knotToCenter), 0f))
|
||||
{
|
||||
tangentOwner.SetMode(BezierEditableKnot.Mode.Broken);
|
||||
var similarity = Math.Abs(Vector3.Dot(math.normalize(scaleDelta), math.normalize(knotToCenter)));
|
||||
// If scale center and knot are both on an axis that's orthogonal to scale operation's axis,
|
||||
// mark knot for mode restore so that mirrored/continous modes can be restored
|
||||
if (Mathf.Approximately(similarity, 0f))
|
||||
restoreMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index = Array.IndexOf(scaledElements, element);
|
||||
if (index == -1) //element not scaled yet
|
||||
{
|
||||
if (tangentOwner.mode == BezierEditableKnot.Mode.Broken)
|
||||
tangent.position = ScaleTangent(tangent, s_MouseDownData[elementIndex].position, scale);
|
||||
else
|
||||
{
|
||||
// Build rotation sync data based on active selection's transformation
|
||||
if (!s_RotationSyncData.initialized)
|
||||
{
|
||||
var targetDirection = ScaleTangent(tangent, s_MouseDownData[elementIndex].position, scale) - tangent.owner.position;
|
||||
var rotationDelta = Quaternion.FromToRotation(tangent.direction, targetDirection);
|
||||
var scaleMultiplier = math.length(targetDirection) / math.length(tangent.direction);
|
||||
|
||||
s_RotationSyncData.Initialize(rotationDelta, 0f, scaleMultiplier);
|
||||
}
|
||||
|
||||
if (tangentOwner.mode == BezierEditableKnot.Mode.Mirrored && s_RotatedKnotCache.Contains(tangentOwner))
|
||||
continue;
|
||||
|
||||
ApplyTangentRotationSyncTransform(tangent, false);
|
||||
}
|
||||
|
||||
if (restoreMode)
|
||||
tangentOwner.SetMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
scaledElements[elementIndex] = element;
|
||||
}
|
||||
|
||||
s_RotationSyncData.Clear();
|
||||
}
|
||||
|
||||
static void ScaleKnot(EditableKnot knot, int dataIndex, float3 scale)
|
||||
{
|
||||
if(Tools.pivotMode == PivotMode.Center)
|
||||
{
|
||||
var deltaPos = math.rotate(s_HandleRotationInv ,s_MouseDownData[dataIndex].position - (float3) pivotPosition);
|
||||
var deltaPosKnot = deltaPos * scale;
|
||||
knot.position = math.rotate(s_HandleRotation, deltaPosKnot) + (float3)pivotPosition;
|
||||
}
|
||||
|
||||
using(new BezierEditableKnot.TangentSafeEditScope(knot))
|
||||
{
|
||||
if(knot is BezierEditableKnot bezierKnot)
|
||||
{
|
||||
var tangent = bezierKnot.tangentIn;
|
||||
tangent.direction = math.rotate(s_HandleRotation, math.rotate(s_HandleRotationInv,s_MouseDownData[dataIndex].inTangentDirection) * scale);
|
||||
tangent = bezierKnot.tangentOut;
|
||||
tangent.direction = math.rotate(s_HandleRotation, math.rotate(s_HandleRotationInv,s_MouseDownData[dataIndex].outTangentDirection) * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float3 ScaleTangent(EditableTangent tangent, float3 originalPosition, float3 scale)
|
||||
{
|
||||
var scaleCenter = Tools.pivotMode == PivotMode.Center ? (float3) pivotPosition : tangent.owner.position;
|
||||
|
||||
var deltaPos = math.rotate(s_HandleRotationInv, originalPosition - scaleCenter) * scale;
|
||||
return math.rotate(s_HandleRotation, deltaPos) + scaleCenter;
|
||||
}
|
||||
|
||||
static void ApplyTangentRotationSyncTransform(EditableTangent tangent, bool absoluteScale = true)
|
||||
{
|
||||
if (tangent.owner is BezierEditableKnot tangentOwner)
|
||||
{
|
||||
// Apply scale only if tangent is active selection or it's part of multi select and its knot is mirrored
|
||||
if (tangent == currentElementSelected ||
|
||||
tangentOwner.mode == BezierEditableKnot.Mode.Mirrored ||
|
||||
(!absoluteScale && tangentOwner.mode == BezierEditableKnot.Mode.Continuous))
|
||||
{
|
||||
if (absoluteScale)
|
||||
tangent.direction += math.normalize(tangent.direction) * s_RotationSyncData.magnitudeDelta;
|
||||
else
|
||||
tangent.direction *= s_RotationSyncData.scaleMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
RotateKnot(tangent.owner, s_RotationSyncData.rotationDelta, tangent.owner.position, false);
|
||||
}
|
||||
|
||||
internal static quaternion CalculateElementSpaceHandleRotation(ISplineElement element)
|
||||
{
|
||||
quaternion handleRotation = quaternion.identity;
|
||||
if (element is EditableTangent editableTangent && editableTangent.owner is BezierEditableKnot tangentKnot)
|
||||
{
|
||||
float3 forward;
|
||||
var knotUp = math.rotate(tangentKnot.rotation, math.up());
|
||||
|
||||
if (math.length(editableTangent.direction) > 0)
|
||||
forward = math.normalize(editableTangent.direction);
|
||||
else // Treat zero length tangent same way as when it's parallel to knot's up vector
|
||||
forward = knotUp;
|
||||
|
||||
float3 right;
|
||||
var dotForwardKnotUp = math.dot(forward, knotUp);
|
||||
if (Mathf.Approximately(math.abs(dotForwardKnotUp), 1f))
|
||||
right = math.rotate(tangentKnot.rotation, math.right()) * math.sign(dotForwardKnotUp);
|
||||
else
|
||||
right = math.cross(forward, knotUp);
|
||||
|
||||
handleRotation = quaternion.LookRotationSafe(forward, math.cross(right, forward));
|
||||
}
|
||||
else if (element is EditableKnot editableKnot)
|
||||
handleRotation = editableKnot.rotation;
|
||||
|
||||
return handleRotation;
|
||||
}
|
||||
|
||||
static EditableKnot GetCurrentSelectionKnot()
|
||||
{
|
||||
if (currentElementSelected == null)
|
||||
return null;
|
||||
|
||||
if (currentElementSelected is EditableTangent tangent)
|
||||
return tangent.owner;
|
||||
|
||||
if (currentElementSelected is EditableKnot knot)
|
||||
return knot;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void RecordMouseDownState()
|
||||
{
|
||||
s_MouseDownData = new TransformData[s_ElementSelectionCount];
|
||||
for (int i = 0; i < s_ElementSelectionCount; i++)
|
||||
{
|
||||
s_MouseDownData[i] = TransformData.GetData(s_ElementSelection[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearMouseDownState()
|
||||
{
|
||||
s_MouseDownData = null;
|
||||
}
|
||||
|
||||
public static Bounds GetSelectionBounds(bool useKnotPositionForTangents = false)
|
||||
{
|
||||
return EditableSplineUtility.GetBounds(s_ElementSelection, useKnotPositionForTangents);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3f7710aed2e378c428337119fb9a1342
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue