1308 lines
43 KiB
C#
1308 lines
43 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
|
|
namespace BezierSolution.Extras
|
|
{
|
|
[CustomEditor( typeof( BezierPoint ) )]
|
|
[CanEditMultipleObjects]
|
|
public class BezierPointEditor : Editor
|
|
{
|
|
private enum PointInsertionMode { None = 0, Simple = 1, PreserveShape = 2 };
|
|
|
|
private class SplineHolder
|
|
{
|
|
public BezierSpline spline;
|
|
public BezierPoint[] points;
|
|
|
|
public SplineHolder( BezierSpline spline, BezierPoint[] points )
|
|
{
|
|
this.spline = spline;
|
|
this.points = points;
|
|
}
|
|
|
|
public void SortPoints( bool forwards )
|
|
{
|
|
if( forwards )
|
|
System.Array.Sort( points, CompareForwards );
|
|
else
|
|
System.Array.Sort( points, CompareBackwards );
|
|
}
|
|
|
|
private int CompareForwards( BezierPoint x, BezierPoint y )
|
|
{
|
|
return x.index.CompareTo( y.index );
|
|
}
|
|
|
|
private int CompareBackwards( BezierPoint x, BezierPoint y )
|
|
{
|
|
return y.index.CompareTo( x.index );
|
|
}
|
|
}
|
|
|
|
private const float CONTROL_POINTS_MINIMUM_SAFE_DISTANCE_SQR = 0.05f * 0.05f;
|
|
|
|
private static readonly Color RESET_POINT_BUTTON_COLOR = new Color( 1f, 1f, 0.65f, 1f );
|
|
private static readonly Color REMOVE_POINT_BUTTON_COLOR = new Color( 1f, 0.65f, 0.65f, 1f );
|
|
private static readonly GUIContent MULTI_EDIT_TIP = new GUIContent( "Tip: Hold Shift to affect all points' Transforms" );
|
|
private static readonly GUIContent OPPOSITE_TRANSFORMATION_OFF_TIP = new GUIContent( "(in THE SAME direction - hit C to toggle)" );
|
|
private static readonly GUIContent OPPOSITE_TRANSFORMATION_ON_TIP = new GUIContent( "(in OPPOSITE directions - hit C to toggle)" );
|
|
private static readonly GUIContent INSERT_POINT_PRESERVE_SHAPE = new GUIContent( "Preserve Shape", "Spline's shape will be preserved but the neighboring end points' 'Handle Mode' will no longer be 'Mirrored'" );
|
|
private static readonly GUIContent APPLY_TO_ALL_POINTS = new GUIContent( "All", "Apply to all points in the selected spline(s)" );
|
|
private static readonly GUIContent HANDLE_MODE_TIP = new GUIContent( "Handle Mode", "Control points are handled in one of 3 ways:\n-Free: allows moving control points independently\n-Mirrored: places the control points opposite to each other\n-Aligned: ensures that both control points are aligned on a line that passes through the end point (unlike Mirrored mode, their distance to end point may differ)" );
|
|
private static readonly GUIContent EXTRA_DATA_TIP = new GUIContent( "Extra Data", "Each end point can store additional data that holds 4 floats. You can interpolate between points' extra data via spline's GetExtraData function. For example, extra data can be used to assign a custom rotation value to each end point that can be read by BezierWalker components" );
|
|
private static readonly GUIContent NORMALS_SET_TO_CAMERA_FORWARD = new GUIContent( "C", "Set to Scene camera's forward direction" );
|
|
private static readonly GUIContent NORMALS_LOOK_AT_CAMERA = new GUIContent( "L", "Look towards Scene camera's current position" );
|
|
private static readonly GUIContent NORMALIZE_NORMALS_TIP = new GUIContent( "Normalize Normals", "Sets normal vectors' length to 1" );
|
|
private static readonly GUIContent EXTRA_DATA_SET_AS_CAMERA_FORWARD = new GUIContent( "C", "Set as Scene camera's current rotation" );
|
|
private static readonly GUIContent EXTRA_DATA_VIEW_AS_FRUSTUM = new GUIContent( "V", "Visualize data as camera frustum in Scene" );
|
|
|
|
private SplineHolder[] selection;
|
|
internal BezierSpline[] allSplines;
|
|
private BezierPoint[] allPoints;
|
|
private int pointCount;
|
|
|
|
private Vector3[] pointInitialPositions;
|
|
private Quaternion[] pointInitialRotations;
|
|
private Vector3[] pointInitialScales;
|
|
private Vector3[] precedingPointInitialPositions;
|
|
private Vector3[] followingPointInitialPositions;
|
|
private bool allPointsModified;
|
|
|
|
private Quaternion[] precedingPointRotations;
|
|
private Quaternion[] followingPointRotations;
|
|
private bool controlPointRotationsInitialized;
|
|
|
|
private Tool previousTool = Tool.None;
|
|
|
|
public static BezierPointEditor ActiveEditor { get; private set; }
|
|
|
|
private void OnEnable()
|
|
{
|
|
Object[] points = targets;
|
|
pointCount = points.Length;
|
|
allPoints = new BezierPoint[pointCount];
|
|
|
|
pointInitialPositions = new Vector3[pointCount];
|
|
pointInitialRotations = new Quaternion[pointCount];
|
|
pointInitialScales = new Vector3[pointCount];
|
|
precedingPointInitialPositions = new Vector3[pointCount];
|
|
followingPointInitialPositions = new Vector3[pointCount];
|
|
|
|
precedingPointRotations = new Quaternion[pointCount];
|
|
followingPointRotations = new Quaternion[pointCount];
|
|
controlPointRotationsInitialized = false;
|
|
|
|
if( pointCount == 1 )
|
|
{
|
|
BezierPoint point = (BezierPoint) points[0];
|
|
|
|
selection = new SplineHolder[1] { new SplineHolder( point.GetComponentInParent<BezierSpline>(), new BezierPoint[1] { point } ) };
|
|
allSplines = selection[0].spline ? new BezierSpline[1] { selection[0].spline } : new BezierSpline[0];
|
|
allPoints[0] = point;
|
|
}
|
|
else
|
|
{
|
|
Dictionary<BezierSpline, List<BezierPoint>> lookupTable = new Dictionary<BezierSpline, List<BezierPoint>>( pointCount );
|
|
List<BezierPoint> nullSplinePoints = null;
|
|
|
|
for( int i = 0; i < pointCount; i++ )
|
|
{
|
|
BezierPoint point = (BezierPoint) points[i];
|
|
BezierSpline spline = point.GetComponentInParent<BezierSpline>();
|
|
if( !spline )
|
|
{
|
|
spline = null;
|
|
|
|
if( nullSplinePoints == null )
|
|
nullSplinePoints = new List<BezierPoint>( pointCount );
|
|
|
|
nullSplinePoints.Add( point );
|
|
}
|
|
else
|
|
{
|
|
List<BezierPoint> _points;
|
|
if( !lookupTable.TryGetValue( spline, out _points ) )
|
|
{
|
|
_points = new List<BezierPoint>( pointCount );
|
|
lookupTable[spline] = _points;
|
|
}
|
|
|
|
_points.Add( point );
|
|
}
|
|
|
|
allPoints[i] = point;
|
|
}
|
|
|
|
int index;
|
|
if( nullSplinePoints != null )
|
|
{
|
|
index = 1;
|
|
selection = new SplineHolder[lookupTable.Count + 1];
|
|
selection[0] = new SplineHolder( null, nullSplinePoints.ToArray() );
|
|
}
|
|
else
|
|
{
|
|
index = 0;
|
|
selection = new SplineHolder[lookupTable.Count];
|
|
}
|
|
|
|
int index2 = 0;
|
|
allSplines = new BezierSpline[lookupTable.Count];
|
|
|
|
foreach( var element in lookupTable )
|
|
{
|
|
selection[index++] = new SplineHolder( element.Key, element.Value.ToArray() );
|
|
allSplines[index2++] = element.Key;
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
selection[i].SortPoints( true );
|
|
|
|
if( selection[i].spline )
|
|
selection[i].spline.Refresh();
|
|
}
|
|
|
|
ActiveEditor = this;
|
|
|
|
Tools.hidden = true;
|
|
|
|
if( BezierUtils.QuickEditSplineMode )
|
|
{
|
|
EditorApplication.update -= SceneView.RepaintAll;
|
|
EditorApplication.update += SceneView.RepaintAll;
|
|
}
|
|
|
|
Undo.undoRedoPerformed -= OnUndoRedo;
|
|
Undo.undoRedoPerformed += OnUndoRedo;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
ActiveEditor = null;
|
|
Tools.hidden = false;
|
|
|
|
Undo.undoRedoPerformed -= OnUndoRedo;
|
|
EditorApplication.update -= SceneView.RepaintAll;
|
|
}
|
|
|
|
private void OnSceneGUI()
|
|
{
|
|
BezierPoint point = (BezierPoint) target;
|
|
if( !point )
|
|
return;
|
|
|
|
if( CheckCommands() )
|
|
return;
|
|
|
|
Event e = Event.current;
|
|
Tool tool = Tools.current;
|
|
|
|
// OnSceneGUI is called separately for each selected point, make sure that the spline is drawn only once, not multiple times
|
|
if( point == allPoints[0] )
|
|
{
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
if( spline )
|
|
{
|
|
BezierPoint[] points = selection[i].points;
|
|
BezierUtils.DrawSplineDetailed( spline );
|
|
for( int j = 0, k = 0; j < spline.Count; j++ )
|
|
{
|
|
bool isSelected = spline[j] == points[k];
|
|
if( isSelected && k < points.Length - 1 )
|
|
k++;
|
|
|
|
if( !isSelected )
|
|
BezierUtils.DrawBezierPoint( spline[j], j + 1, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !BezierUtils.QuickEditSplineMode && allPoints.Length > 1 && ( tool == Tool.Move || tool == Tool.Rotate || tool == Tool.Scale ) )
|
|
{
|
|
GUIStyle style = "PreOverlayLabel"; // Taken from: https://github.com/Unity-Technologies/UnityCsReference/blob/f78f4093c8a2b45949a847cdc704cf209dcf2f36/Editor/Mono/EditorGUI.cs#L629
|
|
Rect multiEditTipRect = new Rect( Vector2.zero, style.CalcSize( MULTI_EDIT_TIP ) );
|
|
|
|
Handles.BeginGUI();
|
|
|
|
EditorGUI.DropShadowLabel( multiEditTipRect, MULTI_EDIT_TIP, style );
|
|
if( tool == Tool.Move || tool == Tool.Rotate )
|
|
{
|
|
Rect multiEditOppositeTransformationTipRect = new Rect( new Vector2( multiEditTipRect.width + 4f, 0f ), style.CalcSize( BezierSettings.MoveMultiplePointsInOppositeDirections ? OPPOSITE_TRANSFORMATION_ON_TIP : OPPOSITE_TRANSFORMATION_OFF_TIP ) );
|
|
EditorGUI.DropShadowLabel( multiEditOppositeTransformationTipRect, BezierSettings.MoveMultiplePointsInOppositeDirections ? OPPOSITE_TRANSFORMATION_ON_TIP : OPPOSITE_TRANSFORMATION_OFF_TIP, style );
|
|
}
|
|
|
|
Handles.EndGUI();
|
|
|
|
if( e.type == EventType.KeyUp && e.keyCode == KeyCode.C && ( tool == Tool.Move || tool == Tool.Rotate ) )
|
|
BezierSettings.MoveMultiplePointsInOppositeDirections = !BezierSettings.MoveMultiplePointsInOppositeDirections;
|
|
}
|
|
|
|
if( e.type == EventType.MouseDown && e.button == 0 )
|
|
{
|
|
// Cache initial Transform values of the points
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
BezierPoint p = allPoints[i];
|
|
|
|
pointInitialPositions[i] = p.position;
|
|
pointInitialRotations[i] = p.rotation;
|
|
pointInitialScales[i] = p.localScale;
|
|
precedingPointInitialPositions[i] = p.precedingControlPointPosition;
|
|
followingPointInitialPositions[i] = p.followingControlPointPosition;
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
}
|
|
|
|
// When Control key is pressed, BezierPoint gizmos should be drawn on top of Transform handles in order to allow selecting/deselecting points
|
|
// If Alt key is pressed, Transform handles aren't drawn at all, so BezierPoint gizmos can be drawn immediately
|
|
// When in point insertion mode, handles aren't drawn and BezierPoint gizmos must be drawn immediately
|
|
if( e.alt || !e.control || BezierUtils.QuickEditSplineMode )
|
|
BezierUtils.DrawBezierPoint( point, point.index + 1, true );
|
|
|
|
if( BezierSettings.ShowEvenlySpacedPoints )
|
|
{
|
|
if( point == allPoints[0] )
|
|
{
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
BezierUtils.DrawSplineEvenlySpacedPoints( allSplines[i] );
|
|
}
|
|
}
|
|
|
|
if( BezierUtils.QuickEditSplineMode )
|
|
{
|
|
// Execute quick edit mode's scene GUI only once (otherwise things can get ugly when multiple points are selected)
|
|
if( point == allPoints[0] )
|
|
{
|
|
BezierUtils.QuickEditModeSceneGUI( allSplines );
|
|
HandleUtility.AddDefaultControl( 0 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Camera rotates with Alt key, don't interfere
|
|
if( e.alt )
|
|
return;
|
|
|
|
int pointIndex = -1;
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( allPoints[i] == point )
|
|
{
|
|
pointIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( previousTool != tool )
|
|
{
|
|
controlPointRotationsInitialized = false;
|
|
previousTool = tool;
|
|
}
|
|
|
|
// Draw Transform handles for control points
|
|
switch( Tools.current )
|
|
{
|
|
case Tool.Move:
|
|
if( !controlPointRotationsInitialized )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
BezierPoint p = allPoints[i];
|
|
|
|
precedingPointRotations[i] = Quaternion.LookRotation( p.precedingControlPointPosition - p.position );
|
|
followingPointRotations[i] = Quaternion.LookRotation( p.followingControlPointPosition - p.position );
|
|
}
|
|
|
|
controlPointRotationsInitialized = true;
|
|
}
|
|
|
|
// No need to show gizmos for control points in Autoconstruct mode
|
|
Vector3 position;
|
|
if( BezierSettings.ShowControlPoints && ( !point.spline || point.spline.autoConstructMode == SplineAutoConstructMode.None ) )
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
position = Handles.PositionHandle( point.precedingControlPointPosition, Tools.pivotRotation == PivotRotation.Local ? precedingPointRotations[pointIndex] : Quaternion.identity );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
Undo.RecordObject( point, "Move Control Point" );
|
|
point.precedingControlPointPosition = position;
|
|
|
|
if( e.shift && allPoints.Length > 1 )
|
|
{
|
|
Vector3 delta = Matrix4x4.TRS( precedingPointInitialPositions[pointIndex], Tools.pivotRotation == PivotRotation.Local ? precedingPointRotations[pointIndex] : Quaternion.identity, Vector3.Distance( precedingPointInitialPositions[pointIndex], point.position ) * Vector3.one ).inverse.MultiplyPoint3x4( position );
|
|
if( BezierSettings.MoveMultiplePointsInOppositeDirections )
|
|
delta = -delta;
|
|
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Control Point" );
|
|
allPoints[i].precedingControlPointPosition = Matrix4x4.TRS( precedingPointInitialPositions[i], Tools.pivotRotation == PivotRotation.Local ? precedingPointRotations[i] : Quaternion.identity, Vector3.Distance( precedingPointInitialPositions[i], allPoints[i].position ) * Vector3.one ).MultiplyPoint3x4( delta );
|
|
}
|
|
}
|
|
|
|
allPointsModified = true;
|
|
}
|
|
else if( !e.shift && allPointsModified ) // If shift is released before the left mouse button, reset other points' positions
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Control Point" );
|
|
allPoints[i].precedingControlPointPosition = precedingPointInitialPositions[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
position = Handles.PositionHandle( point.followingControlPointPosition, Tools.pivotRotation == PivotRotation.Local ? followingPointRotations[pointIndex] : Quaternion.identity );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
Undo.RecordObject( point, "Move Control Point" );
|
|
point.followingControlPointPosition = position;
|
|
|
|
if( e.shift && allPoints.Length > 1 )
|
|
{
|
|
Vector3 delta = Matrix4x4.TRS( followingPointInitialPositions[pointIndex], Tools.pivotRotation == PivotRotation.Local ? followingPointRotations[pointIndex] : Quaternion.identity, Vector3.Distance( followingPointInitialPositions[pointIndex], point.position ) * Vector3.one ).inverse.MultiplyPoint3x4( position );
|
|
if( BezierSettings.MoveMultiplePointsInOppositeDirections )
|
|
delta = -delta;
|
|
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Control Point" );
|
|
allPoints[i].followingControlPointPosition = Matrix4x4.TRS( followingPointInitialPositions[i], Tools.pivotRotation == PivotRotation.Local ? followingPointRotations[i] : Quaternion.identity, Vector3.Distance( followingPointInitialPositions[i], allPoints[i].position ) * Vector3.one ).MultiplyPoint3x4( delta );
|
|
}
|
|
}
|
|
|
|
allPointsModified = true;
|
|
}
|
|
else if( !e.shift && allPointsModified ) // If shift is released before the left mouse button, reset other points' positions
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Control Point" );
|
|
allPoints[i].followingControlPointPosition = followingPointInitialPositions[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
position = Handles.PositionHandle( point.position, Tools.pivotRotation == PivotRotation.Local ? point.rotation : Quaternion.identity );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
Undo.RecordObject( point, "Move Point" );
|
|
Undo.RecordObject( point.transform, "Move Point" );
|
|
point.position = position;
|
|
|
|
if( e.shift && allPoints.Length > 1 )
|
|
{
|
|
Vector3 delta = position - pointInitialPositions[pointIndex];
|
|
if( BezierSettings.MoveMultiplePointsInOppositeDirections )
|
|
delta = -delta;
|
|
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Point" );
|
|
Undo.RecordObject( allPoints[i].transform, "Move Point" );
|
|
allPoints[i].position = pointInitialPositions[i] + delta;
|
|
}
|
|
}
|
|
|
|
allPointsModified = true;
|
|
}
|
|
else if( !e.shift && allPointsModified ) // If shift is released before the left mouse button, reset other points' positions
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Move Point" );
|
|
Undo.RecordObject( allPoints[i].transform, "Move Point" );
|
|
allPoints[i].position = pointInitialPositions[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case Tool.Rotate:
|
|
Quaternion handleRotation;
|
|
if( Tools.pivotRotation == PivotRotation.Local )
|
|
{
|
|
handleRotation = point.rotation;
|
|
controlPointRotationsInitialized = false;
|
|
}
|
|
else
|
|
{
|
|
if( !controlPointRotationsInitialized )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
precedingPointRotations[i] = Quaternion.identity;
|
|
|
|
controlPointRotationsInitialized = true;
|
|
}
|
|
|
|
handleRotation = precedingPointRotations[pointIndex];
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
Quaternion rotation = Handles.RotationHandle( handleRotation, point.position );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
// "rotation * Quaternion.Inverse( handleRotation )": World-space delta rotation
|
|
// "delta rotation * point.rotation": Applying world-space delta rotation to current rotation
|
|
Quaternion pointFinalRotation = rotation * Quaternion.Inverse( handleRotation ) * point.rotation;
|
|
|
|
Undo.RecordObject( point.transform, "Rotate Point" );
|
|
point.rotation = pointFinalRotation;
|
|
|
|
if( e.shift && allPoints.Length > 1 )
|
|
{
|
|
Quaternion delta = pointFinalRotation * Quaternion.Inverse( pointInitialRotations[pointIndex] );
|
|
if( BezierSettings.MoveMultiplePointsInOppositeDirections )
|
|
delta = Quaternion.Inverse( delta );
|
|
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i].transform, "Rotate Point" );
|
|
allPoints[i].rotation = delta * pointInitialRotations[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = true;
|
|
}
|
|
else if( !e.shift && allPointsModified ) // If shift is released before the left mouse button, reset other points' rotations
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i].transform, "Rotate Point" );
|
|
allPoints[i].rotation = pointInitialRotations[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
|
|
if( Tools.pivotRotation == PivotRotation.Global )
|
|
precedingPointRotations[pointIndex] = rotation;
|
|
}
|
|
|
|
break;
|
|
case Tool.Scale:
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 scale = Handles.ScaleHandle( point.localScale, point.position, point.rotation, HandleUtility.GetHandleSize( point.position ) );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
Undo.RecordObject( point.transform, "Scale Point" );
|
|
point.localScale = scale;
|
|
|
|
if( e.shift && allPoints.Length > 1 )
|
|
{
|
|
Vector3 delta = new Vector3( 1f, 1f, 1f );
|
|
Vector3 prevScale = pointInitialScales[pointIndex];
|
|
if( prevScale.x != 0f )
|
|
delta.x = scale.x / prevScale.x;
|
|
if( prevScale.y != 0f )
|
|
delta.y = scale.y / prevScale.y;
|
|
if( prevScale.z != 0f )
|
|
delta.z = scale.z / prevScale.z;
|
|
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
prevScale = pointInitialScales[i];
|
|
prevScale.Scale( delta );
|
|
|
|
Undo.RecordObject( allPoints[i].transform, "Scale Point" );
|
|
allPoints[i].localScale = prevScale;
|
|
}
|
|
}
|
|
|
|
allPointsModified = true;
|
|
}
|
|
else if( !e.shift && allPointsModified ) // If shift is released before the left mouse button, reset other points' scales
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
if( i != pointIndex )
|
|
{
|
|
Undo.RecordObject( allPoints[i].transform, "Scale Point" );
|
|
allPoints[i].localScale = pointInitialScales[i];
|
|
}
|
|
}
|
|
|
|
allPointsModified = false;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if( e.control )
|
|
BezierUtils.DrawBezierPoint( point, point.index + 1, true );
|
|
}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
if( CheckCommands() )
|
|
GUIUtility.ExitGUI();
|
|
|
|
if( allSplines.Length == 0 )
|
|
{
|
|
EditorGUILayout.HelpBox( "Selected point(s) aren't children of a BezierSpline!", MessageType.Error );
|
|
|
|
if( GUILayout.Button( "Refresh" ) )
|
|
OnEnable();
|
|
|
|
return;
|
|
}
|
|
|
|
BezierUtils.DrawSplineInspectorGUI( allSplines );
|
|
|
|
if( BezierUtils.QuickEditSplineMode )
|
|
return;
|
|
|
|
EditorGUILayout.Space();
|
|
BezierUtils.DrawSeparator();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
|
|
if( GUILayout.Button( "<-", BezierUtils.GL_WIDTH_45 ) )
|
|
{
|
|
Object[] newSelection = new Object[pointCount];
|
|
for( int i = 0, index = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
BezierPoint[] points = selection[i].points;
|
|
|
|
if( spline )
|
|
{
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
int prevIndex = points[j].index - 1;
|
|
if( prevIndex < 0 )
|
|
prevIndex = spline.Count - 1;
|
|
|
|
newSelection[index++] = spline[prevIndex].gameObject;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( int j = 0; j < points.Length; j++ )
|
|
newSelection[index++] = points[j].gameObject;
|
|
}
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
string pointIndex = ( pointCount == 1 && selection[0].spline ) ? ( allPoints[0].index + 1 ).ToString() : "-";
|
|
string splineLength = ( selection.Length == 1 && selection[0].spline ) ? selection[0].spline.Count.ToString() : "-";
|
|
GUILayout.Box( "Selected Point: " + pointIndex + " / " + splineLength, GUILayout.ExpandWidth( true ) );
|
|
|
|
if( GUILayout.Button( "->", BezierUtils.GL_WIDTH_45 ) )
|
|
{
|
|
Object[] newSelection = new Object[pointCount];
|
|
for( int i = 0, index = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
BezierPoint[] points = selection[i].points;
|
|
|
|
if( spline )
|
|
{
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
int nextIndex = points[j].index + 1;
|
|
if( nextIndex >= spline.Count )
|
|
nextIndex = 0;
|
|
|
|
newSelection[index++] = spline[nextIndex].gameObject;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( int j = 0; j < points.Length; j++ )
|
|
newSelection[index++] = points[j].gameObject;
|
|
}
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
if( GUILayout.Button( "Decrement Point's Index" ) )
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
if( spline )
|
|
{
|
|
selection[i].SortPoints( true );
|
|
|
|
BezierPoint[] points = selection[i].points;
|
|
int[] newIndices = new int[points.Length];
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
int index = points[j].index;
|
|
int newIndex = index - 1;
|
|
if( newIndex < 0 )
|
|
newIndex = spline.Count - 1;
|
|
|
|
newIndices[j] = newIndex;
|
|
}
|
|
|
|
for( int j = 0; j < points.Length; j++ )
|
|
spline.ChangePointIndex( points[j].index, newIndices[j], "Change point index" );
|
|
|
|
selection[i].SortPoints( true );
|
|
}
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
if( GUILayout.Button( "Increment Point's Index" ) )
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
if( spline )
|
|
{
|
|
selection[i].SortPoints( false );
|
|
|
|
BezierPoint[] points = selection[i].points;
|
|
int[] newIndices = new int[points.Length];
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
int index = points[j].index;
|
|
int newIndex = index + 1;
|
|
if( newIndex >= spline.Count )
|
|
newIndex = 0;
|
|
|
|
newIndices[j] = newIndex;
|
|
}
|
|
|
|
for( int j = 0; j < points.Length; j++ )
|
|
spline.ChangePointIndex( points[j].index, newIndices[j], "Change point index" );
|
|
|
|
selection[i].SortPoints( true );
|
|
}
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
bool allSplinesUsingAutoConstructMode = !System.Array.Find( allSplines, ( s ) => s.autoConstructMode == SplineAutoConstructMode.None );
|
|
bool anySplineUsingAutoCalculateNormals = System.Array.Find( allSplines, ( s ) => s.autoCalculateNormals );
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( "Insert Point Before" ) )
|
|
InsertNewPoints( false, false );
|
|
if( !allSplinesUsingAutoConstructMode && GUILayout.Button( INSERT_POINT_PRESERVE_SHAPE, EditorGUIUtility.wideMode ? BezierUtils.GL_WIDTH_155 : BezierUtils.GL_WIDTH_100 ) )
|
|
InsertNewPoints( false, true );
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( "Insert Point After" ) )
|
|
InsertNewPoints( true, false );
|
|
if( !allSplinesUsingAutoConstructMode && GUILayout.Button( INSERT_POINT_PRESERVE_SHAPE, EditorGUIUtility.wideMode ? BezierUtils.GL_WIDTH_155 : BezierUtils.GL_WIDTH_100 ) )
|
|
InsertNewPoints( true, true );
|
|
GUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
if( GUILayout.Button( "Duplicate Point" ) )
|
|
DuplicateSelectedPoints();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
GUI.enabled = !allSplinesUsingAutoConstructMode;
|
|
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.handleMode == p2.handleMode );
|
|
EditorGUI.BeginChangeCheck();
|
|
BezierPoint.HandleMode handleMode = (BezierPoint.HandleMode) EditorGUILayout.EnumPopup( HANDLE_MODE_TIP, allPoints[0].handleMode );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Change Point Handle Mode" );
|
|
allPoints[i].handleMode = handleMode;
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.precedingControlPointLocalPosition == p2.precedingControlPointLocalPosition );
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 precedingControlPointLocalPosition = EditorGUILayout.Vector3Field( "Preceding Control Point Local Position", allPoints[0].precedingControlPointLocalPosition );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Change Point Position" );
|
|
allPoints[i].precedingControlPointLocalPosition = precedingControlPointLocalPosition;
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.followingControlPointLocalPosition == p2.followingControlPointLocalPosition );
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 followingControlPointLocalPosition = EditorGUILayout.Vector3Field( "Following Control Point Local Position", allPoints[0].followingControlPointLocalPosition );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Change Point Position" );
|
|
allPoints[i].followingControlPointLocalPosition = followingControlPointLocalPosition;
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
bool showControlPointDistanceWarning = false;
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
BezierPoint point = allPoints[i];
|
|
if( ( point.position - point.precedingControlPointPosition ).sqrMagnitude < CONTROL_POINTS_MINIMUM_SAFE_DISTANCE_SQR ||
|
|
( point.position - point.followingControlPointPosition ).sqrMagnitude < CONTROL_POINTS_MINIMUM_SAFE_DISTANCE_SQR )
|
|
{
|
|
showControlPointDistanceWarning = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( showControlPointDistanceWarning )
|
|
EditorGUILayout.HelpBox( "Positions of control point(s) shouldn't be very close to (0,0,0), this might result in unpredictable behaviour while moving along the spline with constant speed.", MessageType.Warning );
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( "Swap Control Points" ) )
|
|
{
|
|
SwapControlPoints( allPoints );
|
|
SceneView.RepaintAll();
|
|
}
|
|
if( GUILayout.Button( APPLY_TO_ALL_POINTS, BezierUtils.GL_WIDTH_60 ) )
|
|
{
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
SwapControlPoints( allSplines[i].endPoints );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUI.enabled = true;
|
|
|
|
EditorGUILayout.Space();
|
|
BezierUtils.DrawSeparator();
|
|
|
|
GUI.enabled = !anySplineUsingAutoCalculateNormals;
|
|
|
|
GUILayout.BeginHorizontal();
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.normal == p2.normal );
|
|
EditorGUI.BeginChangeCheck();
|
|
Rect normalRect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight ); // When using GUILayout, button isn't vertically centered
|
|
normalRect.width -= 65f;
|
|
Vector3 normal = EditorGUI.Vector3Field( normalRect, "Normal", allPoints[0].normal );
|
|
normalRect.x += normalRect.width + 5f;
|
|
normalRect.width = 30f;
|
|
if( GUI.Button( normalRect, NORMALS_SET_TO_CAMERA_FORWARD ) )
|
|
normal = SceneView.lastActiveSceneView.camera.transform.forward;
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
allPoints[i].SetNormalAndResetIntermediateNormals( normal, "Change Normal" );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
normalRect.x += 30f;
|
|
if( GUI.Button( normalRect, NORMALS_LOOK_AT_CAMERA ) )
|
|
{
|
|
Vector3 cameraPos = SceneView.lastActiveSceneView.camera.transform.position;
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
allPoints[i].SetNormalAndResetIntermediateNormals( ( cameraPos - allPoints[i].position ).normalized, "Change Normal" );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
if( !EditorGUIUtility.wideMode )
|
|
GUILayout.Space( EditorGUIUtility.singleLineHeight );
|
|
|
|
if( anySplineUsingAutoCalculateNormals )
|
|
{
|
|
GUI.enabled = true;
|
|
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.autoCalculatedNormalAngleOffset == p2.autoCalculatedNormalAngleOffset );
|
|
EditorGUI.BeginChangeCheck();
|
|
float autoCalculatedNormalAngleOffset = EditorGUILayout.FloatField( "Normal Angle", allPoints[0].autoCalculatedNormalAngleOffset );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Change Normal Angle" );
|
|
allPoints[i].autoCalculatedNormalAngleOffset = autoCalculatedNormalAngleOffset;
|
|
}
|
|
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
BezierUtils.SetSplineDirtyWithUndo( allSplines[i], "Change Normal Angle", InternalDirtyFlags.NormalOffsetChange );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
GUI.enabled = false;
|
|
}
|
|
else
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
float normalRotationAngle = EditorGUILayout.FloatField( "Rotate Normal (Drag Here)", 0f );
|
|
if( EditorGUI.EndChangeCheck() && !Mathf.Approximately( normalRotationAngle, 0f ) )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
BezierSpline spline = allPoints[i].spline;
|
|
int index = allPoints[i].index;
|
|
|
|
if( spline )
|
|
{
|
|
Vector3 tangent;
|
|
if( index < spline.Count - 1 )
|
|
tangent = new BezierSpline.Segment( spline[index], spline[index + 1], 0f ).GetTangent();
|
|
else if( spline.loop )
|
|
tangent = new BezierSpline.Segment( spline[index], spline[0], 0f ).GetTangent();
|
|
else
|
|
tangent = new BezierSpline.Segment( spline[index - 1], spline[index], 1f ).GetTangent();
|
|
|
|
allPoints[i].SetNormalAndResetIntermediateNormals( Quaternion.AngleAxis( normalRotationAngle, tangent ) * allPoints[i].normal, "Change Normal Rotate Angle" );
|
|
}
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( "Flip Normals" ) )
|
|
{
|
|
FlipNormals( allPoints );
|
|
SceneView.RepaintAll();
|
|
}
|
|
if( GUILayout.Button( APPLY_TO_ALL_POINTS, BezierUtils.GL_WIDTH_60 ) )
|
|
{
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
FlipNormals( allSplines[i].endPoints );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( NORMALIZE_NORMALS_TIP ) )
|
|
{
|
|
NormalizeNormals( allPoints );
|
|
SceneView.RepaintAll();
|
|
}
|
|
if( GUILayout.Button( APPLY_TO_ALL_POINTS, BezierUtils.GL_WIDTH_60 ) )
|
|
{
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
NormalizeNormals( allSplines[i].endPoints );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if( GUILayout.Button( "Reset Normals" ) )
|
|
{
|
|
ResetNormals( allPoints );
|
|
SceneView.RepaintAll();
|
|
}
|
|
if( GUILayout.Button( APPLY_TO_ALL_POINTS, BezierUtils.GL_WIDTH_60 ) )
|
|
{
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
ResetNormals( allSplines[i].endPoints );
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUI.enabled = true;
|
|
|
|
EditorGUILayout.Space();
|
|
BezierUtils.DrawSeparator();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
EditorGUI.showMixedValue = HasMultipleDifferentValues( ( p1, p2 ) => p1.extraData == p2.extraData );
|
|
EditorGUI.BeginChangeCheck();
|
|
Rect extraDataRect = EditorGUILayout.GetControlRect( false, EditorGUIUtility.singleLineHeight ); // When using GUILayout, button isn't vertically centered
|
|
extraDataRect.width -= 65f;
|
|
BezierPoint.ExtraData extraData = EditorGUI.Vector4Field( extraDataRect, EXTRA_DATA_TIP, allPoints[0].extraData );
|
|
extraDataRect.x += extraDataRect.width + 5f;
|
|
extraDataRect.width = 30f;
|
|
if( GUI.Button( extraDataRect, EXTRA_DATA_SET_AS_CAMERA_FORWARD ) )
|
|
extraData = SceneView.lastActiveSceneView.camera.transform.rotation;
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i], "Change Extra Data" );
|
|
allPoints[i].extraData = extraData;
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
EditorGUI.showMixedValue = false;
|
|
|
|
extraDataRect.x += 30f;
|
|
EditorGUI.BeginChangeCheck();
|
|
bool visualizeExtraDataAsFrustum = GUI.Toggle( extraDataRect, BezierSettings.VisualizeExtraDataAsFrustum, EXTRA_DATA_VIEW_AS_FRUSTUM, GUI.skin.button );
|
|
if( EditorGUI.EndChangeCheck() )
|
|
{
|
|
BezierSettings.VisualizeExtraDataAsFrustum = visualizeExtraDataAsFrustum;
|
|
SceneView.RepaintAll();
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
if( !EditorGUIUtility.wideMode )
|
|
GUILayout.Space( EditorGUIUtility.singleLineHeight );
|
|
|
|
BezierUtils.DrawSeparator();
|
|
EditorGUILayout.Space();
|
|
|
|
Color c = GUI.backgroundColor;
|
|
GUI.backgroundColor = RESET_POINT_BUTTON_COLOR;
|
|
|
|
if( GUILayout.Button( "Reset Point" ) )
|
|
{
|
|
for( int i = 0; i < allPoints.Length; i++ )
|
|
{
|
|
Undo.RecordObject( allPoints[i].transform, "Reset Point" );
|
|
Undo.RecordObject( allPoints[i], "Reset Point" );
|
|
|
|
allPoints[i].Reset();
|
|
}
|
|
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
GUI.backgroundColor = REMOVE_POINT_BUTTON_COLOR;
|
|
|
|
if( GUILayout.Button( "Remove Point" ) )
|
|
{
|
|
RemoveSelectedPoints();
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
|
|
GUI.backgroundColor = c;
|
|
|
|
for( int i = 0; i < allSplines.Length; i++ )
|
|
allSplines[i].CheckDirty();
|
|
}
|
|
|
|
private bool CheckCommands()
|
|
{
|
|
Event e = Event.current;
|
|
if( e.type == EventType.ValidateCommand )
|
|
{
|
|
if( e.commandName == "Delete" )
|
|
{
|
|
RemoveSelectedPoints();
|
|
e.type = EventType.Ignore;
|
|
|
|
return true;
|
|
}
|
|
else if( e.commandName == "Duplicate" )
|
|
{
|
|
DuplicateSelectedPoints();
|
|
e.type = EventType.Ignore;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( e.isKey && e.type == EventType.KeyDown && e.keyCode == KeyCode.Delete )
|
|
{
|
|
RemoveSelectedPoints();
|
|
e.Use();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool HasMultipleDifferentValues( System.Func<BezierPoint, BezierPoint, bool> comparer )
|
|
{
|
|
if( allPoints.Length <= 1 )
|
|
return false;
|
|
|
|
for( int i = 1; i < allPoints.Length; i++ )
|
|
{
|
|
if( !comparer( allPoints[0], allPoints[i] ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void InsertNewPoints( bool insertAfter, bool preserveShape )
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
Object[] newSelection = new Object[pointCount];
|
|
for( int i = 0, index = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
BezierPoint[] points = selection[i].points;
|
|
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
BezierPoint newPoint;
|
|
if( spline )
|
|
{
|
|
int pointIndex = points[j].index;
|
|
if( insertAfter )
|
|
pointIndex++;
|
|
|
|
Vector3 position, followingControlPointPosition;
|
|
if( spline.Count >= 2 )
|
|
{
|
|
if( !spline.loop && pointIndex == 0 )
|
|
{
|
|
position = spline[0].position - Vector3.Distance( spline[1].position, spline[0].position ) * spline.GetTangent( 0f ).normalized;
|
|
followingControlPointPosition = position - ( position - spline[0].position ) * 0.5f;
|
|
}
|
|
else if( !spline.loop && pointIndex == spline.Count )
|
|
{
|
|
position = spline[pointIndex - 1].position + Vector3.Distance( spline[pointIndex - 1].position, spline[pointIndex - 2].position ) * spline.GetTangent( 1f ).normalized;
|
|
followingControlPointPosition = position + ( position - spline[pointIndex - 1].position ) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
// Insert point in the middle without affecting the spline's shape
|
|
BezierPoint point1 = ( pointIndex == 0 || pointIndex == spline.Count ) ? spline[spline.Count - 1] : spline[pointIndex - 1];
|
|
BezierPoint point2 = ( pointIndex == 0 || pointIndex == spline.Count ) ? spline[0] : spline[pointIndex];
|
|
|
|
Vector3 precedingControlPointPosition;
|
|
CalculateInsertedPointPosition( point1, point2, 0.5f, preserveShape, out position, out precedingControlPointPosition, out followingControlPointPosition );
|
|
}
|
|
}
|
|
else if( spline.Count == 1 )
|
|
{
|
|
position = pointIndex == 0 ? spline[0].position - Vector3.forward : spline[0].position + Vector3.forward;
|
|
followingControlPointPosition = position + ( spline[0].followingControlPointPosition - spline[0].position );
|
|
}
|
|
else
|
|
{
|
|
position = spline.transform.position;
|
|
followingControlPointPosition = Vector3.right;
|
|
}
|
|
|
|
newPoint = spline.InsertNewPointAt( pointIndex );
|
|
newPoint.position = position;
|
|
newPoint.followingControlPointPosition = followingControlPointPosition;
|
|
}
|
|
else
|
|
newPoint = Instantiate( points[j], points[j].transform.parent );
|
|
|
|
Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Insert Point" );
|
|
if( newPoint.transform.parent )
|
|
Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Insert Point" );
|
|
|
|
newSelection[index++] = newPoint.gameObject;
|
|
}
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private void DuplicateSelectedPoints()
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
Object[] newSelection = new Object[pointCount];
|
|
for( int i = 0, index = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
BezierPoint[] points = selection[i].points;
|
|
|
|
for( int j = 0; j < points.Length; j++ )
|
|
{
|
|
BezierPoint newPoint;
|
|
if( spline )
|
|
newPoint = spline.DuplicatePointAt( points[j].index );
|
|
else
|
|
newPoint = Instantiate( points[j], points[j].transform.parent );
|
|
|
|
Undo.RegisterCreatedObjectUndo( newPoint.gameObject, "Duplicate Point" );
|
|
if( newPoint.transform.parent )
|
|
Undo.RegisterCompleteObjectUndo( newPoint.transform.parent, "Duplicate Point" );
|
|
|
|
newSelection[index++] = newPoint.gameObject;
|
|
}
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private void RemoveSelectedPoints()
|
|
{
|
|
Undo.IncrementCurrentGroup();
|
|
|
|
Object[] newSelection = new Object[selection.Length];
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
BezierSpline spline = selection[i].spline;
|
|
BezierPoint[] points = selection[i].points;
|
|
|
|
for( int j = 0; j < points.Length; j++ )
|
|
Undo.DestroyObjectImmediate( points[j].gameObject );
|
|
|
|
if( spline )
|
|
newSelection[i] = spline.gameObject;
|
|
}
|
|
|
|
Selection.objects = newSelection;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private void SwapControlPoints( IList<BezierPoint> points )
|
|
{
|
|
for( int i = 0; i < points.Count; i++ )
|
|
{
|
|
Undo.RecordObject( points[i], "Swap Control Points" );
|
|
Vector3 temp = points[i].precedingControlPointLocalPosition;
|
|
points[i].precedingControlPointLocalPosition = points[i].followingControlPointLocalPosition;
|
|
points[i].followingControlPointLocalPosition = temp;
|
|
}
|
|
}
|
|
|
|
// Credit: https://stackoverflow.com/a/2614028/2373034
|
|
internal static void CalculateInsertedPointPosition( BezierPoint neighbor1, BezierPoint neighbor2, float localT, bool preserveShape, out Vector3 position, out Vector3 precedingControlPointPosition, out Vector3 followingControlPointPosition )
|
|
{
|
|
float oneMinusLocalT = 1f - localT;
|
|
Vector3 P0_1 = oneMinusLocalT * neighbor1.position + localT * neighbor1.followingControlPointPosition;
|
|
Vector3 P1_2 = oneMinusLocalT * neighbor1.followingControlPointPosition + localT * neighbor2.precedingControlPointPosition;
|
|
Vector3 P2_3 = oneMinusLocalT * neighbor2.precedingControlPointPosition + localT * neighbor2.position;
|
|
|
|
precedingControlPointPosition = oneMinusLocalT * P0_1 + localT * P1_2;
|
|
followingControlPointPosition = oneMinusLocalT * P1_2 + localT * P2_3;
|
|
|
|
position = oneMinusLocalT * precedingControlPointPosition + localT * followingControlPointPosition;
|
|
|
|
// We need to change neighboring end points' handleModes if we want to truly preserve the spline's shape
|
|
if( preserveShape )
|
|
{
|
|
Undo.RecordObject( neighbor1, "Insert Point" );
|
|
Undo.RecordObject( neighbor2, "Insert Point" );
|
|
|
|
if( neighbor1.handleMode == BezierPoint.HandleMode.Mirrored )
|
|
neighbor1.handleMode = BezierPoint.HandleMode.Aligned;
|
|
if( neighbor2.handleMode == BezierPoint.HandleMode.Mirrored )
|
|
neighbor2.handleMode = BezierPoint.HandleMode.Aligned;
|
|
|
|
neighbor1.followingControlPointPosition = P0_1;
|
|
neighbor2.precedingControlPointPosition = P2_3;
|
|
}
|
|
}
|
|
|
|
private void FlipNormals( IList<BezierPoint> points )
|
|
{
|
|
for( int i = 0; i < points.Count; i++ )
|
|
points[i].SetNormalAndResetIntermediateNormals( -points[i].normal, "Flip Normals" );
|
|
}
|
|
|
|
private void NormalizeNormals( IList<BezierPoint> points )
|
|
{
|
|
for( int i = 0; i < points.Count; i++ )
|
|
{
|
|
if( points[i].normal != Vector3.zero )
|
|
points[i].SetNormalAndResetIntermediateNormals( points[i].normal.normalized, "Normalize Normals" );
|
|
}
|
|
}
|
|
|
|
private void ResetNormals( IList<BezierPoint> points )
|
|
{
|
|
for( int i = 0; i < points.Count; i++ )
|
|
points[i].SetNormalAndResetIntermediateNormals( Vector3.up, "Reset Normals" );
|
|
}
|
|
|
|
private void OnUndoRedo()
|
|
{
|
|
controlPointRotationsInitialized = false;
|
|
|
|
for( int i = 0; i < selection.Length; i++ )
|
|
{
|
|
if( selection[i].spline )
|
|
{
|
|
selection[i].spline.dirtyFlags |= InternalDirtyFlags.All;
|
|
selection[i].spline.Refresh();
|
|
}
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
private bool HasFrameBounds()
|
|
{
|
|
return !serializedObject.isEditingMultipleObjects;
|
|
}
|
|
|
|
private Bounds OnGetFrameBounds()
|
|
{
|
|
return new Bounds( ( (BezierPoint) target ).position, new Vector3( 1f, 1f, 1f ) );
|
|
}
|
|
}
|
|
} |