Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/UnrealWidgetRender.cpp
2025-05-18 13:04:45 +08:00

1503 lines
67 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UnrealWidget.h"
#include "UnrealWidgetFwd.h"
#include "Materials/Material.h"
#include "CanvasItem.h"
#include "SceneView.h"
#include "Settings/LevelEditorViewportSettings.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "EditorViewportClient.h"
#include "EdMode.h"
#include "EditorModeManager.h"
#include "SnappingUtils.h"
#include "DynamicMeshBuilder.h"
#include "CanvasTypes.h"
#include "EditorInteractiveGizmoManager.h"
#include "TransformGizmoEditorSettings.h"
#include "Misc/AxisDisplayInfo.h"
/*
* Simple struct used to create and group data related to the current window's / viewport's space,
* orientation, and scale.
*/
struct FSpaceDescriptor
{
/*
* Creates a new FSpaceDescriptor.
*
* @param View The virtual view for the space.
* @param Viewport The real viewport for the space.
* @param InLocation The location of the camera in the virtual space.
* @param ExternalScale ExtraScale Factor to apply to the UniformScale to allow for the Widget To Get Scaled.
*/
FSpaceDescriptor(const FSceneView* View, const FEditorViewportClient* Viewport, const FVector& InLocation, const float ExternalScale) :
bIsPerspective(View->ViewMatrices.GetProjectionMatrix().M[3][3] < 1.0f),
bIsLocalSpace(Viewport->GetWidgetCoordSystemSpace() == COORD_Local),
bIsOrthoXY(!bIsPerspective && FMath::Abs(View->ViewMatrices.GetViewMatrix().M[2][2]) > 0.0f),
bIsOrthoXZ(!bIsPerspective && FMath::Abs(View->ViewMatrices.GetViewMatrix().M[1][2]) > 0.0f),
bIsOrthoYZ(!bIsPerspective && FMath::Abs(View->ViewMatrices.GetViewMatrix().M[0][2]) > 0.0f),
UniformScale(ExternalScale * View->WorldToScreen(InLocation).W * (4.0f / View->UnscaledViewRect.Width() / View->ViewMatrices.GetProjectionMatrix().M[0][0])),
Scale(CreateScale())
{
}
// Wether or not the view is perspective.
const bool bIsPerspective;
// Whether or not the view is in local space.
const bool bIsLocalSpace;
// Whether or not the view is orthogonal to the XY plane.
const bool bIsOrthoXY;
// Whether or not the view is orthogonal to the XZ plane.
const bool bIsOrthoXZ;
// Whether or not the view is orthogonal to the YZ plane.
const bool bIsOrthoYZ;
// The uniform scale for the space.
const float UniformScale;
// The scale vector for the space based on orientation.
const FVector Scale;
/*
* Used to determine whether or not the X axis should be drawn.
*
* @param AxisToDraw The desired axis to draw.
*
* @return True if the axis should be drawn. False otherwise.
*/
bool ShouldDrawAxisX(const EAxisList::Type AxisToDraw)
{
return ShouldDrawAxis(EAxisList::X, AxisToDraw, bIsOrthoYZ);
}
/*
* Used to determine whether or not the Y axis should be drawn.
*
* @param AxisToDraw The desired axis to draw.
*
* @return True if the axis should be drawn. False otherwise.
*/
bool ShouldDrawAxisY(const EAxisList::Type AxisToDraw)
{
return ShouldDrawAxis(EAxisList::Y, AxisToDraw, bIsOrthoXZ);
}
/*
* Used to determine whether or not the Z axis should be drawn.
*
* @param AxisToDraw The desired axis to draw.
*
* @return True if the axis should be drawn. False otherwise.
*/
bool ShouldDrawAxisZ(const EAxisList::Type AxisToDraw)
{
return ShouldDrawAxis(EAxisList::Z, AxisToDraw, bIsOrthoXY);
}
private:
/*
* Creates a space scale vector from the determined orientation and uniform scale.
*
* @return Space scale vector.
*/
FVector CreateScale()
{
if (bIsOrthoXY)
{
return FVector(UniformScale, UniformScale, 1.0f);
}
else if (bIsOrthoXZ)
{
return FVector(UniformScale, 1.0f, UniformScale);
}
else if (bIsOrthoYZ)
{
return FVector(1.0f, UniformScale, UniformScale);
}
else
{
return FVector(UniformScale, UniformScale, UniformScale);
}
}
/*
* Used to determine whether or not a specific axis should be drawn.
*
* @param AxisToCheck The axis to check.
* @param AxisToDraw The desired axis to draw.
* @param bIsOrtho Whether or not the axis to check is orthogonal to the viewing orientation.
*
* @return True if the axis should be drawn. False otherwise.
*/
bool ShouldDrawAxis(const EAxisList::Type AxisToCheck, const EAxisList::Type AxisToDraw, const bool bIsOrtho)
{
return (AxisToCheck & AxisToDraw) && (bIsPerspective || bIsLocalSpace || !bIsOrtho);
}
};
/**
* Renders any widget specific HUD text
* @param Canvas - Canvas to use for 2d rendering
*/
void FWidget::DrawHUD (FCanvas* Canvas)
{
if (HUDString.Len())
{
const float DPIScale = Canvas->GetDPIScale();
int32 StringPosX = FMath::FloorToInt(HUDInfoPos.X/DPIScale);
int32 StringPosY = FMath::FloorToInt(HUDInfoPos.Y/DPIScale);
//measure string size
int32 StringSizeX, StringSizeY;
StringSize(GEngine->GetSmallFont(), StringSizeX, StringSizeY, *HUDString);
//add some padding to the outside
const int32 Border = 5;
int32 FillMinX = StringPosX - Border - (StringSizeX>>1);
int32 FillMinY = StringPosY - Border;// - (StringSizeY>>1);
StringSizeX += 2*Border;
StringSizeY += 2*Border;
//mostly alpha'ed black
FCanvasTileItem TileItem( FVector2D( FillMinX, FillMinY), GWhiteTexture, FVector2D( StringSizeX, StringSizeY ), FLinearColor( 0.0f, 0.0f, 0.0f, .25f ) );
TileItem.BlendMode = SE_BLEND_Translucent;
Canvas->DrawItem( TileItem );
FCanvasTextItem TextItem( FVector2D( StringPosX, StringPosY), FText::FromString( HUDString ), GEngine->GetSmallFont(), FLinearColor::White );
TextItem.bCentreX = true;
Canvas->DrawItem( TextItem );
}
}
void FWidget::Render( const FSceneView* View,FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient )
{
check(ViewportClient);
//reset HUD text
HUDString.Empty();
const bool bShowFlagsSupportsWidgetDrawing = View->Family->EngineShowFlags.ModeWidgets;
const bool bEditorModeToolsSupportsWidgetDrawing = EditorModeTools ? EditorModeTools->GetShowWidget() : true;
// Use legacy widget for TranslateRotateZ or 2D modes or when UseLegacyWidget cvar is true
UE::Widget::EWidgetMode WidgetMode = ViewportClient->GetWidgetMode();
bool bUseLegacyWidget = (WidgetMode == UE::Widget::EWidgetMode::WM_TranslateRotateZ || WidgetMode == UE::Widget::EWidgetMode::WM_2D);
if (!bUseLegacyWidget)
{
bUseLegacyWidget = !UEditorInteractiveGizmoManager::UsesNewTRSGizmos();
}
bool bDrawWidget;
// Because the movement routines use the widget axis to determine how to transform mouse movement into
// editor object movement, we need to still run through the Render routine even though widget drawing may be
// disabled. So we keep a flag that is used to determine whether or not to actually render anything. This way
// we can still update the widget axis' based on the Context's transform matrices, even though drawing is disabled.
if(bDefaultVisibility && bShowFlagsSupportsWidgetDrawing && bEditorModeToolsSupportsWidgetDrawing && bUseLegacyWidget)
{
bDrawWidget = true;
// See if there is a custom coordinate system we should be using, only change it if we are drawing widgets.
CustomCoordSystem = ViewportClient->GetWidgetCoordSystem();
}
else
{
bDrawWidget = false;
}
CustomCoordSystemSpace = ViewportClient->GetWidgetCoordSystemSpace();
// If the current modes don't want to use the widget, don't draw it.
if( EditorModeTools && !EditorModeTools->UsesTransformWidget() )
{
CurrentAxis = EAxisList::None;
return;
}
FVector WidgetLocation = ViewportClient->GetWidgetLocation();
FVector2D NewOrigin;
if (View->ScreenToPixel(View->WorldToScreen(WidgetLocation), NewOrigin))
{
// Only update the viewport-space origin if the position was in front of the camera
Origin = NewOrigin;
}
switch( WidgetMode )
{
case UE::Widget::EWidgetMode::WM_Translate:
Render_Translate(View, PDI, ViewportClient, WidgetLocation, bDrawWidget);
break;
case UE::Widget::EWidgetMode::WM_Rotate:
Render_Rotate(View, PDI, ViewportClient, WidgetLocation, bDrawWidget);
break;
case UE::Widget::EWidgetMode::WM_Scale:
Render_Scale(View, PDI, ViewportClient, WidgetLocation, bDrawWidget);
break;
case UE::Widget::EWidgetMode::WM_TranslateRotateZ:
Render_TranslateRotateZ(View, PDI, ViewportClient, WidgetLocation, bDrawWidget);
break;
case UE::Widget::EWidgetMode::WM_2D:
Render_2D(View, PDI, ViewportClient, WidgetLocation, bDrawWidget);
break;
default:
break;
}
};
/**
* Draws an arrow head line for a specific axis.
*/
void FWidget::Render_Axis(const FSceneView* View, FPrimitiveDrawInterface* PDI, EAxisList::Type InAxis, FMatrix& InMatrix, UMaterialInterface* InMaterial, const FLinearColor& InColor, FVector2D& OutAxisDir, const FVector& InScale, bool bDrawWidget, bool bCubeHead, float AxisLengthOffset)
{
FMatrix AxisRotation = FMatrix::Identity;
const bool bUsingLUFCoordinateSysem = AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward;
if( InAxis == EAxisList::Y )
{
AxisRotation = FRotationMatrix::MakeFromXZ(FVector(0, bUsingLUFCoordinateSysem ? -1 : 1, 0), FVector(0, 0, 1));
}
else if( InAxis == EAxisList::Z )
{
AxisRotation = FRotationMatrix::MakeFromXY(FVector(0, 0, 1), FVector(0, bUsingLUFCoordinateSysem ? -1 : 1, 0));
}
FMatrix ArrowToWorld = AxisRotation * InMatrix;
// The scale that is passed in potentially leaves one component with a scale of 1, if that happens
// we need to extract the inform scale and use it to construct the scale that transforms the primitives
float UniformScale = InScale.GetMax() > 1.0f ? InScale.GetMax() : InScale.GetMin() < 1.0f ? InScale.GetMin() : 1.0f;
// After the primitives have been scaled and transformed, we apply this inverse scale that flattens the dimension
// that was scaled up to prevent it from intersecting with the near plane. In perspective this won't have any effect,
// but in the ortho viewports it will prevent scaling in the direction of the camera and thus intersecting the near plane.
FVector FlattenScale = FVector(InScale.Component(0) == 1.0f ? 1.0f / UniformScale : 1.0f, InScale.Component(1) == 1.0f ? 1.0f / UniformScale : 1.0f, InScale.Component(2) == 1.0f ? 1.0f / UniformScale : 1.0f);
FScaleMatrix Scale(UniformScale);
ArrowToWorld = Scale * ArrowToWorld;
if( bDrawWidget )
{
const bool bDisabled = EditorModeTools ? (EditorModeTools->IsDefaultModeActive() && GEditor->HasLockedActors() ) : false;
PDI->SetHitProxy( new HWidgetAxis( InAxis, bDisabled) );
const float AxisLength = AXIS_LENGTH + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize - (AxisLengthOffset * 2);
const float HalfHeight = AxisLength/2.0f;
const float CylinderRadius = 1.2f;
const FVector Offset(0, 0, HalfHeight + AxisLengthOffset);
switch( InAxis )
{
case EAxisList::X:
{
DrawCylinder(PDI, ( Scale * FRotationMatrix(FRotator(-90, 0.f, 0)) * InMatrix ) * FScaleMatrix(FlattenScale), Offset, FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), CylinderRadius, HalfHeight, 16, InMaterial->GetRenderProxy(), SDPG_Foreground);
break;
}
case EAxisList::Y:
{
DrawCylinder(PDI, (Scale * FRotationMatrix(FRotator(0, 0, bUsingLUFCoordinateSysem ? -90 : 90)) * InMatrix)* FScaleMatrix(FlattenScale), Offset, FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), CylinderRadius, HalfHeight, 16, InMaterial->GetRenderProxy(), SDPG_Foreground );
break;
}
case EAxisList::Z:
{
DrawCylinder(PDI, ( Scale * InMatrix ) * FScaleMatrix(FlattenScale), Offset, FVector(1, 0, 0), FVector(0, 1, 0), FVector(0, 0, 1), CylinderRadius, HalfHeight, 16, InMaterial->GetRenderProxy(), SDPG_Foreground);
break;
}
}
if ( bCubeHead )
{
const float CubeHeadOffset = 3.0f;
FVector RootPos(AxisLength + CubeHeadOffset + AxisLengthOffset, 0, 0);
Render_Cube(PDI, (FTranslationMatrix(RootPos) * ArrowToWorld) * FScaleMatrix(FlattenScale), InMaterial, FVector(4.0f));
}
else
{
const float ConeHeadOffset = 12.0f;
FVector RootPos(AxisLength + ConeHeadOffset + AxisLengthOffset, 0, 0);
float Angle = FMath::DegreesToRadians( PI * 5 );
DrawCone(PDI, ( FScaleMatrix(-13) * FTranslationMatrix(RootPos) * ArrowToWorld ) * FScaleMatrix(FlattenScale), Angle, Angle, 32, false, FColor::White, InMaterial->GetRenderProxy(), SDPG_Foreground);
}
PDI->SetHitProxy( nullptr );
}
FVector2D NewOrigin;
FVector2D AxisEnd;
const FVector AxisEndWorld = ArrowToWorld.TransformPosition(FVector(64, 0, 0));
const FVector WidgetOrigin = InMatrix.GetOrigin();
if (View->ScreenToPixel(View->WorldToScreen(WidgetOrigin), NewOrigin) &&
View->ScreenToPixel(View->WorldToScreen(AxisEndWorld), AxisEnd))
{
// If both the origin and the axis endpoint are in front of the camera, trivially calculate the viewport space axis direction
OutAxisDir = (AxisEnd - NewOrigin).GetSafeNormal();
}
else
{
// If either the origin or axis endpoint are behind the camera, translate the entire widget in front of the camera in the view direction before performing the
// viewport space calculation
const FMatrix InvViewMatrix = View->ViewMatrices.GetInvViewMatrix();
const FVector ViewLocation = InvViewMatrix.GetOrigin();
const FVector ViewDirection = InvViewMatrix.GetUnitAxis(EAxis::Z);
const FVector Offset = ViewDirection * (FVector::DotProduct(ViewLocation - WidgetOrigin, ViewDirection) + 100.0f);
const FVector AdjustedWidgetOrigin = WidgetOrigin + Offset;
const FVector AdjustedWidgetAxisEnd = AxisEndWorld + Offset;
if (View->ScreenToPixel(View->WorldToScreen(AdjustedWidgetOrigin), NewOrigin) &&
View->ScreenToPixel(View->WorldToScreen(AdjustedWidgetAxisEnd), AxisEnd))
{
OutAxisDir = -(AxisEnd - NewOrigin).GetSafeNormal();
}
}
}
void FWidget::Render_Cube( FPrimitiveDrawInterface* PDI, const FMatrix& InMatrix, const UMaterialInterface* InMaterial, const FVector& InScale )
{
const FMatrix CubeToWorld = FScaleMatrix(InScale) * InMatrix;
DrawBox( PDI, CubeToWorld, FVector(1,1,1), InMaterial->GetRenderProxy(), SDPG_Foreground );
}
void DrawCornerHelper( FPrimitiveDrawInterface* PDI, const FMatrix& LocalToWorld, const FVector& Length, float Thickness, const FMaterialRenderProxy* MaterialRenderProxy,uint8 DepthPriorityGroup )
{
const float TH = Thickness;
float TX = Length.X/2;
float TY = Length.Y/2;
float TZ = Length.Z/2;
FDynamicMeshBuilder MeshBuilder(PDI->View->GetFeatureLevel());
// Top
{
int32 VertexIndices[4];
VertexIndices[0] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,1,0), FVector3f(0,0,1), FColor::White );
VertexIndices[1] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,1,0), FVector3f(0,0,1), FColor::White );
VertexIndices[2] = MeshBuilder.AddVertex( FVector3f(+TX, +TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,1,0), FVector3f(0,0,1), FColor::White );
VertexIndices[3] = MeshBuilder.AddVertex( FVector3f(+TX, -TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,1,0), FVector3f(0,0,1), FColor::White );
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[1],VertexIndices[2]);
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[2],VertexIndices[3]);
}
//Left
{
int32 VertexIndices[4];
VertexIndices[0] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, TZ-TH), FVector2f::ZeroVector, FVector3f(0,0,1), FVector3f(0,1,0), FVector3f(-1,0,0), FColor::White );
VertexIndices[1] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, TZ), FVector2f::ZeroVector, FVector3f(0,0,1), FVector3f(0,1,0), FVector3f(-1,0,0), FColor::White );
VertexIndices[2] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, TZ), FVector2f::ZeroVector, FVector3f(0,0,1), FVector3f(0,1,0), FVector3f(-1,0,0), FColor::White );
VertexIndices[3] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, TZ-TH), FVector2f::ZeroVector, FVector3f(0,0,1), FVector3f(0,1,0), FVector3f(-1,0,0), FColor::White );
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[1],VertexIndices[2]);
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[2],VertexIndices[3]);
}
// Front
{
int32 VertexIndices[5];
VertexIndices[0] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,1,0), FColor::White );
VertexIndices[1] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, +TZ ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,1,0), FColor::White );
VertexIndices[2] = MeshBuilder.AddVertex( FVector3f(+TX-TH, +TY, +TX ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,1,0), FColor::White );
VertexIndices[3] = MeshBuilder.AddVertex( FVector3f(+TX, +TY, +TZ ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,1,0), FColor::White );
VertexIndices[4] = MeshBuilder.AddVertex( FVector3f(+TX-TH, +TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,1,0), FColor::White );
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[1],VertexIndices[2]);
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[2],VertexIndices[4]);
MeshBuilder.AddTriangle(VertexIndices[4],VertexIndices[2],VertexIndices[3]);
}
// Back
{
int32 VertexIndices[5];
VertexIndices[0] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,1), FVector3f(0,-1,0), FColor::White );
VertexIndices[1] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,1), FVector3f(0,-1,0), FColor::White );
VertexIndices[2] = MeshBuilder.AddVertex( FVector3f(+TX-TH, -TY, +TX), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,1), FVector3f(0,-1,0), FColor::White );
VertexIndices[3] = MeshBuilder.AddVertex( FVector3f(+TX, -TY, +TZ), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,1), FVector3f(0,-1,0), FColor::White );
VertexIndices[4] = MeshBuilder.AddVertex( FVector3f(+TX-TH, -TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,1), FVector3f(0,-1,0), FColor::White );
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[1],VertexIndices[2]);
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[2],VertexIndices[4]);
MeshBuilder.AddTriangle(VertexIndices[4],VertexIndices[2],VertexIndices[3]);
}
// Bottom
{
int32 VertexIndices[4];
VertexIndices[0] = MeshBuilder.AddVertex( FVector3f(-TX, -TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,0,1), FColor::White );
VertexIndices[1] = MeshBuilder.AddVertex( FVector3f(-TX, +TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,0,1), FColor::White );
VertexIndices[2] = MeshBuilder.AddVertex( FVector3f(+TX-TH, +TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,0,1), FColor::White );
VertexIndices[3] = MeshBuilder.AddVertex( FVector3f(+TX-TH, -TY, TZ-TH), FVector2f::ZeroVector, FVector3f(1,0,0), FVector3f(0,0,-1), FVector3f(0,0,1), FColor::White );
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[1],VertexIndices[2]);
MeshBuilder.AddTriangle(VertexIndices[0],VertexIndices[2],VertexIndices[3]);
}
MeshBuilder.Draw(PDI,LocalToWorld,MaterialRenderProxy,DepthPriorityGroup,0.f);
}
void DrawDualAxis( FPrimitiveDrawInterface* PDI, const FMatrix& BoxToWorld,const FVector& Length, float Thickness, const FMaterialRenderProxy* AxisMat,const FMaterialRenderProxy* Axis2Mat )
{
DrawCornerHelper(PDI, BoxToWorld, Length, Thickness, Axis2Mat, SDPG_Foreground);
DrawCornerHelper(PDI, FScaleMatrix(FVector(-1, 1, 1)) * FRotationMatrix(FRotator(-90, 0, 0)) * BoxToWorld, Length, Thickness, AxisMat, SDPG_Foreground);
}
/**
* Draws the translation widget.
*/
void FWidget::Render_Translate( const FSceneView* View, FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient, const FVector& InLocation, bool bDrawWidget )
{
// Figure out axis colors
const FLinearColor& XColor = ( CurrentAxis&EAxisList::X ? (FLinearColor)CurrentColor : AxisColorX );
const FLinearColor& YColor = ( CurrentAxis&EAxisList::Y ? (FLinearColor)CurrentColor : AxisColorY );
const FLinearColor& ZColor = ( CurrentAxis&EAxisList::Z ? (FLinearColor)CurrentColor : AxisColorZ );
FColor CurrentScreenColor = ( CurrentAxis & EAxisList::Screen ? CurrentColor : ScreenSpaceColor );
// Figure out axis matrices
FMatrix WidgetMatrix = CustomCoordSystem * FTranslationMatrix( InLocation );
const EAxisList::Type DrawAxis = GetAxisToDraw( ViewportClient->GetWidgetMode() );
const bool bDisabled = IsWidgetDisabled();
const float ExternalScale = EditorModeTools ? EditorModeTools->GetWidgetScale() : 1.0f;
FSpaceDescriptor Space(View, ViewportClient, InLocation, ExternalScale);
// Draw the axis lines with arrow heads
if( Space.ShouldDrawAxisX(DrawAxis) )
{
UMaterialInstanceDynamic* XMaterial = ( CurrentAxis&EAxisList::X ? CurrentAxisMaterial : AxisMaterialX );
Render_Axis( View, PDI, EAxisList::X, WidgetMatrix, XMaterial, XColor, XAxisDir, Space.Scale, bDrawWidget );
}
if( Space.ShouldDrawAxisY(DrawAxis) )
{
UMaterialInstanceDynamic* YMaterial = ( CurrentAxis&EAxisList::Y ? CurrentAxisMaterial : AxisMaterialY );
Render_Axis( View, PDI, EAxisList::Y, WidgetMatrix, YMaterial, YColor, YAxisDir, Space.Scale, bDrawWidget );
}
if( Space.ShouldDrawAxisZ(DrawAxis) )
{
UMaterialInstanceDynamic* ZMaterial = ( CurrentAxis&EAxisList::Z ? CurrentAxisMaterial : AxisMaterialZ );
Render_Axis( View, PDI, EAxisList::Z, WidgetMatrix, ZMaterial, ZColor, ZAxisDir, Space.Scale, bDrawWidget );
}
// Draw the grabbers
if( bDrawWidget )
{
FVector CornerPos = FVector(7, 0, 7) * Space.UniformScale;
FVector AxisSize = FVector(12, 1.2, 12) * Space.UniformScale;
float CornerLength = 1.2f * Space.UniformScale;
// After the primitives have been scaled and transformed, we apply this inverse scale that flattens the dimension
// that was scaled up to prevent it from intersecting with the near plane. In perspective this won't have any effect,
// but in the ortho viewports it will prevent scaling in the direction of the camera and thus intersecting the near plane.
FVector FlattenScale = FVector(Space.Scale.Component(0) == 1.0f ? 1.0f / Space.UniformScale : 1.0f, Space.Scale.Component(1) == 1.0f ? 1.0f / Space.UniformScale : 1.0f, Space.Scale.Component(2) == 1.0f ? 1.0f / Space.UniformScale : 1.0f);
const bool bUsingLUFCoordinateSysem = AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward;
if (Space.bIsPerspective || Space.bIsLocalSpace || Space.bIsOrthoXY)
{
if( (DrawAxis&EAxisList::XY) == EAxisList::XY ) // Top
{
UMaterialInstanceDynamic* XMaterial = ( (CurrentAxis&EAxisList::XY) == EAxisList::XY ? CurrentAxisMaterial : AxisMaterialX );
UMaterialInstanceDynamic* YMaterial = ( (CurrentAxis&EAxisList::XY) == EAxisList::XY? CurrentAxisMaterial : AxisMaterialY );
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XY, bDisabled) );
{
DrawDualAxis(PDI, ( FTranslationMatrix(CornerPos) * FRotationMatrix(FRotator(0, 0, bUsingLUFCoordinateSysem ? -90 : 90)) * WidgetMatrix ) * FScaleMatrix(FlattenScale), AxisSize, CornerLength, XMaterial->GetRenderProxy(), YMaterial->GetRenderProxy());
}
PDI->SetHitProxy( nullptr );
}
}
if (Space.bIsPerspective || Space.bIsLocalSpace || Space.bIsOrthoXZ) // Front
{
if( (DrawAxis&EAxisList::XZ) == EAxisList::XZ )
{
UMaterialInstanceDynamic* XMaterial = ( (CurrentAxis&EAxisList::XZ) == EAxisList::XZ ? CurrentAxisMaterial : AxisMaterialX );
UMaterialInstanceDynamic* ZMaterial = ( (CurrentAxis&EAxisList::XZ) == EAxisList::XZ ? CurrentAxisMaterial : AxisMaterialZ );
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XZ, bDisabled) );
{
DrawDualAxis(PDI, (FTranslationMatrix(CornerPos) * WidgetMatrix) * FScaleMatrix(FlattenScale), AxisSize, CornerLength, XMaterial->GetRenderProxy(), ZMaterial->GetRenderProxy() );
}
PDI->SetHitProxy( nullptr );
}
}
if( Space.bIsPerspective || Space.bIsLocalSpace || Space.bIsOrthoYZ ) // Side
{
if( (DrawAxis&EAxisList::YZ) == EAxisList::YZ )
{
UMaterialInstanceDynamic* YMaterial = ( (CurrentAxis&EAxisList::YZ) == EAxisList::YZ ? CurrentAxisMaterial : AxisMaterialY );
UMaterialInstanceDynamic* ZMaterial = ( (CurrentAxis&EAxisList::YZ) == EAxisList::YZ ? CurrentAxisMaterial : AxisMaterialZ );
PDI->SetHitProxy( new HWidgetAxis(EAxisList::YZ, bDisabled) );
{
DrawDualAxis(PDI, (FTranslationMatrix(CornerPos) * FRotationMatrix(FRotator(0, bUsingLUFCoordinateSysem ? -90 : 90, 0)) * WidgetMatrix) * FScaleMatrix(FlattenScale), AxisSize, CornerLength, YMaterial->GetRenderProxy(), ZMaterial->GetRenderProxy() );
}
PDI->SetHitProxy( nullptr );
}
}
}
// Draw screen-space movement handle (circle)
if( bDrawWidget && ( DrawAxis & EAxisList::Screen ) && Space.bIsPerspective )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::Screen, bDisabled) );
const FVector CameraXAxis = View->ViewMatrices.GetViewMatrix().GetColumn(0);
const FVector CameraYAxis = View->ViewMatrices.GetViewMatrix().GetColumn(1);
const FVector CameraZAxis = View->ViewMatrices.GetViewMatrix().GetColumn(2);
UMaterialInstanceDynamic* XYZMaterial = ( CurrentAxis&EAxisList::Screen) ? CurrentAxisMaterial : OpaquePlaneMaterialXY;
DrawSphere( PDI, WidgetMatrix.GetOrigin(), FRotator::ZeroRotator, 4.0f * Space.Scale, 10, 5, XYZMaterial->GetRenderProxy(), SDPG_Foreground );
PDI->SetHitProxy( nullptr );
}
}
uint8 LargeInnerAlpha = 0x3f;
uint8 SmallInnerAlpha = 0x0f;
uint8 LargeOuterAlpha = 0x7f;
uint8 SmallOuterAlpha = 0x0f;
void FWidget::DrawColoredSphere(FPrimitiveDrawInterface* PDI, const FVector& Center, const FRotator& Orientation, FColor Color, const FVector& Radii, int32 NumSides, int32 NumRings, const FMaterialRenderProxy* MaterialRenderProxy, uint8 DepthPriority, bool bDisableBackfaceCulling)
{
// Use a mesh builder to draw the sphere.
FDynamicMeshBuilder MeshBuilder(PDI->View->GetFeatureLevel());
{
// The first/last arc are on top of each other.
int32 NumVerts = (NumSides + 1) * (NumRings + 1);
FDynamicMeshVertex* Verts = (FDynamicMeshVertex*)FMemory::Malloc(NumVerts * sizeof(FDynamicMeshVertex));
// Calculate verts for one arc
FDynamicMeshVertex* ArcVerts = (FDynamicMeshVertex*)FMemory::Malloc((NumRings + 1) * sizeof(FDynamicMeshVertex));
for (int32 i = 0; i < NumRings + 1; i++)
{
FDynamicMeshVertex* ArcVert = &ArcVerts[i];
float angle = ((float)i / NumRings) * PI;
ArcVert->Color = Color;
// Note- unit sphere, so position always has mag of one. We can just use it for normal!
ArcVert->Position.X = 0.0f;
ArcVert->Position.Y = FMath::Sin(angle);
ArcVert->Position.Z = FMath::Cos(angle);
ArcVert->SetTangents(
FVector3f(1, 0, 0),
FVector3f(0.0f, -ArcVert->Position.Z, ArcVert->Position.Y),
ArcVert->Position
);
ArcVert->TextureCoordinate[0].X = 0.0f;
ArcVert->TextureCoordinate[0].Y = ((float)i / NumRings);
}
// Then rotate this arc NumSides+1 times.
for (int32 s = 0; s < NumSides + 1; s++)
{
FRotator3f ArcRotator(0, 360.f * (float)s / NumSides, 0);
FRotationMatrix44f ArcRot(ArcRotator);
float XTexCoord = ((float)s / NumSides);
for (int32 v = 0; v < NumRings + 1; v++)
{
int32 VIx = (NumRings + 1)*s + v;
Verts[VIx].Color = Color;
Verts[VIx].Position = ArcRot.TransformPosition(ArcVerts[v].Position);
Verts[VIx].SetTangents(
ArcRot.TransformVector(ArcVerts[v].TangentX.ToFVector3f()),
ArcRot.TransformVector(ArcVerts[v].GetTangentY()),
ArcRot.TransformVector(ArcVerts[v].TangentZ.ToFVector3f())
);
Verts[VIx].TextureCoordinate[0].X = XTexCoord;
Verts[VIx].TextureCoordinate[0].Y = ArcVerts[v].TextureCoordinate[0].Y;
}
}
// Add all of the vertices we generated to the mesh builder.
for (int32 VertIdx = 0; VertIdx < NumVerts; VertIdx++)
{
MeshBuilder.AddVertex(Verts[VertIdx]);
}
// Add all of the triangles we generated to the mesh builder.
for (int32 s = 0; s < NumSides; s++)
{
int32 a0start = (s + 0) * (NumRings + 1);
int32 a1start = (s + 1) * (NumRings + 1);
for (int32 r = 0; r < NumRings; r++)
{
MeshBuilder.AddTriangle(a0start + r + 0, a1start + r + 0, a0start + r + 1);
MeshBuilder.AddTriangle(a1start + r + 0, a1start + r + 1, a0start + r + 1);
}
}
// Free our local copy of verts and arc verts
FMemory::Free(Verts);
FMemory::Free(ArcVerts);
}
MeshBuilder.Draw(PDI, FScaleMatrix(Radii) * FRotationMatrix(Orientation) * FTranslationMatrix(Center), MaterialRenderProxy, DepthPriority, bDisableBackfaceCulling);
}
//CVAR for the arcball size, so animators can adjust it
//DEPRECRATREDin 5.1, since changing the size will cause hit test problems.
static TAutoConsoleVariable<float> CVarArcballSize(
TEXT("r.Editor.ArcballSize"),
1.0,
TEXT("DEPRECRATED in 5.1)"),
ECVF_RenderThreadSafe
);
/**
* Draws the rotation widget.
*/
void FWidget::Render_Rotate( const FSceneView* View,FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient, const FVector& InLocation, bool bDrawWidget )
{
float Scale = View->WorldToScreen(InLocation).W * (4.0f / View->UnscaledViewRect.Width() / View->ViewMatrices.GetProjectionMatrix().M[0][0]);
const float ExternalScale = EditorModeTools ? EditorModeTools->GetWidgetScale() : 1.0f;
Scale *= ExternalScale;
//get the axes
FVector XAxis = CustomCoordSystem.TransformVector(FVector(1, 0, 0));
FVector YAxis = CustomCoordSystem.TransformVector(FVector(0, 1, 0));
FVector ZAxis = CustomCoordSystem.TransformVector(FVector(0, 0, 1));
FMatrix WidgetMatrix = CustomCoordSystem * FTranslationMatrix( InLocation );
EAxisList::Type DrawAxis = GetAxisToDraw(ViewportClient->GetWidgetMode());
FVector DirectionToWidget = View->IsPerspectiveProjection() ? (InLocation - View->ViewMatrices.GetViewOrigin()) : -View->GetViewDirection();
DirectionToWidget.Normalize();
// Draw a circle for each axis
if (bDrawWidget || bDragging)
{
bIsOrthoDrawingFullRing = false;
//now draw the arc segments, only if we are not dragging and moving the arcball per animators request
if ((DrawAxis & EAxisList::XYZ && CurrentAxis == EAxisList::XYZ && bDragging) == false)
{
if (DrawAxis & EAxisList::X)
{
DrawRotationArc(View, PDI, EAxisList::X, WidgetMatrix.GetOrigin(), ZAxis, YAxis, DirectionToWidget, AxisColorX.ToFColor(true), Scale, XAxisDir);
}
if (DrawAxis & EAxisList::Y)
{
DrawRotationArc(View, PDI, EAxisList::Y, WidgetMatrix.GetOrigin(), XAxis, ZAxis, DirectionToWidget, AxisColorY.ToFColor(true), Scale, YAxisDir);
}
if (DrawAxis & EAxisList::Z)
{
DrawRotationArc(View, PDI, EAxisList::Z, WidgetMatrix.GetOrigin(), XAxis, YAxis, DirectionToWidget, AxisColorZ.ToFColor(true), Scale, ZAxisDir);
}
}
if (DrawAxis&EAxisList::XYZ && (!bDragging || CurrentAxis == EAxisList::XYZ) && GetDefault<UTransformGizmoEditorSettings>()->bEnableArcballRotate)
{
FVector Center = InLocation;
FRotator Orientation = FRotator::ZeroRotator;
const float ArcballSize = 1.0f; //always constant, otherwise can cause hit testing issues
const float InnerDistance = (INNER_AXIS_CIRCLE_RADIUS * ArcballSize * Scale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
FVector Radii(InnerDistance, InnerDistance, InnerDistance);
const bool bDisabled = IsWidgetDisabled();
FColor ArcColor = ArcBallColor;
ArcColor.A = (CurrentAxis==EAxisList::XYZ) ? ArcBallColor.A + 5 : ArcBallColor.A; //less transparent if selected
//set priority to foreground so axis get hit first
PDI->SetHitProxy(new HWidgetAxis(EAxisList::XYZ, bDisabled, HPP_Foreground));
{
DrawColoredSphere(PDI, WidgetMatrix.GetOrigin(), FRotator::ZeroRotator, ArcColor, Radii, 32, 24, TransparentPlaneMaterialXY->GetRenderProxy(), SDPG_Foreground, true);
}
PDI->SetHitProxy(nullptr);
}
if(DrawAxis&EAxisList::Screen && (!bDragging || CurrentAxis == EAxisList::Screen) && GetDefault<ULevelEditorViewportSettings>()->bAllowScreenRotate)
{
const bool bIsPerspective = (View->ViewMatrices.GetProjectionMatrix().M[3][3] < 1.0f);
const bool bIsOrtho = !bIsPerspective;
const FVector Axis0 = View->GetViewUp();
const FVector Axis1 = View->GetViewRight();
const float OuterRadius = (OUTER_AXIS_CIRCLE_RADIUS * 1.25f * Scale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float InnerRadius = ((OUTER_AXIS_CIRCLE_RADIUS -1) * 1.25f * Scale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
FColor ArcColor = (CurrentAxis==EAxisList::Screen) ? CurrentColor : ScreenAxisColor.ToFColor(true);
const bool bDisabled = IsWidgetDisabled();
PDI->SetHitProxy(new HWidgetAxis(EAxisList::Screen, bDisabled));
{
FThickArcParams OuterArcParams(PDI, InLocation, TransparentPlaneMaterialXY, InnerRadius, OuterRadius);
//Pass through alpha
DrawThickArc(OuterArcParams, Axis0, Axis1, 0.0f, 2.0f * PI, ArcColor, DirectionToWidget, !bIsPerspective);
}
PDI->SetHitProxy(nullptr);
}
}
}
/**
* Draws the scaling widget.
*/
void FWidget::Render_Scale( const FSceneView* View,FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient, const FVector& InLocation, bool bDrawWidget)
{
// Figure out axis colors
const FLinearColor& XColor = ( CurrentAxis&EAxisList::X ? (FLinearColor)CurrentColor : AxisColorX );
const FLinearColor& YColor = ( CurrentAxis&EAxisList::Y ? (FLinearColor)CurrentColor : AxisColorY );
const FLinearColor& ZColor = ( CurrentAxis&EAxisList::Z ? (FLinearColor)CurrentColor : AxisColorZ );
FColor CurrentScreenColor = ( CurrentAxis & EAxisList::Screen ? CurrentColor : ScreenSpaceColor );
// Figure out axis materials
UMaterialInstanceDynamic* XMaterial = ( CurrentAxis&EAxisList::X ? CurrentAxisMaterial : AxisMaterialX );
UMaterialInstanceDynamic* YMaterial = ( CurrentAxis&EAxisList::Y ? CurrentAxisMaterial : AxisMaterialY );
UMaterialInstanceDynamic* ZMaterial = ( CurrentAxis&EAxisList::Z ? CurrentAxisMaterial : AxisMaterialZ );
UMaterialInstanceDynamic* XYZMaterial = ( CurrentAxis&EAxisList::XYZ ? CurrentAxisMaterial : OpaquePlaneMaterialXY );
FMatrix WidgetMatrix = CustomCoordSystem * FTranslationMatrix( InLocation );
const EAxisList::Type DrawAxis = GetAxisToDraw( ViewportClient->GetWidgetMode() );
const float ExternalScale = EditorModeTools ? EditorModeTools->GetWidgetScale() : 1.0f;
FSpaceDescriptor Space(View, ViewportClient, InLocation, ExternalScale);
// Use a constant uniform scale for this widget since orthographic view for it is not supported.
const FVector UniformScale(Space.UniformScale);
// Draw the axis lines with cube heads
if (Space.ShouldDrawAxisX(DrawAxis))
{
Render_Axis(View, PDI, EAxisList::X, WidgetMatrix, XMaterial, XColor, XAxisDir, UniformScale, bDrawWidget, true, AXIS_LENGTH_SCALE_OFFSET);
}
if (Space.ShouldDrawAxisY(DrawAxis))
{
Render_Axis(View, PDI, EAxisList::Y, WidgetMatrix, YMaterial, YColor, YAxisDir, UniformScale, bDrawWidget, true, AXIS_LENGTH_SCALE_OFFSET);
}
if (Space.ShouldDrawAxisZ(DrawAxis))
{
Render_Axis(View, PDI, EAxisList::Z, WidgetMatrix, ZMaterial, ZColor, ZAxisDir, UniformScale, bDrawWidget, true, AXIS_LENGTH_SCALE_OFFSET);
}
// Draw grabber handles and center cube
if ( bDrawWidget )
{
const bool bDisabled = IsWidgetDisabled();
const bool bUsingLUFCoordinateSysem = AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward;
// Since the Y axis gets flipped in the LUF coord system, flip the Y position of the grabber handles as well
const int8_t GrabberHandlesHalfYPos = bUsingLUFCoordinateSysem ? -12 : 12;
const int8_t GrabberHandlesYPos = bUsingLUFCoordinateSysem ? -24 : 24;
// Grabber handles - since orthographic scale widgets are not supported, we should always draw grabber handles if we're drawing
// the corresponding axes.
if( (DrawAxis&(EAxisList::X|EAxisList::Y)) == (EAxisList::X|EAxisList::Y) )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XY, bDisabled) );
{
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(24,0,0) * UniformScale), WidgetMatrix.TransformPosition(FVector(12,GrabberHandlesHalfYPos,0) * UniformScale), XColor, SDPG_Foreground );
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(12,GrabberHandlesHalfYPos,0) * UniformScale), WidgetMatrix.TransformPosition(FVector(0,GrabberHandlesYPos,0) * UniformScale), YColor, SDPG_Foreground );
}
PDI->SetHitProxy( nullptr );
}
if( (DrawAxis&(EAxisList::X|EAxisList::Z)) == (EAxisList::X|EAxisList::Z) )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XZ, bDisabled) );
{
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(24,0,0) * UniformScale), WidgetMatrix.TransformPosition(FVector(12,0,12) * UniformScale), XColor, SDPG_Foreground );
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(12,0,12) * UniformScale), WidgetMatrix.TransformPosition(FVector(0,0,24) * UniformScale), ZColor, SDPG_Foreground );
}
PDI->SetHitProxy( nullptr );
}
if( (DrawAxis&(EAxisList::Y|EAxisList::Z)) == (EAxisList::Y|EAxisList::Z) )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::YZ, bDisabled) );
{
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(0,GrabberHandlesYPos,0) * UniformScale), WidgetMatrix.TransformPosition(FVector(0,GrabberHandlesHalfYPos,12) * UniformScale), YColor, SDPG_Foreground );
PDI->DrawLine( WidgetMatrix.TransformPosition(FVector(0,GrabberHandlesHalfYPos,12) * UniformScale), WidgetMatrix.TransformPosition(FVector(0,0,24) * UniformScale), ZColor, SDPG_Foreground );
}
PDI->SetHitProxy( nullptr );
}
// Center cube
if( (DrawAxis&(EAxisList::XYZ)) == EAxisList::XYZ )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XYZ, bDisabled) );
Render_Cube(PDI, WidgetMatrix, XYZMaterial, UniformScale * 4 );
PDI->SetHitProxy( nullptr );
}
}
}
/**
* Draws the Translate & Rotate Z widget.
*/
void FWidget::Render_TranslateRotateZ( const FSceneView* View, FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient, const FVector& InLocation, bool bDrawWidget )
{
// Figure out axis colors
FColor XYPlaneColor = ( (CurrentAxis&EAxisList::XY) == EAxisList::XY) ? CurrentColor : PlaneColorXY;
FColor ZRotateColor = ( ( CurrentAxis&EAxisList::ZRotation ) == EAxisList::ZRotation ) ? CurrentColor : AxisColorZ.ToFColor(true);
FColor XColor = ( ( CurrentAxis&EAxisList::X ) == EAxisList::X ) ? CurrentColor : AxisColorX.ToFColor(true);
FColor YColor = ( ( CurrentAxis&EAxisList::Y ) == EAxisList::Y && CurrentAxis != EAxisList::ZRotation ) ? CurrentColor : AxisColorY.ToFColor(true);
FColor ZColor = ( ( CurrentAxis&EAxisList::Z ) == EAxisList::Z ) ? CurrentColor : AxisColorZ.ToFColor(true);
// Figure out axis materials
UMaterialInterface* ZRotateMaterial = (CurrentAxis&EAxisList::ZRotation) == EAxisList::ZRotation? CurrentAxisMaterial : AxisMaterialZ;
UMaterialInterface* XMaterial = CurrentAxis&EAxisList::X? CurrentAxisMaterial : AxisMaterialX;
UMaterialInterface* YMaterial = ( CurrentAxis&EAxisList::Y && CurrentAxis != EAxisList::ZRotation ) ? CurrentAxisMaterial : AxisMaterialY;
UMaterialInterface* ZMaterial = CurrentAxis&EAxisList::Z ? CurrentAxisMaterial : AxisMaterialZ;
// Figure out axis matrices
FMatrix AxisMatrix = CustomCoordSystem * FTranslationMatrix( InLocation );
EAxisList::Type DrawAxis = GetAxisToDraw( ViewportClient->GetWidgetMode() );
const float ExternalScale = EditorModeTools ? EditorModeTools->GetWidgetScale() : 1.0f;
FSpaceDescriptor Space(View, ViewportClient, InLocation, ExternalScale);
// Draw the grabbers
if( bDrawWidget )
{
// Draw the axis lines with arrow heads
if( DrawAxis&EAxisList::X && (Space.bIsPerspective || Space.bIsLocalSpace || View->ViewMatrices.GetViewMatrix().M[0][2] != -1.f) )
{
Render_Axis( View, PDI, EAxisList::X, AxisMatrix, XMaterial, XColor, XAxisDir, Space.Scale, bDrawWidget );
}
if( DrawAxis&EAxisList::Y && (Space.bIsPerspective || Space.bIsLocalSpace || View->ViewMatrices.GetViewMatrix().M[1][2] != -1.f) )
{
Render_Axis( View, PDI, EAxisList::Y, AxisMatrix, YMaterial, YColor, YAxisDir, Space.Scale, bDrawWidget );
}
if( DrawAxis&EAxisList::Z && (Space.bIsPerspective || Space.bIsLocalSpace || View->ViewMatrices.GetViewMatrix().M[0][1] != 1.f) )
{
Render_Axis( View, PDI, EAxisList::Z, AxisMatrix, ZMaterial, ZColor, ZAxisDir, Space.Scale, bDrawWidget );
}
const bool bDisabled = IsWidgetDisabled();
const float ScaledRadius = (TRANSLATE_ROTATE_AXIS_CIRCLE_RADIUS * Space.UniformScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
//ZRotation
if( DrawAxis&EAxisList::ZRotation && (Space.bIsPerspective || Space.bIsLocalSpace || View->ViewMatrices.GetViewMatrix().M[0][2] != -1.f) )
{
PDI->SetHitProxy( new HWidgetAxis(EAxisList::ZRotation, bDisabled) );
{
FVector XAxis;
FVector YAxis;
if (Space.bIsLocalSpace)
{
// In local space, only need to set axis
XAxis = CustomCoordSystem.TransformPosition(FVector::ForwardVector);
YAxis = CustomCoordSystem.TransformPosition(FVector::RightVector);
}
else
{
// In world space, need to both set and modify axis
XAxis = CustomCoordSystem.TransformPosition(FVector::ForwardVector.RotateAngleAxis((EditorModeTools ? EditorModeTools->TranslateRotateXAxisAngle : 0), FVector::UpVector));
YAxis = CustomCoordSystem.TransformPosition(FVector::RightVector.RotateAngleAxis((EditorModeTools ? EditorModeTools->TranslateRotateXAxisAngle : 0), FVector::UpVector));
}
FVector BaseArrowPoint = InLocation + XAxis * ScaledRadius;
DrawFlatArrow(PDI, BaseArrowPoint, XAxis, YAxis, ZRotateColor, ScaledRadius, ScaledRadius*.5f, ZRotateMaterial->GetRenderProxy(), SDPG_Foreground);
}
PDI->SetHitProxy( nullptr );
}
//XY Plane
if( Space.bIsPerspective || Space.bIsLocalSpace || View->ViewMatrices.GetViewMatrix().M[0][1] != 1.f )
{
if( (DrawAxis & EAxisList::XY) == EAxisList::XY )
{
// Add more sides to the circle if we've been scaled up to keep the circle looking circular
// An extra side for every 5 extra unreal units seems to produce a nice result
const int32 CircleSides = (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize > 0)
? AXIS_CIRCLE_SIDES + (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize / 5)
: AXIS_CIRCLE_SIDES;
PDI->SetHitProxy( new HWidgetAxis(EAxisList::XY, bDisabled) );
{
DrawCircle( PDI, InLocation, CustomCoordSystem.TransformPosition( FVector(1,0,0) ), CustomCoordSystem.TransformPosition( FVector(0,1,0) ), XYPlaneColor, ScaledRadius, CircleSides, SDPG_Foreground );
XYPlaneColor.A = ((CurrentAxis&EAxisList::XY) == EAxisList::XY) ? 0x3f : 0x0f; //make the disc transparent
DrawDisc ( PDI, InLocation, CustomCoordSystem.TransformPosition( FVector(1,0,0) ), CustomCoordSystem.TransformPosition( FVector(0,1,0) ), XYPlaneColor, ScaledRadius, CircleSides, TransparentPlaneMaterialXY->GetRenderProxy(), SDPG_Foreground );
}
PDI->SetHitProxy( nullptr );
}
}
}
}
/**
* Draws the 2D widget.
*/
void FWidget::Render_2D(const FSceneView* View, FPrimitiveDrawInterface* PDI, FEditorViewportClient* ViewportClient, const FVector& InLocation, bool bDrawWidget)
{
//////////////////////////////////////////////////////////////////////////
// Translation subwidget
//////////////////////////////////////////////////////////////////////////
// Figure out axis colors
const FLinearColor& XColor = (CurrentAxis&EAxisList::X ? (FLinearColor)CurrentColor : AxisColorX);
const FLinearColor& YColor = (CurrentAxis&EAxisList::Y ? (FLinearColor)CurrentColor : AxisColorY);
const FLinearColor& ZColor = (CurrentAxis&EAxisList::Z ? (FLinearColor)CurrentColor : AxisColorZ);
FColor CurrentScreenColor = (CurrentAxis & EAxisList::Screen ? CurrentColor : ScreenSpaceColor);
// Figure out axis matrices
FMatrix WidgetMatrix = CustomCoordSystem * FTranslationMatrix(InLocation);
const float ExternalScale = EditorModeTools ? EditorModeTools->GetWidgetScale() : 1.0f;
FSpaceDescriptor Space(View, ViewportClient, InLocation, ExternalScale);
EAxisList::Type DrawAxis = EAxisList::None;
if (Space.bIsOrthoXY)
{
DrawAxis = EAxisList::X;
}
else if (Space.bIsOrthoXZ)
{
DrawAxis = EAxisList::XZ;
}
else if (Space.bIsOrthoYZ)
{
DrawAxis = EAxisList::Z;
}
else if (Space.bIsPerspective)
{
// Find the best plane to move on
const FVector CameraZAxis = View->ViewMatrices.GetViewMatrix().GetColumn(2);
const FVector LargestAxis = CameraZAxis.GetAbs();
if (LargestAxis.X > LargestAxis.Y)
{
if (LargestAxis.Z > LargestAxis.X)
{
DrawAxis = EAxisList::X;
}
else
{
DrawAxis = EAxisList::Z;
}
}
else
{
if (LargestAxis.Y > LargestAxis.Z)
{
DrawAxis = EAxisList::XZ;
}
else
{
DrawAxis = EAxisList::X;
}
}
}
const bool bDisabled = IsWidgetDisabled();
// Radius
const float ScaledRadius = (TWOD_AXIS_CIRCLE_RADIUS * Space.UniformScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const int32 CircleSides = (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize > 0)
? AXIS_CIRCLE_SIDES + (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize / 5)
: AXIS_CIRCLE_SIDES;
// Draw the grabbers
if (bDrawWidget)
{
FVector CornerPos = FVector(7, 0, 7) * Space.UniformScale;
FVector AxisSize = FVector(12, 1.2, 12) * Space.UniformScale;
float CornerLength = 1.2f * Space.UniformScale;
// Draw the axis lines with arrow heads
if (Space.ShouldDrawAxisX(DrawAxis))
{
UMaterialInstanceDynamic* XMaterial = (CurrentAxis&EAxisList::X ? CurrentAxisMaterial : AxisMaterialX);
Render_Axis(View, PDI, EAxisList::X, WidgetMatrix, XMaterial, XColor, XAxisDir, Space.Scale, bDrawWidget);
}
if (Space.ShouldDrawAxisY(DrawAxis))
{
UMaterialInstanceDynamic* YMaterial = (CurrentAxis&EAxisList::Y ? CurrentAxisMaterial : AxisMaterialY);
Render_Axis(View, PDI, EAxisList::Y, WidgetMatrix, YMaterial, YColor, YAxisDir, Space.Scale, bDrawWidget);
}
if (Space.ShouldDrawAxisZ(DrawAxis))
{
UMaterialInstanceDynamic* ZMaterial = (CurrentAxis&EAxisList::Z ? CurrentAxisMaterial : AxisMaterialZ);
Render_Axis(View, PDI, EAxisList::Z, WidgetMatrix, ZMaterial, ZColor, ZAxisDir, Space.Scale, bDrawWidget);
}
// After the primitives have been scaled and transformed, we apply this inverse scale that flattens the dimension
// that was scaled up to prevent it from intersecting with the near plane. In perspective this won't have any effect,
// but in the ortho viewports it will prevent scaling in the direction of the camera and thus intersecting the near plane.
FVector FlattenScale = FVector(Space.Scale.Component(0) == 1.0f ? 1.0f / Space.UniformScale : 1.0f, Space.Scale.Component(1) == 1.0f ? 1.0f / Space.UniformScale : 1.0f, Space.Scale.Component(2) == 1.0f ? 1.0f / Space.UniformScale : 1.0f);
float ArrowRadius = ScaledRadius * 2.0f;
float ArrowStartRadius = ScaledRadius * 1.3f;
uint8 HoverAlpha = 0xff;
uint8 NormalAlpha = 0x2f;
if (((DrawAxis&EAxisList::XZ) == EAxisList::XZ) && (Space.bIsPerspective || Space.bIsLocalSpace || Space.bIsOrthoXZ)) // Front
{
uint8 Alpha = ((CurrentAxis&EAxisList::XZ) == EAxisList::XZ ? HoverAlpha : NormalAlpha);
PDI->SetHitProxy(new HWidgetAxis(EAxisList::XZ, bDisabled));
{
FColor Color = YColor.ToFColor(true);
DrawCircle(PDI, InLocation, CustomCoordSystem.TransformPosition(FVector(1, 0, 0)), CustomCoordSystem.TransformPosition(FVector(0, 0, 1)), Color, ScaledRadius, CircleSides, SDPG_Foreground);
Color.A = Alpha;
DrawDisc(PDI, InLocation, CustomCoordSystem.TransformPosition(FVector(1, 0, 0)), CustomCoordSystem.TransformPosition(FVector(0, 0, 1)), Color, ScaledRadius, CircleSides, TransparentPlaneMaterialXY->GetRenderProxy(), SDPG_Foreground);
}
PDI->SetHitProxy(nullptr);
PDI->SetHitProxy(new HWidgetAxis(EAxisList::Rotate2D, bDisabled));
{
FColor Color = YColor.ToFColor(true);
Color.A = ((CurrentAxis&EAxisList::Rotate2D) == EAxisList::Rotate2D ? HoverAlpha : NormalAlpha);
FVector XAxis = CustomCoordSystem.TransformPosition(FVector(1, 0, 0).RotateAngleAxis((EditorModeTools ? EditorModeTools->TranslateRotate2DAngle : 0), FVector(0, -1, 0)));
FVector YAxis = CustomCoordSystem.TransformPosition(FVector(0, 0, 1).RotateAngleAxis((EditorModeTools ? EditorModeTools->TranslateRotate2DAngle : 0), FVector(0, -1, 0)));
FVector BaseArrowPoint = InLocation + XAxis * ArrowStartRadius;
DrawFlatArrow(PDI, BaseArrowPoint, XAxis, YAxis, Color, ArrowRadius, ArrowRadius*.5f, TransparentPlaneMaterialXY->GetRenderProxy(), SDPG_Foreground);
}
PDI->SetHitProxy(nullptr);
}
}
}
/**
* Caches off HUD text to display after 3d rendering is complete
* @param View - Information about the scene/camera/etc
* @param PDI - Drawing interface
* @param InLocation - The Origin of the widget
* @param Axis0 - The Axis that describes a 0 degree rotation
* @param Axis1 - The Axis that describes a 90 degree rotation
* @param AngleOfAngle - angle we've rotated so far (in degrees)
*/
void FWidget::CacheRotationHUDText(const FSceneView* View, FPrimitiveDrawInterface* PDI, const FVector& InLocation, const FVector& Axis0, const FVector& Axis1, const float AngleOfChange, const float InScale)
{
const float TextDistance = (ROTATION_TEXT_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
FVector AxisVectors[4] = { Axis0, Axis1, -Axis0, -Axis1};
for (int i = 0 ; i < 4; ++i)
{
FVector PotentialTextPosition = InLocation + (TextDistance)*AxisVectors[i];
if(View->ScreenToPixel(View->WorldToScreen(PotentialTextPosition), HUDInfoPos))
{
if (FMath::IsWithin<float>(HUDInfoPos.X, 0, View->UnscaledViewRect.Width()) && FMath::IsWithin<float>(HUDInfoPos.Y, 0, View->UnscaledViewRect.Height()))
{
//only valid screen locations get a valid string
HUDString = FString::Printf(TEXT("%3.2f"), AngleOfChange);
break;
}
}
}
}
/**
* If actively dragging, draws a ring representing the potential rotation of the selected objects, snap ticks, and "delta" markers
* If not actively dragging, draws a quarter ring representing the closest quadrant to the camera
* @param View - Information about the scene/camera/etc
* @param PDI - Drawing interface
* @param InAxis - Enumeration of axis to rotate about
* @param InLocation - The Origin of the widget
* @param Axis0 - The Axis that describes a 0 degree rotation
* @param Axis1 - The Axis that describes a 90 degree rotation
* @param InDirectionToWidget - Direction from camera to the widget
* @param InColor - The color associated with the axis of rotation
* @param InScale - Multiplier to maintain a constant screen size for rendering the widget
*/
void FWidget::DrawRotationArc(const FSceneView* View, FPrimitiveDrawInterface* PDI, EAxisList::Type InAxis, const FVector& InLocation, const FVector& Axis0, const FVector& Axis1, const FVector& InDirectionToWidget, const FColor& InColor, const float InScale, FVector2D& OutAxisDir)
{
bool bIsPerspective = ( View->ViewMatrices.GetProjectionMatrix().M[3][3] < 1.0f );
bool bIsOrtho = !bIsPerspective;
//if we're in an ortho viewport and the ring is perpendicular to the camera (both Axis0 & Axis1 are perpendicular)
bIsOrthoDrawingFullRing |= bIsOrtho && (FMath::Abs(Axis0|InDirectionToWidget) < KINDA_SMALL_NUMBER) && (FMath::Abs(Axis1|InDirectionToWidget) < KINDA_SMALL_NUMBER);
FColor ArcColor = InColor;
ArcColor.A = LargeOuterAlpha;
if (bDragging || (bIsOrthoDrawingFullRing))
{
if ((CurrentAxis&InAxis) || (bIsOrthoDrawingFullRing))
{
bool bDrawingArcBallAlso = CurrentAxis == EAxisList::XYZ;
float DeltaRotation = GetDeltaRotation();
float AdjustedDeltaRotation = IsRotationLocalSpace() ? -DeltaRotation : DeltaRotation;
float AbsRotation = FRotator::ClampAxis(FMath::Abs(DeltaRotation));
float AngleOfChangeRadians (AbsRotation * PI / 180.f);
//always draw clockwise, so if we're negative we need to flip the angle
float StartAngle = AdjustedDeltaRotation < 0.0f ? -AngleOfChangeRadians : 0.0f;
float FilledAngle = AngleOfChangeRadians;
//the axis of rotation
FVector ZAxis = Axis0 ^ Axis1;
ArcColor.A = LargeOuterAlpha;
DrawPartialRotationArc(View, PDI, InAxis, InLocation, Axis0, Axis1, StartAngle, StartAngle + FilledAngle, ArcColor, InScale, InDirectionToWidget);
ArcColor.A = SmallOuterAlpha;
DrawPartialRotationArc(View, PDI, InAxis, InLocation, Axis0, Axis1, StartAngle + FilledAngle, StartAngle + 2*PI, ArcColor, InScale, InDirectionToWidget);
ArcColor = (CurrentAxis&InAxis && !bDrawingArcBallAlso) ? CurrentColor : ArcColor;
if (!bDrawingArcBallAlso)
{
//Hallow Arrow
ArcColor.A = 0;
DrawStartStopMarker(PDI, InLocation, Axis0, Axis1, 0, ArcColor, InScale);
//Filled Arrow
ArcColor.A = LargeOuterAlpha;
DrawStartStopMarker(PDI, InLocation, Axis0, Axis1, AdjustedDeltaRotation, ArcColor, InScale);
ArcColor.A = 255;
FVector SnapLocation = InLocation;
if (GetDefault<ULevelEditorViewportSettings>()->RotGridEnabled)
{
float DeltaAngle = GEditor->GetRotGridSize().Yaw;
//every 22.5 degrees
float TickMarker = 22.5f;
for (float Angle = 0; Angle < 360.f; Angle += DeltaAngle)
{
FVector GridAxis = Axis0.RotateAngleAxis(Angle, ZAxis);
float PercentSize = (FMath::Fmod(Angle, TickMarker) == 0) ? .75f : .25f;
if (FMath::Fmod(Angle, 90.f) != 0)
{
DrawSnapMarker(PDI, SnapLocation, GridAxis, FVector::ZeroVector, ArcColor, InScale, 0.0f, PercentSize);
}
}
}
//draw axis tick marks
FColor AxisColor = InColor;
//Rotate Colors to match Axis 0
Swap(AxisColor.R, AxisColor.G);
Swap(AxisColor.B, AxisColor.R);
AxisColor.A = (AdjustedDeltaRotation == 0) ? MAX_uint8 : LargeOuterAlpha;
DrawSnapMarker(PDI, SnapLocation, Axis0, Axis1, AxisColor, InScale, .25f);
AxisColor.A = (AdjustedDeltaRotation == 180.f) ? MAX_uint8 : LargeOuterAlpha;
DrawSnapMarker(PDI, SnapLocation, -Axis0, -Axis1, AxisColor, InScale, .25f);
//Rotate Colors to match Axis 1
Swap(AxisColor.R, AxisColor.G);
Swap(AxisColor.B, AxisColor.R);
AxisColor.A = (AdjustedDeltaRotation == 90.f) ? MAX_uint8 : LargeOuterAlpha;
DrawSnapMarker(PDI, SnapLocation, Axis1, -Axis0, AxisColor, InScale, .25f);
AxisColor.A = (AdjustedDeltaRotation == 270.f) ? MAX_uint8 : LargeOuterAlpha;
DrawSnapMarker(PDI, SnapLocation, -Axis1, Axis0, AxisColor, InScale, .25f);
if (bDragging)
{
float OffsetAngle = IsRotationLocalSpace() ? 0 : AdjustedDeltaRotation;
if (AxisDisplayInfo::GetAxisDisplayCoordinateSystem() == EAxisList::LeftUpForward)
{
if (InAxis == EAxisList::Y || InAxis == EAxisList::Z)
{
DeltaRotation = -DeltaRotation;
}
}
CacheRotationHUDText(View, PDI, InLocation, Axis0.RotateAngleAxis(OffsetAngle, ZAxis), Axis1.RotateAngleAxis(OffsetAngle, ZAxis), DeltaRotation, InScale);
}
}
}
}
else
{
//Reverse the axes based on camera view
bool bMirrorAxis0 = ((Axis0 | InDirectionToWidget) <= 0.0f);
bool bMirrorAxis1 = ((Axis1 | InDirectionToWidget) <= 0.0f);
FVector RenderAxis0 = bMirrorAxis0 ? Axis0 : -Axis0;
FVector RenderAxis1 = bMirrorAxis1 ? Axis1 : -Axis1;
float Direction = (bMirrorAxis0 ^ bMirrorAxis1) ? -1.0f : 1.0f;
DrawPartialRotationArc(View, PDI, InAxis, InLocation, RenderAxis0, RenderAxis1, 0, PI/2, ArcColor, InScale, InDirectionToWidget);
FVector2D Axis0ScreenLocation;
if (!View->ScreenToPixel(View->WorldToScreen(InLocation + RenderAxis0 * 64.0f), Axis0ScreenLocation))
{
Axis0ScreenLocation.X = Axis0ScreenLocation.Y = 0;
}
FVector2D Axis1ScreenLocation;
if (!View->ScreenToPixel(View->WorldToScreen(InLocation + RenderAxis1 * 64.0f), Axis1ScreenLocation))
{
Axis1ScreenLocation.X = Axis1ScreenLocation.Y = 0;
}
OutAxisDir = ((Axis1ScreenLocation - Axis0ScreenLocation) * Direction).GetSafeNormal();
}
}
/**
* If actively dragging, draws a ring representing the potential rotation of the selected objects, snap ticks, and "delta" markers
* If not actively dragging, draws a quarter ring representing the closest quadrant to the camera
* @param View - Information about the scene/camera/etc
* @param PDI - Drawing interface
* @param InAxis - Enumeration of axis to rotate about
* @param InLocation - The Origin of the widget
* @param Axis0 - The Axis that describes a 0 degree rotation
* @param Axis1 - The Axis that describes a 90 degree rotation
* @param InStartAngle - The starting angle about (Axis0^Axis1) to render the arc, in radians
* @param InEndAngle - The ending angle about (Axis0^Axis1) to render the arc, in radians
* @param InColor - The color associated with the axis of rotation
* @param InScale - Multiplier to maintain a constant screen size for rendering the widget
*/
void FWidget::DrawPartialRotationArc(const FSceneView* View, FPrimitiveDrawInterface* PDI, EAxisList::Type InAxis, const FVector& InLocation, const FVector& Axis0, const FVector& Axis1, const float InStartAngle, const float InEndAngle, const FColor& InColor, const float InScale, const FVector& InDirectionToWidget )
{
const float InnerRadius = (INNER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float OuterRadius = (OUTER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
bool bIsPerspective = ( View->ViewMatrices.GetProjectionMatrix().M[3][3] < 1.0f );
PDI->SetHitProxy( new HWidgetAxis( InAxis ) );
{
FThickArcParams OuterArcParams(PDI, InLocation, TransparentPlaneMaterialXY, InnerRadius, OuterRadius);
FColor OuterColor = ( (CurrentAxis&InAxis && ( CurrentAxis != EAxisList::XYZ ) )? CurrentColor : InColor );
//Pass through alpha
OuterColor.A = InColor.A;
DrawThickArc(OuterArcParams, Axis0, Axis1, InStartAngle, InEndAngle, OuterColor, InDirectionToWidget, !bIsPerspective );
}
PDI->SetHitProxy( nullptr );
const bool bIsHitProxyView = View->Family->EngineShowFlags.HitProxies;
if (bIsPerspective && !bIsHitProxyView && !PDI->IsHitTesting())
{
FThickArcParams InnerArcParams(PDI, InLocation, GridMaterial, 0.0f, InnerRadius);
FColor InnerColor = InColor;
//if something is selected and it's not this
InnerColor.A = ((CurrentAxis & InAxis) && !bDragging) ? LargeInnerAlpha : SmallInnerAlpha;
DrawThickArc(InnerArcParams, Axis0, Axis1, InStartAngle, InEndAngle, InnerColor, InDirectionToWidget, false );
}
}
/**
* Renders a portion of an arc for the rotation widget
* @param InParams - Material, Radii, etc
* @param InStartAxis - Start of the arc, in radians
* @param InEndAxis - End of the arc, in radians
* @param InColor - Color to use for the arc
*/
void FWidget::DrawThickArc (const FThickArcParams& InParams, const FVector& Axis0, const FVector& Axis1, const float InStartAngle, const float InEndAngle, const FColor& InColor, const FVector& InDirectionToWidget, bool bIsOrtho )
{
if (InColor.A == 0)
{
return;
}
// Add more sides to the circle if we've been scaled up to keep the circle looking circular
// An extra side for every 5 extra unreal units seems to produce a nice result
const int32 CircleSides = (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize > 0)
? AXIS_CIRCLE_SIDES + (GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize / 5)
: AXIS_CIRCLE_SIDES;
const int32 NumPoints = FMath::TruncToInt(CircleSides * (InEndAngle-InStartAngle)/(PI/2)) + 1;
FColor TriangleColor = InColor;
FColor RingColor = InColor;
RingColor.A = MAX_uint8;
FVector ZAxis = Axis0 ^ Axis1;
FVector LastWorldVertex;
FDynamicMeshBuilder MeshBuilder(InParams.PDI->View->GetFeatureLevel());
for (int32 RadiusIndex = 0; RadiusIndex < 2; ++RadiusIndex)
{
float Radius = (RadiusIndex == 0) ? InParams.OuterRadius : InParams.InnerRadius;
float TCRadius = Radius / (float) InParams.OuterRadius;
//Compute vertices for base circle.
for(int32 VertexIndex = 0;VertexIndex <= NumPoints;VertexIndex++)
{
float Percent = VertexIndex/(float)NumPoints;
float Angle = FMath::Lerp(InStartAngle, InEndAngle, Percent);
float AngleDeg = FRotator::ClampAxis(Angle * 180.f / PI);
FVector VertexDir = Axis0.RotateAngleAxis(AngleDeg, ZAxis);
VertexDir.Normalize();
float TCAngle = Percent*(PI/2);
FVector2f TC(TCRadius*FMath::Cos(Angle), TCRadius*FMath::Sin(Angle));
// Keep the vertices in local space so that we don't lose precision when dealing with LWC
// The local-to-world transform is handled in the MeshBuilder.Draw() call at the end of this function
const FVector VertexPosition = VertexDir*Radius;
FVector Normal = VertexPosition;
Normal.Normalize();
FDynamicMeshVertex MeshVertex;
MeshVertex.Position = (FVector3f)VertexPosition;
MeshVertex.Color = TriangleColor;
MeshVertex.TextureCoordinate[0] = TC;
MeshVertex.SetTangents(
(FVector3f)-ZAxis,
FVector3f((-ZAxis) ^ Normal),
(FVector3f)Normal
);
MeshBuilder.AddVertex(MeshVertex); //Add bottom vertex
// Push out the arc line borders so they dont z-fight with the mesh arcs
// DrawLine needs vertices in world space, but this is fine because it takes FVectors and works with LWC well
FVector EndLinePos = VertexPosition + InParams.Position;
if (VertexIndex != 0)
{
InParams.PDI->DrawLine(LastWorldVertex,EndLinePos,RingColor,SDPG_Foreground);
}
LastWorldVertex = EndLinePos;
}
}
//Add top/bottom triangles, in the style of a fan.
int32 InnerVertexStartIndex = NumPoints + 1;
for(int32 VertexIndex = 0; VertexIndex < NumPoints; VertexIndex++)
{
MeshBuilder.AddTriangle(VertexIndex, VertexIndex+1, InnerVertexStartIndex+VertexIndex);
MeshBuilder.AddTriangle(VertexIndex+1, InnerVertexStartIndex+VertexIndex+1, InnerVertexStartIndex+VertexIndex);
}
MeshBuilder.Draw(InParams.PDI, FTranslationMatrix(InParams.Position), InParams.Material->GetRenderProxy(),SDPG_Foreground,0.f);
}
/**
* Draws protractor like ticks where the rotation widget would snap too.
* Also, used to draw the wider axis tick marks
* @param PDI - Drawing interface
* @param InLocation - The Origin of the widget
* @param Axis0 - The Axis that describes a 0 degree rotation
* @param Axis1 - The Axis that describes a 90 degree rotation
* @param InAngle - The Angle to rotate about the axis of rotation, the vector (Axis0 ^ Axis1)
* @param InColor - The color to use for line/poly drawing
* @param InScale - Multiplier to maintain a constant screen size for rendering the widget
* @param InWidthPercent - The percent of the distance between the outer ring and inner ring to use for tangential thickness
* @param InPercentSize - The percent of the distance between the outer ring and inner ring to use for radial distance
*/
void FWidget::DrawSnapMarker(FPrimitiveDrawInterface* PDI, const FVector& InLocation, const FVector& Axis0, const FVector& Axis1, const FColor& InColor, const float InScale, const float InWidthPercent, const float InPercentSize)
{
const float InnerDistance = (INNER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float OuterDistance = (OUTER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float MaxMarkerHeight = OuterDistance - InnerDistance;
const float MarkerWidth = MaxMarkerHeight*InWidthPercent;
const float MarkerHeight = MaxMarkerHeight*InPercentSize;
FVector LocalVertices[4];
LocalVertices[0] = (OuterDistance)*Axis0 - (MarkerWidth*.5)*Axis1;
LocalVertices[1] = LocalVertices[0] + (MarkerWidth)*Axis1;
LocalVertices[2] = (OuterDistance-MarkerHeight)*Axis0 - (MarkerWidth*.5)*Axis1;
LocalVertices[3] = LocalVertices[2] + (MarkerWidth)*Axis1;
//draw at least one line
// DrawLine needs vertices in world space, but this is fine because it takes FVectors and works with LWC well
PDI->DrawLine(LocalVertices[0] + InLocation, LocalVertices[2] + InLocation, InColor, SDPG_Foreground);
//if there should be thickness, draw the other lines
if (InWidthPercent > 0.0f)
{
PDI->DrawLine(LocalVertices[0] + InLocation, LocalVertices[1] + InLocation, InColor, SDPG_Foreground);
PDI->DrawLine(LocalVertices[1] + InLocation, LocalVertices[3] + InLocation, InColor, SDPG_Foreground);
PDI->DrawLine(LocalVertices[2] + InLocation, LocalVertices[3] + InLocation, InColor, SDPG_Foreground);
//fill in the box
FDynamicMeshBuilder MeshBuilder(PDI->View->GetFeatureLevel());
for(int32 VertexIndex = 0;VertexIndex < 4; VertexIndex++)
{
FDynamicMeshVertex MeshVertex;
MeshVertex.Position = (FVector3f)LocalVertices[VertexIndex];
MeshVertex.Color = InColor;
MeshVertex.TextureCoordinate[0] = FVector2f(0.0f, 0.0f);
MeshVertex.SetTangents(
(FVector3f)Axis0,
(FVector3f)Axis1,
FVector3f((Axis0) ^ Axis1)
);
MeshBuilder.AddVertex(MeshVertex); //Add bottom vertex
}
MeshBuilder.AddTriangle(0, 1, 2);
MeshBuilder.AddTriangle(1, 3, 2);
MeshBuilder.Draw(PDI, FTranslationMatrix(InLocation), TransparentPlaneMaterialXY->GetRenderProxy(),SDPG_Foreground,0.f);
}
}
/**
* Draw Start/Stop Marker to show delta rotations along the arc of rotation
* @param PDI - Drawing interface
* @param InLocation - The Origin of the widget
* @param Axis0 - The Axis that describes a 0 degree rotation
* @param Axis1 - The Axis that describes a 90 degree rotation
* @param InAngle - The Angle to rotate about the axis of rotation, the vector (Axis0 ^ Axis1), units are degrees
* @param InColor - The color to use for line/poly drawing
* @param InScale - Multiplier to maintain a constant screen size for rendering the widget
*/
void FWidget::DrawStartStopMarker(FPrimitiveDrawInterface* PDI, const FVector& InLocation, const FVector& Axis0, const FVector& Axis1, const float InAngle, const FColor& InColor, const float InScale)
{
const float ArrowHeightPercent = .8f;
const float InnerDistance = (INNER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float OuterDistance = (OUTER_AXIS_CIRCLE_RADIUS * InScale) + GetDefault<UTransformGizmoEditorSettings>()->TransformGizmoSize;
const float RingHeight = OuterDistance - InnerDistance;
const float ArrowHeight = RingHeight*ArrowHeightPercent;
const float ThirtyDegrees = PI / 6.0f;
const float HalfArrowidth = ArrowHeight*FMath::Tan(ThirtyDegrees);
FVector ZAxis = Axis0 ^ Axis1;
FVector RotatedAxis0 = Axis0.RotateAngleAxis(InAngle, ZAxis);
FVector RotatedAxis1 = Axis1.RotateAngleAxis(InAngle, ZAxis);
FVector LocalVertices[3];
LocalVertices[0] = (OuterDistance)*RotatedAxis0;
LocalVertices[1] = LocalVertices[0] + (ArrowHeight)*RotatedAxis0 - HalfArrowidth*RotatedAxis1;
LocalVertices[2] = LocalVertices[1] + (2*HalfArrowidth)*RotatedAxis1;
// DrawLine needs vertices in world space, but this is fine because it takes FVectors and works with LWC well
PDI->DrawLine(LocalVertices[0] + InLocation, LocalVertices[1] + InLocation, InColor, SDPG_Foreground);
PDI->DrawLine(LocalVertices[1] + InLocation, LocalVertices[2] + InLocation, InColor, SDPG_Foreground);
PDI->DrawLine(LocalVertices[0] + InLocation, LocalVertices[2] + InLocation, InColor, SDPG_Foreground);
if (InColor.A > 0)
{
//fill in the box
FDynamicMeshBuilder MeshBuilder(PDI->View->GetFeatureLevel());
for(int32 VertexIndex = 0;VertexIndex < 3; VertexIndex++)
{
FDynamicMeshVertex MeshVertex;
MeshVertex.Position = (FVector3f)LocalVertices[VertexIndex];
MeshVertex.Color = InColor;
MeshVertex.TextureCoordinate[0] = FVector2f(0.0f, 0.0f);
MeshVertex.SetTangents(
(FVector3f)RotatedAxis0,
(FVector3f)RotatedAxis1,
FVector3f((RotatedAxis0) ^ RotatedAxis1)
);
MeshBuilder.AddVertex(MeshVertex); //Add bottom vertex
}
MeshBuilder.AddTriangle(0, 1, 2);
MeshBuilder.Draw(PDI, FTranslationMatrix(InLocation), TransparentPlaneMaterialXY->GetRenderProxy(),SDPG_Foreground,0.f);
}
}
EAxisList::Type FWidget::GetAxisToDraw( UE::Widget::EWidgetMode WidgetMode ) const
{
return EditorModeTools ? EditorModeTools->GetWidgetAxisToDraw( WidgetMode ) : EAxisList::All;
}