EM_Task/UnrealEd/Private/UnrealWidget.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

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;
}