You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#branch UE4 #proj Editor.ComponentVisualizers, Editor.UnrealEd, Runtime.Core, Runtime.Engine #change FInterpCurve now uses a binary search to obtain the correct key. Removed the optional out-parameter on Eval, EvalDerivative, etc. Removed FORCEINLINEs on large methods. #change USplineComponent now builds the remap table with points spaced at equal distances, which leads to better results. Added internal methods to obtain the segment length, and the interpolation parameter along the segment at a given distance. #add Added Duration to USplineComponent. #add Added a number of different methods for obtaining position, tangent and rotation at a specified distance, or a specified time (optionally at constant velocity). #add Added methods for building the spline procedurally. #add Supports right-click context menu on ComponentVisualizers in the standard interface. #add Key tangents can be manually edited. #change FLevelEditorViewportClient::ProcessClick now handles right clicks to the Unreal gizmo in the same manner as left clicks, i.e. the method recurses using a version of the hit proxy with the gizmo removed, so we can look at what's underneath. This allows for "genuine" context menus, i.e. depending on what was clicked, instead of using a single default provided by the current level editor. #reviewedby James.Golding [CL 2089561 by Richard TalbotWatkin in Main branch]
415 lines
13 KiB
C++
415 lines
13 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ComponentVisualizersPrivatePCH.h"
|
|
#include "SplineComponentVisualizer.h"
|
|
|
|
IMPLEMENT_HIT_PROXY(HSplineVisProxy, HComponentVisProxy);
|
|
IMPLEMENT_HIT_PROXY(HSplineKeyProxy, HSplineVisProxy);
|
|
|
|
#define LOCTEXT_NAMESPACE "SplineComponentVisualizer"
|
|
|
|
|
|
/** Define commands for the spline component visualizer */
|
|
class FSplineComponentVisualizerCommands : public TCommands<FSplineComponentVisualizerCommands>
|
|
{
|
|
public:
|
|
FSplineComponentVisualizerCommands() : TCommands <FSplineComponentVisualizerCommands>
|
|
(
|
|
"SplineComponentVisualizer", // Context name for fast lookup
|
|
LOCTEXT("SplineComponentVisualizer", "Spline Component Visualizer"), // Localized context name for displaying
|
|
NAME_None, // Parent
|
|
FEditorStyle::GetStyleSetName()
|
|
)
|
|
{
|
|
}
|
|
|
|
virtual void RegisterCommands() OVERRIDE
|
|
{
|
|
UI_COMMAND(DeleteKey, "Delete key", "Delete the currently selected key.", EUserInterfaceActionType::Button, FInputGesture(EKeys::Delete));
|
|
UI_COMMAND(DuplicateKey, "Duplicate key", "Duplicates the currently selected key.", EUserInterfaceActionType::Button, FInputGesture());
|
|
UI_COMMAND(ResetToAutomaticTangent, "Reset to Automatic Tangent", "Specify that the tangent for this key will be reset to its default auto-generated value.", EUserInterfaceActionType::Button, FInputGesture());
|
|
}
|
|
|
|
public:
|
|
/** Delete key */
|
|
TSharedPtr<FUICommandInfo> DeleteKey;
|
|
|
|
/** Duplicate key */
|
|
TSharedPtr<FUICommandInfo> DuplicateKey;
|
|
|
|
/** Reset to automatic tangent */
|
|
TSharedPtr<FUICommandInfo> ResetToAutomaticTangent;
|
|
};
|
|
|
|
|
|
|
|
FSplineComponentVisualizer::FSplineComponentVisualizer()
|
|
: FComponentVisualizer()
|
|
, SelectedKeyIndex(INDEX_NONE)
|
|
, bAllowDuplication(true)
|
|
{
|
|
FSplineComponentVisualizerCommands::Register();
|
|
|
|
SplineComponentVisualizerActions = MakeShareable(new FUICommandList);
|
|
}
|
|
|
|
void FSplineComponentVisualizer::OnRegister()
|
|
{
|
|
const auto& Commands = FSplineComponentVisualizerCommands::Get();
|
|
|
|
SplineComponentVisualizerActions->MapAction(
|
|
Commands.DeleteKey,
|
|
FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnDeleteKey),
|
|
FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::IsSelectionValid));
|
|
|
|
SplineComponentVisualizerActions->MapAction(
|
|
Commands.DuplicateKey,
|
|
FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnDuplicateKey),
|
|
FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::IsSelectionValid));
|
|
|
|
SplineComponentVisualizerActions->MapAction(
|
|
Commands.ResetToAutomaticTangent,
|
|
FExecuteAction::CreateSP(this, &FSplineComponentVisualizer::OnResetToAutomaticTangent),
|
|
FCanExecuteAction::CreateSP(this, &FSplineComponentVisualizer::CanResetToAutomaticTangent));
|
|
}
|
|
|
|
FSplineComponentVisualizer::~FSplineComponentVisualizer()
|
|
{
|
|
FSplineComponentVisualizerCommands::Unregister();
|
|
}
|
|
|
|
void FSplineComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
if (const USplineComponent* SplineComp = Cast<const USplineComponent>(Component))
|
|
{
|
|
FColor DrawColor(255,255,255);
|
|
|
|
const FInterpCurveVector& SplineInfo = SplineComp->SplineInfo;
|
|
|
|
/*
|
|
// Debug draw keys on the SplineReparamTable
|
|
const FInterpCurveFloat& SplineReparamTable = SplineComp->SplineReparamTable;
|
|
for (const auto& Point : SplineReparamTable.Points)
|
|
{
|
|
const float Value = Point.OutVal;
|
|
FVector KeyPos = SplineComp->ComponentToWorld.TransformPosition(SplineInfo.Eval(Value, FVector::ZeroVector));
|
|
PDI->DrawPoint(KeyPos, FColor(255, 0, 255), 5.f, SDPG_World);
|
|
}
|
|
*/
|
|
|
|
FVector OldKeyPos(0);
|
|
float OldKeyTime = 0.f;
|
|
for (int32 KeyIdx = 0; KeyIdx<SplineInfo.Points.Num(); KeyIdx++)
|
|
{
|
|
float NewKeyTime = SplineInfo.Points[KeyIdx].InVal;
|
|
FVector NewKeyPos = SplineComp->ComponentToWorld.TransformPosition( SplineInfo.Eval(NewKeyTime, FVector::ZeroVector) );
|
|
|
|
USplineComponent* EditedSplineComp = GetEditedSplineComponent();
|
|
FColor KeyColor = (SplineComp == EditedSplineComp && KeyIdx == SelectedKeyIndex) ? FColor(255,0,0) : DrawColor;
|
|
|
|
// Draw the keypoint
|
|
PDI->SetHitProxy(new HSplineKeyProxy(Component, KeyIdx));
|
|
|
|
PDI->DrawPoint(NewKeyPos, KeyColor, 8.f, SDPG_World);
|
|
|
|
PDI->SetHitProxy(NULL);
|
|
|
|
|
|
// If not the first keypoint, draw a line to the last keypoint.
|
|
if (KeyIdx>0)
|
|
{
|
|
// For constant interpolation - don't draw ticks - just draw dotted line.
|
|
if (SplineInfo.Points[KeyIdx - 1].InterpMode == CIM_Constant)
|
|
{
|
|
DrawDashedLine(PDI, OldKeyPos, NewKeyPos, DrawColor, 20, SDPG_World);
|
|
}
|
|
else
|
|
{
|
|
int32 NumSteps = FMath::CeilToInt((NewKeyTime - OldKeyTime) / 0.02f);
|
|
float DrawSubstep = (NewKeyTime - OldKeyTime) / NumSteps;
|
|
|
|
// Find position on first keyframe.
|
|
float OldTime = OldKeyTime;
|
|
FVector OldPos = OldKeyPos;
|
|
|
|
// Then draw a line for each substep.
|
|
for (int32 StepIdx = 1; StepIdx<NumSteps + 1; StepIdx++)
|
|
{
|
|
float NewTime = OldKeyTime + StepIdx*DrawSubstep;
|
|
FVector NewPos = SplineComp->ComponentToWorld.TransformPosition( SplineInfo.Eval(NewTime, FVector::ZeroVector) );
|
|
|
|
PDI->DrawLine(OldPos, NewPos, DrawColor, SDPG_World);
|
|
|
|
// Don't draw point for last one - its the keypoint drawn above.
|
|
if (false)// (StepIdx != NumSteps)
|
|
{
|
|
PDI->DrawPoint(NewPos, DrawColor, 3.f, SDPG_World);
|
|
}
|
|
|
|
OldTime = NewTime;
|
|
OldPos = NewPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
OldKeyTime = NewKeyTime;
|
|
OldKeyPos = NewKeyPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSplineComponentVisualizer::VisProxyHandleClick(HComponentVisProxy* VisProxy)
|
|
{
|
|
bool bEditing = false;
|
|
|
|
if(VisProxy && VisProxy->Component.IsValid())
|
|
{
|
|
const USplineComponent* SplineComp = CastChecked<const USplineComponent>(VisProxy->Component.Get());
|
|
|
|
SplineCompPropName = GetComponentPropertyName(SplineComp);
|
|
if(SplineCompPropName != NAME_None)
|
|
{
|
|
SplineOwningActor = SplineComp->GetOwner();
|
|
|
|
if (VisProxy->IsA(HSplineKeyProxy::StaticGetType()))
|
|
{
|
|
HSplineKeyProxy* KeyProxy = (HSplineKeyProxy*)VisProxy;
|
|
SelectedKeyIndex = KeyProxy->KeyIndex;
|
|
bEditing = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SplineOwningActor = NULL;
|
|
}
|
|
}
|
|
|
|
return bEditing;
|
|
}
|
|
|
|
USplineComponent* FSplineComponentVisualizer::GetEditedSplineComponent() const
|
|
{
|
|
return Cast<USplineComponent>(GetComponentFromPropertyName(SplineOwningActor.Get(), SplineCompPropName));
|
|
}
|
|
|
|
|
|
bool FSplineComponentVisualizer::GetWidgetLocation(FVector& OutLocation) const
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
if (SplineComp && SelectedKeyIndex != INDEX_NONE)
|
|
{
|
|
if (SelectedKeyIndex < SplineComp->SplineInfo.Points.Num())
|
|
{
|
|
OutLocation = SplineComp->ComponentToWorld.TransformPosition( SplineComp->SplineInfo.Points[SelectedKeyIndex].OutVal );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool FSplineComponentVisualizer::HandleInputDelta(FLevelEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
if( SplineComp &&
|
|
SelectedKeyIndex != INDEX_NONE &&
|
|
SelectedKeyIndex < SplineComp->SplineInfo.Points.Num() )
|
|
{
|
|
if (ViewportClient->IsAltPressed() && bAllowDuplication)
|
|
{
|
|
OnDuplicateKey();
|
|
// Don't duplicate again until we release LMB
|
|
bAllowDuplication = false;
|
|
}
|
|
|
|
FInterpCurvePoint<FVector>& EditedPoint = SplineComp->SplineInfo.Points[SelectedKeyIndex];
|
|
|
|
if (!DeltaTranslate.IsZero())
|
|
{
|
|
// Find key position in world space
|
|
FVector CurrentWorldPos = SplineComp->ComponentToWorld.TransformPosition(EditedPoint.OutVal);
|
|
// Move in world space
|
|
FVector NewWorldPos = CurrentWorldPos + DeltaTranslate;
|
|
// Convert back to local space
|
|
EditedPoint.OutVal = SplineComp->ComponentToWorld.InverseTransformPosition(NewWorldPos);
|
|
}
|
|
|
|
if (!DeltaRotate.IsZero())
|
|
{
|
|
// Set point tangent as user controlled
|
|
EditedPoint.InterpMode = CIM_CurveUser;
|
|
|
|
// Rotate tangent according to delta rotation
|
|
const FVector NewTangent = DeltaRotate.RotateVector(EditedPoint.LeaveTangent);
|
|
EditedPoint.LeaveTangent = NewTangent;
|
|
EditedPoint.ArriveTangent = NewTangent;
|
|
}
|
|
|
|
if (!DeltaScale.IsZero())
|
|
{
|
|
// Set point tangent as user controlled
|
|
EditedPoint.InterpMode = CIM_CurveUser;
|
|
|
|
// Break tangent into direction and length so we can change its scale (the 'tension')
|
|
// independently of its direction.
|
|
FVector Direction;
|
|
float Length;
|
|
EditedPoint.LeaveTangent.ToDirectionAndLength(Direction, Length);
|
|
|
|
// Figure out which component has changed, and use it
|
|
float DeltaScaleValue = (DeltaScale.X != 0.0f) ? DeltaScale.X : ((DeltaScale.Y != 0.0f) ? DeltaScale.Y : DeltaScale.Z);
|
|
|
|
// Change scale, avoiding singularity by never allowing a scale of 0, hence preserving direction.
|
|
Length += DeltaScaleValue * 10.0f;
|
|
if (Length == 0.0f)
|
|
{
|
|
Length = SMALL_NUMBER;
|
|
}
|
|
|
|
const FVector NewTangent = Direction * Length;
|
|
EditedPoint.LeaveTangent = NewTangent;
|
|
EditedPoint.ArriveTangent = NewTangent;
|
|
}
|
|
|
|
// Update tangents and reparam table
|
|
SplineComp->SplineInfo.AutoSetTangents();
|
|
SplineComp->UpdateSplineReparamTable();
|
|
|
|
// Notify of change so any CS is re-run
|
|
if (SplineOwningActor.IsValid())
|
|
{
|
|
SplineOwningActor.Get()->PostEditMove(true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSplineComponentVisualizer::HandleInputKey(FLevelEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
|
|
{
|
|
// TODO: find a way to make this work with ProcessCommandBindings(FKeyboardEvent&)
|
|
|
|
bool bHandled = false;
|
|
|
|
if(Key == EKeys::LeftMouseButton && Event == IE_Released)
|
|
{
|
|
// Reset duplication flag on LMB release
|
|
bAllowDuplication = true;
|
|
}
|
|
else if(Key == EKeys::Delete && Event == IE_Pressed)
|
|
{
|
|
// Delete selected key
|
|
if (IsSelectionValid())
|
|
{
|
|
OnDeleteKey();
|
|
bHandled = true; // consume key input
|
|
}
|
|
}
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
|
|
void FSplineComponentVisualizer::EndEditing()
|
|
{
|
|
SplineOwningActor = NULL;
|
|
SplineCompPropName = NAME_None;
|
|
SelectedKeyIndex = INDEX_NONE;
|
|
}
|
|
|
|
|
|
void FSplineComponentVisualizer::OnDuplicateKey()
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
|
|
FInterpCurvePoint<FVector> KeyToDupe = SplineComp->SplineInfo.Points[SelectedKeyIndex];
|
|
KeyToDupe.InterpMode = CIM_CurveAuto;
|
|
int32 NewKeyIndex = SplineComp->SplineInfo.Points.Insert(KeyToDupe, SelectedKeyIndex);
|
|
// move selection to 'next' key
|
|
SelectedKeyIndex++;
|
|
|
|
// Update Input value for all keys
|
|
SplineComp->RefreshSplineInputs();
|
|
SplineComp->SplineInfo.AutoSetTangents(); // update tangents
|
|
SplineComp->UpdateSplineReparamTable(); // update reparam table
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
void FSplineComponentVisualizer::OnDeleteKey()
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
|
|
SplineComp->SplineInfo.Points.RemoveAt(SelectedKeyIndex);
|
|
|
|
SplineComp->RefreshSplineInputs(); // update input value for each key
|
|
SplineComp->SplineInfo.AutoSetTangents(); // update tangents
|
|
SplineComp->UpdateSplineReparamTable(); // update reparam table
|
|
SelectedKeyIndex = INDEX_NONE; // deselect any keys
|
|
|
|
// Notify of change so any CS is re-run
|
|
if (SplineOwningActor.IsValid())
|
|
{
|
|
SplineOwningActor.Get()->PostEditMove(true);
|
|
}
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
bool FSplineComponentVisualizer::IsSelectionValid() const
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
return (SplineComp &&
|
|
SelectedKeyIndex != INDEX_NONE &&
|
|
SelectedKeyIndex < SplineComp->SplineInfo.Points.Num());
|
|
}
|
|
|
|
|
|
void FSplineComponentVisualizer::OnResetToAutomaticTangent()
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
SplineComp->SplineInfo.Points[SelectedKeyIndex].InterpMode = CIM_CurveAuto;
|
|
SplineComp->SplineInfo.AutoSetTangents();
|
|
SplineComp->UpdateSplineReparamTable();
|
|
|
|
// Notify of change so any CS is re-run
|
|
if (SplineOwningActor.IsValid())
|
|
{
|
|
SplineOwningActor.Get()->PostEditMove(true);
|
|
}
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
bool FSplineComponentVisualizer::CanResetToAutomaticTangent() const
|
|
{
|
|
USplineComponent* SplineComp = GetEditedSplineComponent();
|
|
return (SplineComp &&
|
|
SelectedKeyIndex != INDEX_NONE &&
|
|
SelectedKeyIndex < SplineComp->SplineInfo.Points.Num() &&
|
|
SplineComp->SplineInfo.Points[SelectedKeyIndex].InterpMode != CIM_CurveAuto);
|
|
}
|
|
|
|
|
|
TSharedPtr<SWidget> FSplineComponentVisualizer::GenerateContextMenu() const
|
|
{
|
|
FMenuBuilder MenuBuilder(true, SplineComponentVisualizerActions);
|
|
{
|
|
MenuBuilder.BeginSection("SplineKeyEdit");
|
|
{
|
|
MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().DeleteKey);
|
|
MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().DuplicateKey);
|
|
MenuBuilder.AddMenuEntry(FSplineComponentVisualizerCommands::Get().ResetToAutomaticTangent);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
TSharedPtr<SWidget> MenuWidget = MenuBuilder.MakeWidget();
|
|
return MenuWidget;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|