Skip to content

Commit 480f429

Browse files
committed
In Quick Edit Mode, it's now possible to move points along a specified plane (e.g. XY) (closed #26)
1 parent dd2eb59 commit 480f429

File tree

3 files changed

+118
-77
lines changed

3 files changed

+118
-77
lines changed

Plugins/BezierSolution/Editor/BezierSettings.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using UnityEditor;
1+
using System.Reflection;
2+
using UnityEditor;
23
using UnityEngine;
34

45
namespace BezierSolution.Extras
@@ -370,6 +371,23 @@ public static bool MoveMultiplePointsInOppositeDirections
370371
}
371372
}
372373

374+
private static QuickEditModePointPlacement? m_quickEditPointPlacement = null;
375+
public static QuickEditModePointPlacement QuickEditPointPlacement
376+
{
377+
get
378+
{
379+
if( m_quickEditPointPlacement == null )
380+
m_quickEditPointPlacement = (QuickEditModePointPlacement) EditorPrefs.GetInt( "BezierSolution_QuickEditPointPlacement", (int) QuickEditModePointPlacement.SceneGeometry );
381+
382+
return m_quickEditPointPlacement.Value;
383+
}
384+
set
385+
{
386+
m_quickEditPointPlacement = value;
387+
EditorPrefs.SetInt( "BezierSolution_QuickEditPointPlacement", (int) value );
388+
}
389+
}
390+
373391
private static bool? m_quickEditSplineModifyNormals = null;
374392
public static bool QuickEditSplineModifyNormals
375393
{

Plugins/BezierSolution/Editor/BezierUtils.cs

Lines changed: 98 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System;
22
using UnityEngine;
33
using UnityEditor;
4-
using Object = UnityEngine.Object;
54
using System.Reflection;
5+
using Object = UnityEngine.Object;
66

77
namespace BezierSolution.Extras
88
{
9+
public enum QuickEditModePointPlacement { SceneGeometry = 0, CameraPlane = 1, XY = 2, XZ = 3, YZ = 4 };
10+
911
public static class BezierUtils
1012
{
1113
private const string PRECEDING_CONTROL_POINT_LABEL = " <--";
@@ -30,7 +32,8 @@ public static class BezierUtils
3032
private static readonly GUIContent AUTO_CONSTRUCT_SPLINE_2_TEXT = new GUIContent( "Auto Construct Spline 2", "Constructs a smooth path (another algorithm)" );
3133
private static readonly GUIContent AUTO_CALCULATE_NORMALS_TEXT = new GUIContent( "Auto Calculate Normals", "Attempts to automatically calculate the end points' normal vectors" );
3234
private static readonly GUIContent AUTO_CONSTRUCT_ALWAYS_TEXT = new GUIContent( "Always", "Applies this method automatically as spline's points change" );
33-
private static readonly GUIContent QUICK_EDIT_MODE_TEXT = new GUIContent( "Quick Edit Mode", "Quickly add new points to the spline or snap existing points to the scene geometry" );
35+
private static readonly GUIContent QUICK_EDIT_MODE_TEXT = new GUIContent( "Quick Edit Mode", "Quickly add new points to the spline or move the existing points" );
36+
private static readonly GUIContent QUICK_EDIT_POINT_PLACEMENT_MODE_TEXT = new GUIContent( "Point Placement Mode", "Determines where the dragged or newly created points will be placed:\n- Scene Geometry: the scene geometry under the cursor\n- Camera Plane: Scene camera's local XY plane\n- XY: XY plane\n- XZ: XZ plane\n- YZ: YZ plane" );
3437
private static readonly GUIContent QUICK_EDIT_MODIFY_NORMALS_TEXT = new GUIContent( "Use Raycast Normals", "While dragging a point or adding a new point, the point's Normal vector will be set to the normal of the scene geometry under the cursor" );
3538
private static readonly GUIContent QUICK_EDIT_PRESERVE_SPLINE_SHAPE_TEXT = new GUIContent( "Preserve Spline Shape", "While inserting new points along the spline, the spline's shape will be preserved but the neighboring end points' 'Handle Mode' will no longer be 'Mirrored'" );
3639

@@ -515,21 +518,23 @@ public static void DrawSplineInspectorGUI( BezierSpline[] splines )
515518

516519
if( QuickEditSplineMode )
517520
{
518-
EditorGUILayout.HelpBox( "- Dragging a point: snaps the dragged point to the scene geometry under the cursor\n- CTRL+Left Click: adds a new point to the end of the spline\n- CTRL+Shift+Left Click: inserts a new point along the spline\n- Shift+Left Click: deletes clicked point", MessageType.Info );
521+
EditorGUILayout.HelpBox( "- Dragging a point: moves the dragged point\n- CTRL+Left Click: adds a new point to the end of the spline\n- CTRL+Shift+Left Click: inserts a new point along the spline\n- Shift+Left Click: deletes clicked point", MessageType.Info );
519522

520-
if( Array.Find( splines, ( s ) => !s.autoCalculateNormals ) )
523+
EditorGUI.indentLevel++;
524+
525+
BezierSettings.QuickEditPointPlacement = (QuickEditModePointPlacement) EditorGUILayout.EnumPopup( QUICK_EDIT_POINT_PLACEMENT_MODE_TEXT, BezierSettings.QuickEditPointPlacement );
526+
527+
if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && Array.Find( splines, ( s ) => !s.autoCalculateNormals ) )
521528
{
522529
EditorGUI.indentLevel++;
523530
BezierSettings.QuickEditSplineModifyNormals = EditorGUILayout.Toggle( QUICK_EDIT_MODIFY_NORMALS_TEXT, BezierSettings.QuickEditSplineModifyNormals );
524531
EditorGUI.indentLevel--;
525532
}
526533

527534
if( Array.Find( splines, ( s ) => s.autoConstructMode == SplineAutoConstructMode.None ) )
528-
{
529-
EditorGUI.indentLevel++;
530535
BezierSettings.QuickEditSplinePreserveShape = EditorGUILayout.Toggle( QUICK_EDIT_PRESERVE_SPLINE_SHAPE_TEXT, BezierSettings.QuickEditSplinePreserveShape );
531-
EditorGUI.indentLevel--;
532-
}
536+
537+
EditorGUI.indentLevel--;
533538
}
534539
}
535540

@@ -559,13 +564,14 @@ public static void DrawBezierPoint( BezierPoint point, int pointIndex, bool isSe
559564
{
560565
// Point is dragged, snap it to the scene geometry
561566
Vector3 sceneHitPoint, sceneHitNormal;
562-
RaycastAgainstScene( point.spline[point.spline.Count - 1], out sceneHitPoint, out sceneHitNormal );
563-
564-
Undo.RecordObject( point.transform, "Move point" );
565-
point.transform.position = sceneHitPoint;
567+
if( RaycastAgainstScene( point.spline[point.spline.Count - 1], out sceneHitPoint, out sceneHitNormal ) )
568+
{
569+
Undo.RecordObject( point.transform, "Move point" );
570+
point.transform.position = sceneHitPoint;
566571

567-
if( BezierSettings.QuickEditSplineModifyNormals && !point.spline.autoCalculateNormals )
568-
point.SetNormalAndResetIntermediateNormals( sceneHitNormal, "Move point" );
572+
if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && BezierSettings.QuickEditSplineModifyNormals && !point.spline.autoCalculateNormals )
573+
point.SetNormalAndResetIntermediateNormals( sceneHitNormal, "Move point" );
574+
}
569575
}
570576
}
571577
else
@@ -745,34 +751,35 @@ public static void QuickEditModeSceneGUI( BezierSpline[] splines )
745751
if( closestEndPoint )
746752
{
747753
Vector3 sceneHitPoint, sceneHitNormal;
748-
RaycastAgainstScene( closestEndPoint, out sceneHitPoint, out sceneHitNormal );
749-
750-
// Draw a line from the closest end point to the raycast hit point
751-
Color c = Handles.color;
752-
Handles.color = BezierSettings.QuickEditModeNewEndPointColor;
753-
Handles.DotHandleCap( 0, sceneHitPoint, Quaternion.identity, HandleUtility.GetHandleSize( sceneHitPoint ) * BezierSettings.QuickEditModeNewEndPointSize, EventType.Repaint );
754-
Handles.DrawLine( sceneHitPoint, closestEndPoint.position );
755-
Handles.color = c;
756-
757-
// When left clicked, insert a point at the highlighted position
758-
if( e.type == EventType.MouseDown && e.button == 0 )
754+
if( RaycastAgainstScene( closestEndPoint, out sceneHitPoint, out sceneHitNormal ) )
759755
{
760-
BezierPoint newPoint = closestEndPoint.spline.InsertNewPointAt( closestEndPoint.spline.Count );
761-
newPoint.position = sceneHitPoint;
762-
if( BezierSettings.QuickEditSplineModifyNormals && !closestEndPoint.spline.autoCalculateNormals )
763-
newPoint.SetNormalAndResetIntermediateNormals( sceneHitNormal, null );
764-
765-
// Rotate the previous point's followingControlPointPosition in the direction of the new point and assign the resulting vector
766-
// to the new point's followingControlPointPosition
767-
Vector3 directionToNewPoint = sceneHitPoint - closestEndPoint.position;
768-
Quaternion controlPointDeltaRotation = Quaternion.FromToRotation( closestEndPoint.followingControlPointPosition - closestEndPoint.position, directionToNewPoint );
769-
newPoint.followingControlPointPosition = sceneHitPoint + controlPointDeltaRotation * ( directionToNewPoint * 0.35f );
770-
771-
Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Insert Point" );
772-
if( newPoint.transform.parent )
773-
Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Insert Point" );
774-
775-
e.Use();
756+
// Draw a line from the closest end point to the raycast hit point
757+
Color c = Handles.color;
758+
Handles.color = BezierSettings.QuickEditModeNewEndPointColor;
759+
Handles.DotHandleCap( 0, sceneHitPoint, Quaternion.identity, HandleUtility.GetHandleSize( sceneHitPoint ) * BezierSettings.QuickEditModeNewEndPointSize, EventType.Repaint );
760+
Handles.DrawLine( sceneHitPoint, closestEndPoint.position );
761+
Handles.color = c;
762+
763+
// When left clicked, insert a point at the highlighted position
764+
if( e.type == EventType.MouseDown && e.button == 0 )
765+
{
766+
BezierPoint newPoint = closestEndPoint.spline.InsertNewPointAt( closestEndPoint.spline.Count );
767+
newPoint.position = sceneHitPoint;
768+
if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry && BezierSettings.QuickEditSplineModifyNormals && !closestEndPoint.spline.autoCalculateNormals )
769+
newPoint.SetNormalAndResetIntermediateNormals( sceneHitNormal, null );
770+
771+
// Rotate the previous point's followingControlPointPosition in the direction of the new point and assign the resulting vector
772+
// to the new point's followingControlPointPosition
773+
Vector3 directionToNewPoint = sceneHitPoint - closestEndPoint.position;
774+
Quaternion controlPointDeltaRotation = Quaternion.FromToRotation( closestEndPoint.followingControlPointPosition - closestEndPoint.position, directionToNewPoint );
775+
newPoint.followingControlPointPosition = sceneHitPoint + controlPointDeltaRotation * ( directionToNewPoint * 0.35f );
776+
777+
Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Insert Point" );
778+
if( newPoint.transform.parent )
779+
Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Insert Point" );
780+
781+
e.Use();
782+
}
776783
}
777784
}
778785
}
@@ -906,71 +913,87 @@ internal static void SetSplineDirtyWithUndo( BezierSpline spline, string undo, I
906913
spline.CheckDirty();
907914
}
908915

909-
private static void RaycastAgainstScene( BezierPoint referencePoint, out Vector3 position, out Vector3 normal )
916+
private static bool RaycastAgainstScene( BezierPoint referencePoint, out Vector3 position, out Vector3 normal )
910917
{
911918
EventType eventType = Event.current.type;
912919
Ray ray = HandleUtility.GUIPointToWorldRay( Event.current.mousePosition );
913920

914-
// First, try raycasting against scene geometry with or without colliders (it doesn't matter)
915-
// Credit: https://forum.unity.com/threads/editor-raycast-against-scene-meshes-without-collider-editor-select-object-using-gui-coordinate.485502
916-
if( intersectRayMeshMethod != null && eventType != EventType.Layout && eventType != EventType.Repaint ) // HandleUtility.PickGameObject doesn't work with Layout and Repaint events in OnSceneGUI
921+
if( BezierSettings.QuickEditPointPlacement == QuickEditModePointPlacement.SceneGeometry )
917922
{
918-
GameObject gameObjectUnderCursor = HandleUtility.PickGameObject( Event.current.mousePosition, false );
919-
if( gameObjectUnderCursor )
923+
// First, try raycasting against scene geometry with or without colliders (it doesn't matter)
924+
// Credit: https://forum.unity.com/threads/editor-raycast-against-scene-meshes-without-collider-editor-select-object-using-gui-coordinate.485502
925+
if( intersectRayMeshMethod != null && eventType != EventType.Layout && eventType != EventType.Repaint ) // HandleUtility.PickGameObject doesn't work with Layout and Repaint events in OnSceneGUI
920926
{
921-
Mesh meshUnderCursor = null;
922-
MeshFilter meshFilter = gameObjectUnderCursor.GetComponent<MeshFilter>();
923-
if( meshFilter )
924-
meshUnderCursor = meshFilter.sharedMesh;
925-
926-
if( !meshUnderCursor )
927+
GameObject gameObjectUnderCursor = HandleUtility.PickGameObject( Event.current.mousePosition, false );
928+
if( gameObjectUnderCursor )
927929
{
928-
SkinnedMeshRenderer skinnedMeshRenderer = gameObjectUnderCursor.GetComponent<SkinnedMeshRenderer>();
929-
if( skinnedMeshRenderer )
930-
meshUnderCursor = skinnedMeshRenderer.sharedMesh;
931-
}
930+
Mesh meshUnderCursor = null;
931+
MeshFilter meshFilter = gameObjectUnderCursor.GetComponent<MeshFilter>();
932+
if( meshFilter )
933+
meshUnderCursor = meshFilter.sharedMesh;
932934

933-
if( meshUnderCursor )
934-
{
935-
object[] rayMeshParameters = new object[] { ray, meshUnderCursor, gameObjectUnderCursor.transform.localToWorldMatrix, null };
936-
if( (bool) intersectRayMeshMethod.Invoke( null, rayMeshParameters ) )
935+
if( !meshUnderCursor )
936+
{
937+
SkinnedMeshRenderer skinnedMeshRenderer = gameObjectUnderCursor.GetComponent<SkinnedMeshRenderer>();
938+
if( skinnedMeshRenderer )
939+
meshUnderCursor = skinnedMeshRenderer.sharedMesh;
940+
}
941+
942+
if( meshUnderCursor )
937943
{
938-
RaycastHit hit = (RaycastHit) rayMeshParameters[3];
939-
position = hit.point;
940-
normal = hit.normal.normalized;
944+
object[] rayMeshParameters = new object[] { ray, meshUnderCursor, gameObjectUnderCursor.transform.localToWorldMatrix, null };
945+
if( (bool) intersectRayMeshMethod.Invoke( null, rayMeshParameters ) )
946+
{
947+
RaycastHit hit = (RaycastHit) rayMeshParameters[3];
948+
position = hit.point;
949+
normal = hit.normal.normalized;
941950

942-
return;
951+
return true;
952+
}
943953
}
944954
}
945955
}
946-
}
947956

948-
// Raycast against scene geometry with colliders
949-
object raycastResult = HandleUtility.RaySnap( ray );
950-
if( raycastResult != null && raycastResult is RaycastHit )
951-
{
952-
position = ( (RaycastHit) raycastResult ).point;
953-
normal = ( (RaycastHit) raycastResult ).normal.normalized;
957+
// Raycast against scene geometry with colliders
958+
object raycastResult = HandleUtility.RaySnap( ray );
959+
if( raycastResult != null && raycastResult is RaycastHit )
960+
{
961+
position = ( (RaycastHit) raycastResult ).point;
962+
normal = ( (RaycastHit) raycastResult ).normal.normalized;
954963

955-
return;
964+
return true;
965+
}
956966
}
957967

958968
// Raycast against a plane that goes through referencePoint
959969
if( referencePoint )
960970
{
961-
Plane plane = new Plane( referencePoint.normal, referencePoint.position );
971+
Vector3 planeNormal;
972+
switch( BezierSettings.QuickEditPointPlacement )
973+
{
974+
case QuickEditModePointPlacement.SceneGeometry: planeNormal = ( referencePoint.normal != Vector3.zero ) ? referencePoint.normal : Vector3.up; break;
975+
case QuickEditModePointPlacement.CameraPlane: planeNormal = SceneView.lastActiveSceneView.camera.transform.forward; break;
976+
case QuickEditModePointPlacement.XY: planeNormal = Vector3.forward; break;
977+
case QuickEditModePointPlacement.XZ: planeNormal = Vector3.up; break;
978+
case QuickEditModePointPlacement.YZ: planeNormal = Vector3.right; break;
979+
default: planeNormal = Vector3.up; break;
980+
}
981+
982+
Plane plane = new Plane( planeNormal, referencePoint.position );
962983
float enter;
963984
if( plane.Raycast( ray, out enter ) )
964985
{
965986
position = ray.GetPoint( enter );
966987
normal = referencePoint.normal;
967988

968-
return;
989+
return true;
969990
}
970991
}
971992

972993
position = ray.GetPoint( 5f );
973994
normal = Vector3.up;
995+
996+
return false;
974997
}
975998

976999
public static void DrawSeparator()

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "com.yasirkula.beziersolution",
33
"displayName": "Bezier Solution",
4-
"version": "2.3.0",
4+
"version": "2.3.1",
55
"documentationUrl": "https://github.com/yasirkula/UnityBezierSolution",
66
"changelogUrl": "https://github.com/yasirkula/UnityBezierSolution/releases",
77
"licensesUrl": "https://github.com/yasirkula/UnityBezierSolution/blob/master/LICENSE.txt",

0 commit comments

Comments
 (0)