914 lines
40 KiB
C++
914 lines
40 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UnrealWidget.h"
|
|
|
|
#include "CanvasItem.h"
|
|
#include "CanvasTypes.h"
|
|
#include "DynamicMeshBuilder.h"
|
|
#include "EdMode.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorViewportClient.h"
|
|
#include "Materials/Material.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Settings/LevelEditorViewportSettings.h"
|
|
#include "SnappingUtils.h"
|
|
|
|
IMPLEMENT_HIT_PROXY(HWidgetAxis, HHitProxy);
|
|
|
|
constexpr float FWidget::AXIS_LENGTH;
|
|
constexpr float FWidget::TRANSLATE_ROTATE_AXIS_CIRCLE_RADIUS;
|
|
constexpr float FWidget::TWOD_AXIS_CIRCLE_RADIUS;
|
|
constexpr float FWidget::INNER_AXIS_CIRCLE_RADIUS;
|
|
constexpr float FWidget::OUTER_AXIS_CIRCLE_RADIUS;
|
|
constexpr float FWidget::ROTATION_TEXT_RADIUS;
|
|
constexpr int32 FWidget::AXIS_CIRCLE_SIDES;
|
|
constexpr float FWidget::ARCALL_RELATIVE_INNER_SIZE;
|
|
constexpr float FWidget::AXIS_LENGTH_SCALE_OFFSET;
|
|
|
|
FWidget::FWidget()
|
|
{
|
|
EditorModeTools = NULL;
|
|
TotalDeltaRotation = 0;
|
|
CurrentDeltaRotation = 0;
|
|
|
|
AxisColorX = FLinearColor(0.594f, 0.0197f, 0.0f);
|
|
AxisColorY = FLinearColor(0.1349f, 0.3959f, 0.0f);
|
|
AxisColorZ = FLinearColor(0.0251f, 0.207f, 0.85f);
|
|
ScreenAxisColor = FLinearColor(0.76, 0.72, 0.14f);
|
|
PlaneColorXY = FColor::Yellow;
|
|
ArcBallColor = FColor(128, 128, 128, 6);
|
|
ScreenSpaceColor = FColor(196, 196, 196);
|
|
CurrentColor = FColor::Yellow;
|
|
|
|
UMaterial* AxisMaterialBase = GEngine->ArrowMaterial;
|
|
|
|
AxisMaterialX = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
|
|
AxisMaterialX->SetVectorParameterValue("GizmoColor", AxisColorX);
|
|
|
|
AxisMaterialY = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
|
|
AxisMaterialY->SetVectorParameterValue("GizmoColor", AxisColorY);
|
|
|
|
AxisMaterialZ = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
|
|
AxisMaterialZ->SetVectorParameterValue("GizmoColor", AxisColorZ);
|
|
|
|
CurrentAxisMaterial = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
|
|
CurrentAxisMaterial->SetVectorParameterValue("GizmoColor", CurrentColor);
|
|
|
|
OpaquePlaneMaterialXY = UMaterialInstanceDynamic::Create(AxisMaterialBase, NULL);
|
|
OpaquePlaneMaterialXY->SetVectorParameterValue("GizmoColor", FLinearColor::White);
|
|
|
|
TransparentPlaneMaterialXY = (UMaterial*)StaticLoadObject(
|
|
UMaterial::StaticClass(), NULL,
|
|
TEXT("/Engine/EditorMaterials/WidgetVertexColorMaterial.WidgetVertexColorMaterial"), NULL, LOAD_None, NULL);
|
|
|
|
GridMaterial = (UMaterial*)StaticLoadObject(
|
|
UMaterial::StaticClass(), NULL,
|
|
TEXT("/Engine/EditorMaterials/WidgetGridVertexColorMaterial_Ma.WidgetGridVertexColorMaterial_Ma"), NULL,
|
|
LOAD_None, NULL);
|
|
if (!GridMaterial)
|
|
{
|
|
GridMaterial = TransparentPlaneMaterialXY;
|
|
}
|
|
|
|
CurrentAxis = EAxisList::None;
|
|
|
|
CustomCoordSystem = FMatrix::Identity;
|
|
CustomCoordSystemSpace = COORD_World;
|
|
|
|
bAbsoluteTranslationInitialOffsetCached = false;
|
|
InitialTranslationOffset = FVector::ZeroVector;
|
|
InitialTranslationPosition = FVector(0, 0, 0);
|
|
|
|
bDragging = false;
|
|
bSnapEnabled = false;
|
|
bDefaultVisibility = true;
|
|
bIsOrthoDrawingFullRing = false;
|
|
|
|
Origin = FVector2D::ZeroVector;
|
|
XAxisDir = FVector2D::ZeroVector;
|
|
YAxisDir = FVector2D::ZeroVector;
|
|
ZAxisDir = FVector2D::ZeroVector;
|
|
DragStartPos = FVector2D::ZeroVector;
|
|
LastDragPos = FVector2D::ZeroVector;
|
|
}
|
|
|
|
extern ENGINE_API void StringSize(UFont* Font, int32& XL, int32& YL, const TCHAR* Text, FCanvas* Canvas);
|
|
|
|
void FWidget::SetUsesEditorModeTools(FEditorModeTools* InEditorModeTools)
|
|
{
|
|
EditorModeTools = InEditorModeTools;
|
|
}
|
|
|
|
void FWidget::ConvertMouseToAxis_Translate(FVector2D DragDir, FVector& InOutDelta, FVector& OutDrag) const
|
|
{
|
|
// Get drag delta in widget axis space
|
|
OutDrag = FVector((CurrentAxis & EAxisList::X) ? FVector2D::DotProduct(XAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Y) ? FVector2D::DotProduct(YAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Z) ? FVector2D::DotProduct(ZAxisDir, DragDir) : 0.0f);
|
|
|
|
// Snap to grid in widget axis space
|
|
const FVector GridSize = FVector(GEditor->GetGridSize());
|
|
FSnappingUtils::SnapPointToGrid(OutDrag, GridSize);
|
|
|
|
// Convert to effective screen space delta, and replace input delta, adjusted for inverted screen space Y axis
|
|
const FVector2D EffectiveDelta = OutDrag.X * XAxisDir + OutDrag.Y * YAxisDir + OutDrag.Z * ZAxisDir;
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
|
|
// Transform drag delta into world space
|
|
OutDrag = CustomCoordSystem.TransformPosition(OutDrag);
|
|
}
|
|
|
|
void FWidget::ConvertMouseToAxis_Rotate(FVector2D TangentDir, FVector2D DragDir, FSceneView* InView,
|
|
FEditorViewportClient* InViewportClient, FVector& InOutDelta,
|
|
FRotator& OutRotation)
|
|
{
|
|
if (CurrentAxis == EAxisList::X)
|
|
{
|
|
FRotator Rotation;
|
|
FVector2D EffectiveDelta;
|
|
// Get screen direction representing positive rotation
|
|
const FVector2D AxisDir = bIsOrthoDrawingFullRing ? TangentDir : XAxisDir;
|
|
|
|
// Get rotation in widget local space
|
|
Rotation = FRotator(0, 0, FVector2D::DotProduct(AxisDir, DragDir));
|
|
FSnappingUtils::SnapRotatorToGrid(Rotation);
|
|
|
|
// Record delta rotation (used by the widget to render the accumulated delta)
|
|
CurrentDeltaRotation = Rotation.Roll;
|
|
|
|
// Use to calculate the new input delta
|
|
EffectiveDelta = AxisDir * Rotation.Roll;
|
|
// Adjust the input delta according to how much rotation was actually applied
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
// Need to get the delta rotation in the current coordinate space of the widget
|
|
OutRotation = (CustomCoordSystem.Inverse() * FRotationMatrix(Rotation) * CustomCoordSystem).Rotator();
|
|
}
|
|
else if (CurrentAxis == EAxisList::Y)
|
|
{
|
|
FRotator Rotation;
|
|
FVector2D EffectiveDelta;
|
|
// TODO: Determine why -TangentDir is necessary here, and fix whatever is causing it
|
|
const FVector2D AxisDir = bIsOrthoDrawingFullRing ? -TangentDir : YAxisDir;
|
|
|
|
Rotation = FRotator(FVector2D::DotProduct(AxisDir, DragDir), 0, 0);
|
|
FSnappingUtils::SnapRotatorToGrid(Rotation);
|
|
|
|
CurrentDeltaRotation = Rotation.Pitch;
|
|
EffectiveDelta = AxisDir * Rotation.Pitch;
|
|
// Adjust the input delta according to how much rotation was actually applied
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
// Need to get the delta rotation in the current coordinate space of the widget
|
|
OutRotation = (CustomCoordSystem.Inverse() * FRotationMatrix(Rotation) * CustomCoordSystem).Rotator();
|
|
}
|
|
else if (CurrentAxis == EAxisList::Z)
|
|
{
|
|
FRotator Rotation;
|
|
FVector2D EffectiveDelta;
|
|
const FVector2D AxisDir = bIsOrthoDrawingFullRing ? TangentDir : ZAxisDir;
|
|
|
|
Rotation = FRotator(0, FVector2D::DotProduct(AxisDir, DragDir), 0);
|
|
FSnappingUtils::SnapRotatorToGrid(Rotation);
|
|
|
|
CurrentDeltaRotation = Rotation.Yaw;
|
|
EffectiveDelta = AxisDir * Rotation.Yaw;
|
|
// Adjust the input delta according to how much rotation was actually applied
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
// Need to get the delta rotation in the current coordinate space of the widget
|
|
OutRotation = (CustomCoordSystem.Inverse() * FRotationMatrix(Rotation) * CustomCoordSystem).Rotator();
|
|
}
|
|
else if (CurrentAxis == EAxisList::XYZ) // arcball rotate
|
|
{
|
|
// To do this we need to calculate the rotation axis and rotation angle.
|
|
// The Axis is the cross product of the current ray from eye to pixel in world space with the previous ray.
|
|
// The Angle is angle amount we rotate from the object's location to the imaginary sphere that matches up with the difference between the current and previous ray
|
|
// From the Camera. Those rays form a triangle, which we can bisect with a common side.
|
|
FVector2D MousePosition(InViewportClient->Viewport->GetMouseX(), InViewportClient->Viewport->GetMouseY());
|
|
FViewportCursorLocation OldMouseViewportRay(InView, InViewportClient, LastDragPos.X, LastDragPos.Y);
|
|
FViewportCursorLocation MouseViewportRay(InView, InViewportClient, MousePosition.X, MousePosition.Y);
|
|
|
|
LastDragPos = MousePosition;
|
|
FVector DirectionToWidget = InViewportClient->GetWidgetLocation() - MouseViewportRay.GetOrigin();
|
|
float Length = DirectionToWidget.Size();
|
|
if (!FMath::IsNearlyZero(Length))
|
|
{
|
|
// Calc Axis
|
|
DirectionToWidget /= Length;
|
|
const FVector CameraToPixelDir = MouseViewportRay.GetDirection();
|
|
const FVector OldCameraToPixelDir = OldMouseViewportRay.GetDirection();
|
|
FVector RotationAxis = FVector::CrossProduct(OldCameraToPixelDir, CameraToPixelDir);
|
|
RotationAxis.Normalize();
|
|
float RotationAngle = 0.0f;
|
|
FVector4 ScreenLocation = InView->WorldToScreen(InViewportClient->GetWidgetLocation());
|
|
FVector2D PixelLocation;
|
|
InView->ScreenToPixel(ScreenLocation, PixelLocation);
|
|
float Distance = FVector2D::Distance(PixelLocation, MousePosition);
|
|
const float MaxDiff = 2.0f * (INNER_AXIS_CIRCLE_RADIUS);
|
|
// If outside radius, do screen rotate instead, like other DCC's
|
|
|
|
if (Distance > MaxDiff)
|
|
{
|
|
FPlane Plane(InViewportClient->GetWidgetLocation(), DirectionToWidget);
|
|
FVector StartOnPlane =
|
|
FMath::RayPlaneIntersection(MouseViewportRay.GetOrigin(), CameraToPixelDir, Plane);
|
|
FVector OldOnPlane =
|
|
FMath::RayPlaneIntersection(MouseViewportRay.GetOrigin(), OldCameraToPixelDir, Plane);
|
|
StartOnPlane -= InViewportClient->GetWidgetLocation();
|
|
OldOnPlane -= InViewportClient->GetWidgetLocation();
|
|
StartOnPlane.Normalize();
|
|
OldOnPlane.Normalize();
|
|
RotationAngle = FMath::Acos(FVector::DotProduct(StartOnPlane, OldOnPlane));
|
|
|
|
FVector Cross = FVector::CrossProduct(OldCameraToPixelDir, CameraToPixelDir);
|
|
if (FVector::DotProduct(DirectionToWidget, Cross) < 0.0f)
|
|
{
|
|
RotationAngle *= -1.0f;
|
|
}
|
|
RotationAxis = DirectionToWidget;
|
|
}
|
|
else
|
|
{
|
|
const float Scale = ScreenLocation.W *
|
|
(4.0f / InView->UnscaledViewRect.Width() / InView->ViewMatrices.GetProjectionMatrix().M[0][0]);
|
|
const float InnerRadius = (INNER_AXIS_CIRCLE_RADIUS * Scale) +
|
|
GetDefault<ULevelEditorViewportSettings>()->TransformWidgetSizeAdjustment;
|
|
const float LengthOfAdjacent = Length - InnerRadius;
|
|
RotationAngle = FMath::Acos(FVector::DotProduct(OldCameraToPixelDir, CameraToPixelDir));
|
|
const float OppositeSize = FMath::Tan(RotationAngle) * LengthOfAdjacent;
|
|
RotationAngle = FMath::Atan(OppositeSize / InnerRadius);
|
|
RotationAngle = RotationAngle < 0.0f ? RotationAngle : -RotationAngle;
|
|
}
|
|
|
|
const FQuat QuatRotation(RotationAxis, RotationAngle);
|
|
OutRotation = FRotator(QuatRotation);
|
|
}
|
|
return;
|
|
}
|
|
else if (CurrentAxis == EAxisList::Screen)
|
|
{
|
|
FVector2D MousePosition(InViewportClient->Viewport->GetMouseX(), InViewportClient->Viewport->GetMouseY());
|
|
FViewportCursorLocation OldMouseViewportRay(InView, InViewportClient, LastDragPos.X, LastDragPos.Y);
|
|
FViewportCursorLocation MouseViewportRay(InView, InViewportClient, MousePosition.X, MousePosition.Y);
|
|
|
|
LastDragPos = MousePosition;
|
|
FVector DirectionToWidget = InViewportClient->GetWidgetLocation() - MouseViewportRay.GetOrigin();
|
|
float Length = DirectionToWidget.Size();
|
|
|
|
if (!FMath::IsNearlyZero(Length))
|
|
{
|
|
DirectionToWidget /= Length;
|
|
|
|
const FVector CameraToPixelDir = MouseViewportRay.GetDirection();
|
|
const FVector OldCameraToPixelDir = OldMouseViewportRay.GetDirection();
|
|
FPlane Plane(InViewportClient->GetWidgetLocation(), DirectionToWidget);
|
|
FVector StartOnPlane = FMath::RayPlaneIntersection(MouseViewportRay.GetOrigin(), CameraToPixelDir, Plane);
|
|
FVector OldOnPlane = FMath::RayPlaneIntersection(MouseViewportRay.GetOrigin(), OldCameraToPixelDir, Plane);
|
|
StartOnPlane -= InViewportClient->GetWidgetLocation();
|
|
OldOnPlane -= InViewportClient->GetWidgetLocation();
|
|
StartOnPlane.Normalize();
|
|
OldOnPlane.Normalize();
|
|
float RotationAngle = FMath::Acos(FVector::DotProduct(StartOnPlane, OldOnPlane));
|
|
FVector Cross = FVector::CrossProduct(OldCameraToPixelDir, CameraToPixelDir);
|
|
if (FVector::DotProduct(DirectionToWidget, Cross) < 0.0f)
|
|
{
|
|
RotationAngle *= -1.0f;
|
|
}
|
|
const FQuat QuatRotation(DirectionToWidget, RotationAngle);
|
|
OutRotation = FRotator(QuatRotation);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void FWidget::ConvertMouseToAxis_Scale(FVector2D DragDir, FVector& InOutDelta, FVector& OutScale)
|
|
{
|
|
FVector2D AxisDir = FVector2D::ZeroVector;
|
|
|
|
if (CurrentAxis & EAxisList::X)
|
|
{
|
|
AxisDir += XAxisDir;
|
|
}
|
|
|
|
if (CurrentAxis & EAxisList::Y)
|
|
{
|
|
AxisDir += YAxisDir;
|
|
}
|
|
|
|
if (CurrentAxis & EAxisList::Z)
|
|
{
|
|
AxisDir += ZAxisDir;
|
|
}
|
|
|
|
AxisDir.Normalize();
|
|
const float ScaleDelta = FVector2D::DotProduct(AxisDir, DragDir);
|
|
|
|
OutScale =
|
|
FVector((CurrentAxis & EAxisList::X) ? ScaleDelta : 0.0f, (CurrentAxis & EAxisList::Y) ? ScaleDelta : 0.0f,
|
|
(CurrentAxis & EAxisList::Z) ? ScaleDelta : 0.0f);
|
|
|
|
// Snap to grid in widget axis space
|
|
const FVector GridSize = FVector(GEditor->GetGridSize());
|
|
FSnappingUtils::SnapScale(OutScale, GridSize);
|
|
|
|
// Convert to effective screen space delta, and replace input delta, adjusted for inverted screen space Y axis
|
|
const float ScaleMax = OutScale.GetMax();
|
|
const float ScaleMin = OutScale.GetMin();
|
|
const float ScaleApplied = (ScaleMax > -ScaleMin) ? ScaleMax : ScaleMin;
|
|
const FVector2D EffectiveDelta = AxisDir * ScaleApplied;
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
}
|
|
|
|
void FWidget::ConvertMouseToAxis_TranslateRotateZ(FVector2D TangentDir, FVector2D DragDir, FVector& InOutDelta,
|
|
FVector& OutDrag, FRotator& OutRotation)
|
|
{
|
|
if (CurrentAxis == EAxisList::ZRotation)
|
|
{
|
|
const FVector2D AxisDir = bIsOrthoDrawingFullRing ? TangentDir : ZAxisDir;
|
|
FRotator Rotation = FRotator(0, FVector2D::DotProduct(AxisDir, DragDir), 0);
|
|
FSnappingUtils::SnapRotatorToGrid(Rotation);
|
|
CurrentDeltaRotation = Rotation.Yaw;
|
|
|
|
const FVector2D EffectiveDelta = AxisDir * Rotation.Yaw;
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
|
|
OutRotation = (CustomCoordSystem.Inverse() * FRotationMatrix(Rotation) * CustomCoordSystem).Rotator();
|
|
}
|
|
else
|
|
{
|
|
// Get drag delta in widget axis space
|
|
OutDrag = FVector((CurrentAxis & EAxisList::X) ? FVector2D::DotProduct(XAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Y) ? FVector2D::DotProduct(YAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Z) ? FVector2D::DotProduct(ZAxisDir, DragDir) : 0.0f);
|
|
|
|
// Snap to grid in widget axis space
|
|
const FVector GridSize = FVector(GEditor->GetGridSize());
|
|
FSnappingUtils::SnapPointToGrid(OutDrag, GridSize);
|
|
|
|
// Convert to effective screen space delta, and replace input delta, adjusted for inverted screen space Y axis
|
|
const FVector2D EffectiveDelta = OutDrag.X * XAxisDir + OutDrag.Y * YAxisDir + OutDrag.Z * ZAxisDir;
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
|
|
// Transform drag delta into world space
|
|
OutDrag = CustomCoordSystem.TransformPosition(OutDrag);
|
|
}
|
|
}
|
|
|
|
void FWidget::ConvertMouseToAxis_WM_2D(FVector2D TangentDir, FVector2D DragDir, FVector& InOutDelta, FVector& OutDrag,
|
|
FRotator& OutRotation)
|
|
{
|
|
if (CurrentAxis == EAxisList::Rotate2D)
|
|
{
|
|
// TODO: Determine why -TangentDir is necessary here, and fix whatever is causing it
|
|
const FVector2D AxisDir = bIsOrthoDrawingFullRing ? -TangentDir : YAxisDir;
|
|
|
|
FRotator Rotation = FRotator(FVector2D::DotProduct(AxisDir, DragDir), 0, 0);
|
|
FSnappingUtils::SnapRotatorToGrid(Rotation);
|
|
|
|
CurrentDeltaRotation = Rotation.Pitch;
|
|
FVector2D EffectiveDelta = AxisDir * Rotation.Pitch;
|
|
|
|
// Adjust the input delta according to how much rotation was actually applied
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
|
|
// Need to get the delta rotation in the current coordinate space of the widget
|
|
OutRotation = (CustomCoordSystem.Inverse() * FRotationMatrix(Rotation) * CustomCoordSystem).Rotator();
|
|
}
|
|
else
|
|
{
|
|
// Get drag delta in widget axis space
|
|
OutDrag = FVector((CurrentAxis & EAxisList::X) ? FVector2D::DotProduct(XAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Y) ? FVector2D::DotProduct(YAxisDir, DragDir) : 0.0f,
|
|
(CurrentAxis & EAxisList::Z) ? FVector2D::DotProduct(ZAxisDir, DragDir) : 0.0f);
|
|
|
|
// Snap to grid in widget axis space
|
|
const FVector GridSize = FVector(GEditor->GetGridSize());
|
|
FSnappingUtils::SnapPointToGrid(OutDrag, GridSize);
|
|
|
|
// Convert to effective screen space delta, and replace input delta, adjusted for inverted screen space Y axis
|
|
const FVector2D EffectiveDelta = OutDrag.X * XAxisDir + OutDrag.Y * YAxisDir + OutDrag.Z * ZAxisDir;
|
|
InOutDelta = FVector(EffectiveDelta.X, -EffectiveDelta.Y, 0.0f);
|
|
|
|
// Transform drag delta into world space
|
|
OutDrag = CustomCoordSystem.TransformPosition(OutDrag);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts mouse movement on the screen to widget axis movement/rotation.
|
|
*/
|
|
void FWidget::ConvertMouseMovementToAxisMovement(FSceneView* InView, FEditorViewportClient* InViewportClient,
|
|
bool bInUsedDragModifier, FVector& InOutDelta, FVector& OutDrag,
|
|
FRotator& OutRotation, FVector& OutScale)
|
|
{
|
|
OutDrag = FVector::ZeroVector;
|
|
OutRotation = FRotator::ZeroRotator;
|
|
OutScale = FVector::ZeroVector;
|
|
|
|
const int32 WidgetMode = InViewportClient->GetWidgetMode();
|
|
|
|
// Get input delta as 2D vector, adjusted for inverted screen space Y axis
|
|
const FVector2D DragDir = FVector2D(InOutDelta.X, -InOutDelta.Y);
|
|
|
|
// Get offset of the drag start position from the widget origin
|
|
const FVector2D DirectionToMousePos = FVector2D(DragStartPos - Origin).GetSafeNormal();
|
|
|
|
// For rotations which display as a full ring, calculate the tangent direction representing a clockwise movement
|
|
FVector2D TangentDir = bInUsedDragModifier ?
|
|
// If a drag modifier has been used, this implies we are not actually touching the widget, so don't attempt to
|
|
// calculate the tangent dir based on the relative offset of the cursor from the widget location.
|
|
FVector2D(1, 1).GetSafeNormal() :
|
|
// Treat the tangent dir as perpendicular to the relative offset of the cursor from the widget location.
|
|
FVector2D(-DirectionToMousePos.Y, DirectionToMousePos.X);
|
|
|
|
switch (WidgetMode)
|
|
{
|
|
case WM_Translate:
|
|
ConvertMouseToAxis_Translate(DragDir, InOutDelta, OutDrag);
|
|
break;
|
|
case WM_Rotate:
|
|
ConvertMouseToAxis_Rotate(TangentDir, DragDir, InView, InViewportClient, InOutDelta, OutRotation);
|
|
break;
|
|
case WM_Scale:
|
|
ConvertMouseToAxis_Scale(DragDir, InOutDelta, OutScale);
|
|
break;
|
|
case WM_TranslateRotateZ:
|
|
ConvertMouseToAxis_TranslateRotateZ(TangentDir, DragDir, InOutDelta, OutDrag, OutRotation);
|
|
break;
|
|
case WM_2D:
|
|
ConvertMouseToAxis_WM_2D(TangentDir, DragDir, InOutDelta, OutDrag, OutRotation);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For axis movement, get the "best" planar normal and axis mask
|
|
* @param InAxis - Axis of movement
|
|
* @param InDirToPixel -
|
|
* @param OutPlaneNormal - Normal of the plane to project the mouse onto
|
|
* @param OutMask - Used to mask out the component of the planar movement we want
|
|
*/
|
|
void GetAxisPlaneNormalAndMask(const FMatrix& InCoordSystem, const FVector& InAxis, const FVector& InDirToPixel,
|
|
FVector& OutPlaneNormal, FVector& NormalToRemove)
|
|
{
|
|
FVector XAxis = InCoordSystem.TransformVector(FVector(1, 0, 0));
|
|
FVector YAxis = InCoordSystem.TransformVector(FVector(0, 1, 0));
|
|
FVector ZAxis = InCoordSystem.TransformVector(FVector(0, 0, 1));
|
|
|
|
float XDot = FMath::Abs(InDirToPixel | XAxis);
|
|
float YDot = FMath::Abs(InDirToPixel | YAxis);
|
|
float ZDot = FMath::Abs(InDirToPixel | ZAxis);
|
|
|
|
if ((InAxis | XAxis) > .1f)
|
|
{
|
|
OutPlaneNormal = (YDot > ZDot) ? YAxis : ZAxis;
|
|
NormalToRemove = (YDot > ZDot) ? ZAxis : YAxis;
|
|
}
|
|
else if ((InAxis | YAxis) > .1f)
|
|
{
|
|
OutPlaneNormal = (XDot > ZDot) ? XAxis : ZAxis;
|
|
NormalToRemove = (XDot > ZDot) ? ZAxis : XAxis;
|
|
}
|
|
else
|
|
{
|
|
OutPlaneNormal = (XDot > YDot) ? XAxis : YAxis;
|
|
NormalToRemove = (XDot > YDot) ? YAxis : XAxis;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For planar movement, get the "best" planar normal and axis mask
|
|
* @param InAxis - Axis of movement
|
|
* @param OutPlaneNormal - Normal of the plane to project the mouse onto
|
|
* @param OutMask - Used to mask out the component of the planar movement we want
|
|
*/
|
|
void GetPlaneNormalAndMask(const FVector& InAxis, FVector& OutPlaneNormal, FVector& NormalToRemove)
|
|
{
|
|
OutPlaneNormal = InAxis;
|
|
NormalToRemove = InAxis;
|
|
}
|
|
|
|
void FWidget::AbsoluteConvertMouseToAxis_Translate(FSceneView* InView, const FMatrix& InputCoordSystem,
|
|
FAbsoluteMovementParams& InOutParams, FVector& OutDrag)
|
|
{
|
|
switch (CurrentAxis)
|
|
{
|
|
case EAxisList::X:
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.XAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::Y:
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.YAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::Z:
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.ZAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::XY:
|
|
GetPlaneNormalAndMask(InOutParams.ZAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::XZ:
|
|
GetPlaneNormalAndMask(InOutParams.YAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::YZ:
|
|
GetPlaneNormalAndMask(InOutParams.XAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
break;
|
|
case EAxisList::Screen:
|
|
InOutParams.XAxis = InView->ViewMatrices.GetViewMatrix().GetColumn(0);
|
|
InOutParams.YAxis = InView->ViewMatrices.GetViewMatrix().GetColumn(1);
|
|
InOutParams.ZAxis = InView->ViewMatrices.GetViewMatrix().GetColumn(2);
|
|
GetPlaneNormalAndMask(InOutParams.ZAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
// do not damp the movement in this case, we also want to snap
|
|
InOutParams.bMovementLockedToCamera = false;
|
|
break;
|
|
}
|
|
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
}
|
|
|
|
void FWidget::AbsoluteConvertMouseToAxis_WM_2D(const FMatrix& InputCoordSystem, FAbsoluteMovementParams& InOutParams,
|
|
FVector& OutDrag, FRotator& OutRotation)
|
|
{
|
|
switch (CurrentAxis)
|
|
{
|
|
case EAxisList::X: {
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.XAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
case EAxisList::Z: {
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.ZAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
case EAxisList::XZ: {
|
|
GetPlaneNormalAndMask(InOutParams.YAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
|
|
// Rotate about the y-axis
|
|
case EAxisList::Rotate2D: {
|
|
// no position snapping, we'll handle the rotation snapping elsewhere
|
|
InOutParams.bPositionSnapping = false;
|
|
|
|
GetPlaneNormalAndMask(InOutParams.YAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
// No DAMPING
|
|
InOutParams.bMovementLockedToCamera = false;
|
|
// this is the one movement type where we want to always use the widget origin and
|
|
// NOT the "first click" origin
|
|
FVector XZPlaneProjectedPosition = GetAbsoluteTranslationDelta(InOutParams) + InitialTranslationOffset;
|
|
|
|
// remove the component along the normal we want to mute
|
|
float MovementAlongMutedAxis = XZPlaneProjectedPosition | InOutParams.NormalToRemove;
|
|
XZPlaneProjectedPosition = XZPlaneProjectedPosition - (InOutParams.NormalToRemove * MovementAlongMutedAxis);
|
|
|
|
if (!XZPlaneProjectedPosition.Normalize())
|
|
{
|
|
XZPlaneProjectedPosition = InOutParams.YAxis;
|
|
}
|
|
|
|
// NOW, find the rotation around the PlaneNormal to make the xaxis point at InDrag
|
|
OutRotation = FRotator::ZeroRotator;
|
|
|
|
float PitchDegrees = -FMath::Atan2(-XZPlaneProjectedPosition.Z, XZPlaneProjectedPosition.X) * 180.f / PI;
|
|
OutRotation.Pitch = PitchDegrees - (EditorModeTools ? EditorModeTools->TranslateRotate2DAngle : 0);
|
|
|
|
if (bSnapEnabled)
|
|
{
|
|
FSnappingUtils::SnapRotatorToGrid(OutRotation);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
void FWidget::AbsoluteConvertMouseToAxis_TranslateRotateZ(const FMatrix& InputCoordSystem,
|
|
FAbsoluteMovementParams& InOutParams, FVector& OutDrag,
|
|
FRotator& OutRotation)
|
|
{
|
|
FVector LineToUse;
|
|
switch (CurrentAxis)
|
|
{
|
|
case EAxisList::X: {
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.XAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
case EAxisList::Y: {
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.YAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
case EAxisList::Z: {
|
|
GetAxisPlaneNormalAndMask(InputCoordSystem, InOutParams.ZAxis, InOutParams.CameraDir, InOutParams.PlaneNormal,
|
|
InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
case EAxisList::XY: {
|
|
GetPlaneNormalAndMask(InOutParams.ZAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
OutDrag = GetAbsoluteTranslationDelta(InOutParams);
|
|
break;
|
|
}
|
|
// Rotate about the z-axis
|
|
case EAxisList::ZRotation: {
|
|
// no position snapping, we'll handle the rotation snapping elsewhere
|
|
InOutParams.bPositionSnapping = false;
|
|
|
|
// find new point on the
|
|
GetPlaneNormalAndMask(InOutParams.ZAxis, InOutParams.PlaneNormal, InOutParams.NormalToRemove);
|
|
// No DAMPING
|
|
InOutParams.bMovementLockedToCamera = false;
|
|
// this is the one movement type where we want to always use the widget origin and
|
|
// NOT the "first click" origin
|
|
FVector XYPlaneProjectedPosition = GetAbsoluteTranslationDelta(InOutParams) + InitialTranslationOffset;
|
|
|
|
// remove the component along the normal we want to mute
|
|
float MovementAlongMutedAxis = XYPlaneProjectedPosition | InOutParams.NormalToRemove;
|
|
XYPlaneProjectedPosition = XYPlaneProjectedPosition - (InOutParams.NormalToRemove * MovementAlongMutedAxis);
|
|
|
|
if (!XYPlaneProjectedPosition.Normalize())
|
|
{
|
|
XYPlaneProjectedPosition = InOutParams.XAxis;
|
|
}
|
|
|
|
// NOW, find the rotation around the PlaneNormal to make the xaxis point at InDrag
|
|
OutRotation = FRotator::ZeroRotator;
|
|
|
|
OutRotation.Yaw = XYPlaneProjectedPosition.Rotation().Yaw -
|
|
(EditorModeTools ? EditorModeTools->TranslateRotateXAxisAngle : 0);
|
|
|
|
if (bSnapEnabled)
|
|
{
|
|
FSnappingUtils::SnapRotatorToGrid(OutRotation);
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Absolute Translation conversion from mouse movement on the screen to widget axis movement/rotation.
|
|
*/
|
|
void FWidget::AbsoluteTranslationConvertMouseMovementToAxisMovement(FSceneView* InView,
|
|
FEditorViewportClient* InViewportClient,
|
|
const FVector& InLocation,
|
|
const FVector2D& InMousePosition, FVector& OutDrag,
|
|
FRotator& OutRotation, FVector& OutScale)
|
|
{
|
|
// Compute a world space ray from the screen space mouse coordinates
|
|
FViewportCursorLocation MouseViewportRay(InView, InViewportClient, InMousePosition.X, InMousePosition.Y);
|
|
|
|
FAbsoluteMovementParams Params;
|
|
Params.EyePos = MouseViewportRay.GetOrigin();
|
|
Params.PixelDir = MouseViewportRay.GetDirection();
|
|
Params.CameraDir = InView->GetViewDirection();
|
|
Params.Position = InLocation;
|
|
// dampen by
|
|
Params.bMovementLockedToCamera = InViewportClient->IsShiftPressed();
|
|
Params.bPositionSnapping = true;
|
|
|
|
FMatrix InputCoordSystem = InViewportClient->GetWidgetCoordSystem();
|
|
|
|
Params.XAxis = InputCoordSystem.TransformVector(FVector(1, 0, 0));
|
|
Params.YAxis = InputCoordSystem.TransformVector(FVector(0, 1, 0));
|
|
Params.ZAxis = InputCoordSystem.TransformVector(FVector(0, 0, 1));
|
|
|
|
switch (InViewportClient->GetWidgetMode())
|
|
{
|
|
case WM_Translate:
|
|
AbsoluteConvertMouseToAxis_Translate(InView, InputCoordSystem, Params, OutDrag);
|
|
break;
|
|
|
|
case WM_2D:
|
|
AbsoluteConvertMouseToAxis_WM_2D(InputCoordSystem, Params, OutDrag, OutRotation);
|
|
break;
|
|
case WM_TranslateRotateZ:
|
|
AbsoluteConvertMouseToAxis_TranslateRotateZ(InputCoordSystem, Params, OutDrag, OutRotation);
|
|
break;
|
|
case WM_Rotate:
|
|
case WM_Scale:
|
|
case WM_None:
|
|
case WM_Max:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Only some modes support Absolute Translation Movement */
|
|
bool FWidget::AllowsAbsoluteTranslationMovement(EWidgetMode WidgetMode)
|
|
{
|
|
if ((WidgetMode == WM_Translate) || (WidgetMode == WM_TranslateRotateZ) || (WidgetMode == WM_2D))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Only some modes support Absolute Rotation Movement/arcball*/
|
|
bool FWidget::AllowsAbsoluteRotationMovement(EWidgetMode WidgetMode, EAxisList::Type InAxisType)
|
|
{
|
|
if (WidgetMode == WM_Rotate && (InAxisType == EAxisList::XYZ || InAxisType == EAxisList::Screen))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Serializes the widget references so they don't get garbage collected.
|
|
*
|
|
* @param Ar FArchive to serialize with
|
|
*/
|
|
void FWidget::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
Collector.AddReferencedObject(AxisMaterialX);
|
|
Collector.AddReferencedObject(AxisMaterialY);
|
|
Collector.AddReferencedObject(AxisMaterialZ);
|
|
Collector.AddReferencedObject(OpaquePlaneMaterialXY);
|
|
Collector.AddReferencedObject(TransparentPlaneMaterialXY);
|
|
Collector.AddReferencedObject(GridMaterial);
|
|
Collector.AddReferencedObject(CurrentAxisMaterial);
|
|
}
|
|
|
|
#define CAMERA_LOCK_DAMPING_FACTOR .1f
|
|
#define MAX_CAMERA_MOVEMENT_SPEED 512.0f
|
|
/**
|
|
* Returns the Delta from the current position that the absolute movement system wants the object to be at
|
|
* @param InParams - Structure containing all the information needed for absolute movement
|
|
* @return - The requested delta from the current position
|
|
*/
|
|
FVector FWidget::GetAbsoluteTranslationDelta(const FAbsoluteMovementParams& InParams)
|
|
{
|
|
FPlane MovementPlane(InParams.Position, InParams.PlaneNormal);
|
|
FVector ProposedEndofEyeVector =
|
|
InParams.EyePos + (InParams.PixelDir * (InParams.Position - InParams.EyePos).Size());
|
|
|
|
// default to not moving
|
|
FVector RequestedPosition = InParams.Position;
|
|
|
|
float DotProductWithPlaneNormal = InParams.PixelDir | InParams.PlaneNormal;
|
|
// check to make sure we're not co-planar
|
|
if (FMath::Abs(DotProductWithPlaneNormal) > DELTA)
|
|
{
|
|
// Get closest point on plane
|
|
RequestedPosition = FMath::LinePlaneIntersection(InParams.EyePos, ProposedEndofEyeVector, MovementPlane);
|
|
}
|
|
|
|
// drag is a delta position, so just update the different between the previous position and the new position
|
|
FVector DeltaPosition = RequestedPosition - InParams.Position;
|
|
|
|
// Retrieve the initial offset, passing in the current requested position and the current position
|
|
FVector InitialOffset = GetAbsoluteTranslationInitialOffset(RequestedPosition, InParams.Position);
|
|
|
|
// subtract off the initial offset (where the widget was clicked) to prevent popping
|
|
DeltaPosition -= InitialOffset;
|
|
|
|
// remove the component along the normal we want to mute
|
|
float MovementAlongMutedAxis = DeltaPosition | InParams.NormalToRemove;
|
|
FVector OutDrag = DeltaPosition - (InParams.NormalToRemove * MovementAlongMutedAxis);
|
|
|
|
if (InParams.bMovementLockedToCamera)
|
|
{
|
|
// DAMPEN ABSOLUTE MOVEMENT when the camera is locked to the object
|
|
OutDrag *= CAMERA_LOCK_DAMPING_FACTOR;
|
|
OutDrag.X = FMath::Clamp(OutDrag.X, -MAX_CAMERA_MOVEMENT_SPEED, MAX_CAMERA_MOVEMENT_SPEED);
|
|
OutDrag.Y = FMath::Clamp(OutDrag.Y, -MAX_CAMERA_MOVEMENT_SPEED, MAX_CAMERA_MOVEMENT_SPEED);
|
|
OutDrag.Z = FMath::Clamp(OutDrag.Z, -MAX_CAMERA_MOVEMENT_SPEED, MAX_CAMERA_MOVEMENT_SPEED);
|
|
}
|
|
|
|
// the they requested position snapping and we're not moving with the camera
|
|
if (InParams.bPositionSnapping && !InParams.bMovementLockedToCamera && bSnapEnabled)
|
|
{
|
|
FVector MovementAlongAxis =
|
|
FVector(OutDrag | InParams.XAxis, OutDrag | InParams.YAxis, OutDrag | InParams.ZAxis);
|
|
// translation (either xy plane or z)
|
|
FSnappingUtils::SnapPointToGrid(
|
|
MovementAlongAxis, FVector(GEditor->GetGridSize(), GEditor->GetGridSize(), GEditor->GetGridSize()));
|
|
OutDrag = MovementAlongAxis.X * InParams.XAxis + MovementAlongAxis.Y * InParams.YAxis +
|
|
MovementAlongAxis.Z * InParams.ZAxis;
|
|
}
|
|
|
|
// get the distance from the original position to the new proposed position
|
|
FVector DeltaFromStart = InParams.Position + OutDrag - InitialTranslationPosition;
|
|
|
|
// Get the vector from the eye to the proposed new position (to make sure it's not behind the camera
|
|
FVector EyeToNewPosition = (InParams.Position + OutDrag) - InParams.EyePos;
|
|
float BehindTheCameraDotProduct = EyeToNewPosition | InParams.CameraDir;
|
|
|
|
// Don't let the requested position go behind the camera
|
|
if (BehindTheCameraDotProduct <= 0)
|
|
{
|
|
OutDrag = OutDrag.ZeroVector;
|
|
}
|
|
return OutDrag;
|
|
}
|
|
|
|
/**
|
|
* Returns the offset from the initial selection point
|
|
*/
|
|
FVector FWidget::GetAbsoluteTranslationInitialOffset(const FVector& InNewPosition, const FVector& InCurrentPosition)
|
|
{
|
|
if (!bAbsoluteTranslationInitialOffsetCached)
|
|
{
|
|
bAbsoluteTranslationInitialOffsetCached = true;
|
|
InitialTranslationOffset = InNewPosition - InCurrentPosition;
|
|
InitialTranslationPosition = InCurrentPosition;
|
|
}
|
|
return InitialTranslationOffset;
|
|
}
|
|
|
|
/**
|
|
* Returns true if we're in Local Space editing mode
|
|
*/
|
|
bool FWidget::IsRotationLocalSpace() const
|
|
{
|
|
return (CustomCoordSystemSpace == COORD_Local);
|
|
}
|
|
|
|
void FWidget::UpdateDeltaRotation()
|
|
{
|
|
TotalDeltaRotation += CurrentDeltaRotation;
|
|
if ((TotalDeltaRotation <= -360.f) || (TotalDeltaRotation >= 360.f))
|
|
{
|
|
TotalDeltaRotation = FRotator::ClampAxis(TotalDeltaRotation);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the angle in degrees representation of how far we have just rotated
|
|
*/
|
|
float FWidget::GetDeltaRotation() const
|
|
{
|
|
return TotalDeltaRotation;
|
|
}
|
|
|
|
uint32 FWidget::GetDominantAxisIndex(const FVector& InDiff, FEditorViewportClient* ViewportClient) const
|
|
{
|
|
uint32 DominantIndex = 0;
|
|
if (FMath::Abs(InDiff.X) < FMath::Abs(InDiff.Y))
|
|
{
|
|
DominantIndex = 1;
|
|
}
|
|
|
|
const int32 WidgetMode = ViewportClient->GetWidgetMode();
|
|
|
|
switch (WidgetMode)
|
|
{
|
|
case WM_Translate:
|
|
switch (ViewportClient->ViewportType)
|
|
{
|
|
case LVT_OrthoXY:
|
|
if (CurrentAxis == EAxisList::X)
|
|
{
|
|
DominantIndex = 0;
|
|
}
|
|
else if (CurrentAxis == EAxisList::Y)
|
|
{
|
|
DominantIndex = 1;
|
|
}
|
|
break;
|
|
case LVT_OrthoXZ:
|
|
if (CurrentAxis == EAxisList::X)
|
|
{
|
|
DominantIndex = 0;
|
|
}
|
|
else if (CurrentAxis == EAxisList::Z)
|
|
{
|
|
DominantIndex = 1;
|
|
}
|
|
break;
|
|
case LVT_OrthoYZ:
|
|
if (CurrentAxis == EAxisList::Y)
|
|
{
|
|
DominantIndex = 0;
|
|
}
|
|
else if (CurrentAxis == EAxisList::Z)
|
|
{
|
|
DominantIndex = 1;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DominantIndex;
|
|
}
|
|
|
|
bool FWidget::IsWidgetDisabled() const
|
|
{
|
|
return EditorModeTools ? (EditorModeTools->IsDefaultModeActive() && GEditor->HasLockedActors()) : false;
|
|
}
|