2198 lines
81 KiB
C++
2198 lines
81 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BaseGizmos/CombinedTransformGizmo.h"
|
|
#include "InteractiveGizmoManager.h"
|
|
#include "SceneQueries/SceneSnappingManager.h"
|
|
#include "BaseGizmos/AxisPositionGizmo.h"
|
|
#include "BaseGizmos/FreePositionSubGizmo.h"
|
|
#include "BaseGizmos/FreeRotationSubGizmo.h"
|
|
#include "BaseGizmos/PlanePositionGizmo.h"
|
|
#include "BaseGizmos/AxisAngleGizmo.h"
|
|
#include "BaseGizmos/GizmoComponents.h"
|
|
#include "BaseGizmos/GizmoUtil.h"
|
|
|
|
#include "BaseGizmos/GizmoArrowComponent.h"
|
|
#include "BaseGizmos/GizmoRectangleComponent.h"
|
|
#include "BaseGizmos/GizmoCircleComponent.h"
|
|
#include "BaseGizmos/GizmoBoxComponent.h"
|
|
#include "BaseGizmos/GizmoLineHandleComponent.h"
|
|
#include "BaseGizmos/GizmoPrivateUtil.h" // ToAxis
|
|
#include "BaseGizmos/GizmoRenderingUtil.h"
|
|
#include "BaseGizmos/GizmoViewContext.h"
|
|
#include "BaseGizmos/TransformSubGizmoUtil.h" // FTransformSubGizmoCommonParams, FTransformSubGizmoSharedState
|
|
#include "BaseGizmos/ViewAdjustedStaticMeshGizmoComponent.h"
|
|
#include "BaseGizmos/ViewBasedTransformAdjusters.h" // FSubGizmoTransformAdjuster
|
|
|
|
#include "Quaternion.h"
|
|
#include "MathUtil.h"
|
|
#include "MatrixTypes.h"
|
|
#include "VectorUtil.h"
|
|
|
|
// need this to implement hover
|
|
#include "BaseGizmos/GizmoBaseComponent.h"
|
|
|
|
#include "Components/SphereComponent.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "ContextObjectStore.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine/CollisionProfile.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "HAL/IConsoleManager.h" // FAutoConsoleVariableRef
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(CombinedTransformGizmo)
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "UCombinedTransformGizmo"
|
|
|
|
namespace CombinedTransformGizmoLocals
|
|
{
|
|
const int32 DrawModeValue_Meshes = 1;
|
|
|
|
// CVar that determines how we draw the gizmo
|
|
int32 GizmoDrawMode = DrawModeValue_Meshes;
|
|
static FAutoConsoleVariableRef CVarGizmoDrawMode(
|
|
TEXT("modeling.Gizmo.DrawMode"),
|
|
GizmoDrawMode,
|
|
TEXT("When 0, modeling gizmos are drawn using the old PDI system. When 1, modeling gizmos use new adjusted-size components. "
|
|
"Gizmos have to be recreated (by restarting mode/tools) for the change to take effect."));
|
|
|
|
// Helper functions that determine whether parts of the gizmo should be visible when using DrawModeValue_Meshes. For example
|
|
// we don't want the size of a rotation component to be hiding the axis behind it in ortho view.
|
|
auto ShouldAxisBeVisible = [](const UE::GizmoRenderingUtil::ISceneViewInterface& View,
|
|
const FTransform& ComponentToWorld)
|
|
{
|
|
static const double ARROW_RENDERVISIBILITY_DOT_THRESHOLD = FMath::Cos(FMath::DegreesToRadians(3));
|
|
|
|
FVector ViewDirection = View.IsPerspectiveProjection() ?
|
|
ComponentToWorld.GetLocation() - View.GetViewLocation() : View.GetViewDirection();
|
|
ViewDirection.Normalize();
|
|
FVector ArrowDirection = ComponentToWorld.TransformVector(FVector::XAxisVector);
|
|
ArrowDirection.Normalize();
|
|
|
|
return FMath::Abs(FVector::DotProduct(ArrowDirection, ViewDirection)) <= ARROW_RENDERVISIBILITY_DOT_THRESHOLD;
|
|
};
|
|
auto ShouldPlaneBeVisible = [](const UE::GizmoRenderingUtil::ISceneViewInterface& View,
|
|
const FTransform& ComponentToWorld)
|
|
{
|
|
static const double RECTANGLE_RENDERVISIBILITY_DOT_THRESHOLD = FMath::Cos(FMath::DegreesToRadians(87));;
|
|
|
|
FVector ViewDirection = View.IsPerspectiveProjection() ?
|
|
ComponentToWorld.GetLocation() - View.GetViewLocation() : View.GetViewDirection();
|
|
ViewDirection.Normalize();
|
|
FVector PlaneNormal = ComponentToWorld.TransformVector(FVector::XAxisVector);
|
|
PlaneNormal.Normalize();
|
|
|
|
return
|
|
FMath::Abs(FVector::DotProduct(PlaneNormal, ViewDirection)) >= RECTANGLE_RENDERVISIBILITY_DOT_THRESHOLD;
|
|
};
|
|
|
|
FLinearColor FreeRotateColor(0.5f, 0.5f, 0.5f, 0.15f); // A faint translucent gray
|
|
// Slightly gray so that the selection highlight pops a bit more
|
|
FLinearColor FreeTranslateColor(0.7f, 0.7f, 0.7f, 1.0f);
|
|
FLinearColor UniformScaleColor = FreeTranslateColor;
|
|
FVector CornerScalePositionCombined(0, 120, 120);
|
|
FVector CornerScalePositionSeparate(0, 75, 75);
|
|
double CornerScaleHandleScale = 0.5;
|
|
|
|
// Helper functions that get the appropriate values to use from an EAxis value, so
|
|
// that we can write helpers that just take that as an argument.
|
|
FLinearColor AxisToLegacyColor(EAxis::Type Axis)
|
|
{
|
|
switch (Axis)
|
|
{
|
|
case EAxis::X:
|
|
return FLinearColor::Red;
|
|
case EAxis::Y:
|
|
return FLinearColor::Green;
|
|
case EAxis::Z:
|
|
return FLinearColor::Blue;
|
|
default:
|
|
ensure(false);
|
|
}
|
|
return FLinearColor::Black;
|
|
}
|
|
FVector AxisToVector(EAxis::Type Axis)
|
|
{
|
|
switch (Axis)
|
|
{
|
|
case EAxis::X:
|
|
return FVector::XAxisVector;
|
|
case EAxis::Y:
|
|
return FVector::YAxisVector;
|
|
case EAxis::Z:
|
|
return FVector::ZAxisVector;
|
|
default:
|
|
ensure(false);
|
|
}
|
|
return FVector::XAxisVector;
|
|
}
|
|
void AxisToLegacyPairOfVectors(EAxis::Type Axis, FVector& Vector1, FVector& Vector2)
|
|
{
|
|
switch (Axis)
|
|
{
|
|
case EAxis::X:
|
|
Vector1 = FVector::YAxisVector;
|
|
Vector2 = FVector::ZAxisVector;
|
|
return;
|
|
case EAxis::Y:
|
|
Vector1 = FVector::XAxisVector;
|
|
Vector2 = FVector::ZAxisVector;
|
|
return;
|
|
case EAxis::Z:
|
|
Vector1 = FVector::XAxisVector;
|
|
Vector2 = FVector::YAxisVector;
|
|
return;
|
|
default:
|
|
ensure(false);
|
|
}
|
|
}
|
|
|
|
// Looks at a gizmo actor and figures out what sub element flags must have been active when creating it.
|
|
ETransformGizmoSubElements GetSubElementFlagsFromActor(ACombinedTransformGizmoActor* GizmoActor)
|
|
{
|
|
ETransformGizmoSubElements Elements = ETransformGizmoSubElements::None;
|
|
if (!GizmoActor)
|
|
{
|
|
return Elements;
|
|
}
|
|
|
|
if (GizmoActor->TranslateX) { Elements |= ETransformGizmoSubElements::TranslateAxisX; }
|
|
if (GizmoActor->TranslateY) { Elements |= ETransformGizmoSubElements::TranslateAxisY; }
|
|
if (GizmoActor->TranslateZ) { Elements |= ETransformGizmoSubElements::TranslateAxisZ; }
|
|
if (GizmoActor->TranslateXY) { Elements |= ETransformGizmoSubElements::TranslatePlaneXY; }
|
|
if (GizmoActor->TranslateYZ) { Elements |= ETransformGizmoSubElements::TranslatePlaneYZ; }
|
|
if (GizmoActor->TranslateXZ) { Elements |= ETransformGizmoSubElements::TranslatePlaneXZ; }
|
|
if (GizmoActor->FreeTranslateHandle) { Elements |= ETransformGizmoSubElements::FreeTranslate; }
|
|
|
|
if (GizmoActor->RotateX) { Elements |= ETransformGizmoSubElements::RotateAxisX; }
|
|
if (GizmoActor->RotateY) { Elements |= ETransformGizmoSubElements::RotateAxisY; }
|
|
if (GizmoActor->RotateZ) { Elements |= ETransformGizmoSubElements::RotateAxisZ; }
|
|
if (GizmoActor->FreeRotateHandle) { Elements |= ETransformGizmoSubElements::FreeRotate; }
|
|
|
|
if (GizmoActor->AxisScaleX) { Elements |= ETransformGizmoSubElements::ScaleAxisX; }
|
|
if (GizmoActor->AxisScaleY) { Elements |= ETransformGizmoSubElements::ScaleAxisY; }
|
|
if (GizmoActor->AxisScaleZ) { Elements |= ETransformGizmoSubElements::ScaleAxisZ; }
|
|
if (GizmoActor->PlaneScaleXY) { Elements |= ETransformGizmoSubElements::ScalePlaneXY; }
|
|
if (GizmoActor->PlaneScaleYZ) { Elements |= ETransformGizmoSubElements::ScalePlaneYZ; }
|
|
if (GizmoActor->PlaneScaleXZ) { Elements |= ETransformGizmoSubElements::ScalePlaneXZ; }
|
|
|
|
if (GizmoActor->UniformScale) { Elements |= ETransformGizmoSubElements::ScaleUniform; }
|
|
|
|
return Elements;
|
|
}
|
|
}
|
|
|
|
ACombinedTransformGizmoActor::ACombinedTransformGizmoActor()
|
|
{
|
|
// root component is a hidden sphere
|
|
USphereComponent* SphereComponent = CreateDefaultSubobject<USphereComponent>(TEXT("GizmoCenter"));
|
|
RootComponent = SphereComponent;
|
|
SphereComponent->InitSphereRadius(1.0f);
|
|
SphereComponent->SetVisibility(false);
|
|
SphereComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ACombinedTransformGizmoActor::ReplaceSubGizmoComponent(ETransformGizmoSubElements Element,
|
|
UPrimitiveComponent* NewComponent, const FTransform& SubGizmoToGizmo, UPrimitiveComponent** ReplacedComponentOut)
|
|
{
|
|
// We allow a null NewComponent (which equates to element removal), but if we do have a component,
|
|
// it should have this actor in its outer chain. It might be possible to loosen that restriction,
|
|
// but it's likely that something is wrong in this case.
|
|
if (NewComponent && !ensure(NewComponent->GetOwner() == this))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto ReplaceComponent = [this, &SubGizmoToGizmo, NewComponent, ReplacedComponentOut](TObjectPtr<UPrimitiveComponent>& ComponentToReplace)
|
|
{
|
|
if (ComponentToReplace)
|
|
{
|
|
ComponentToReplace->DestroyComponent();
|
|
}
|
|
if (ReplacedComponentOut)
|
|
{
|
|
*ReplacedComponentOut = ComponentToReplace;
|
|
}
|
|
|
|
ComponentToReplace = NewComponent;
|
|
|
|
if (NewComponent)
|
|
{
|
|
AddInstanceComponent(NewComponent);
|
|
NewComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
NewComponent->SetRelativeTransform(SubGizmoToGizmo);
|
|
if (!NewComponent->IsRegistered())
|
|
{
|
|
NewComponent->RegisterComponent();
|
|
}
|
|
}
|
|
};
|
|
|
|
switch (Element)
|
|
{
|
|
case ETransformGizmoSubElements::TranslateAxisX:
|
|
ReplaceComponent(TranslateX);
|
|
break;
|
|
case ETransformGizmoSubElements::TranslateAxisY:
|
|
ReplaceComponent(TranslateY);
|
|
break;
|
|
case ETransformGizmoSubElements::TranslateAxisZ:
|
|
ReplaceComponent(TranslateZ);
|
|
break;
|
|
case ETransformGizmoSubElements::TranslatePlaneXY:
|
|
ReplaceComponent(TranslateXY);
|
|
break;
|
|
case ETransformGizmoSubElements::TranslatePlaneXZ:
|
|
ReplaceComponent(TranslateXZ);
|
|
break;
|
|
case ETransformGizmoSubElements::TranslatePlaneYZ:
|
|
ReplaceComponent(TranslateYZ);
|
|
break;
|
|
case ETransformGizmoSubElements::RotateAxisX:
|
|
ReplaceComponent(RotateX);
|
|
break;
|
|
case ETransformGizmoSubElements::RotateAxisY:
|
|
ReplaceComponent(RotateY);
|
|
break;
|
|
case ETransformGizmoSubElements::RotateAxisZ:
|
|
ReplaceComponent(RotateZ);
|
|
break;
|
|
case ETransformGizmoSubElements::ScaleAxisX:
|
|
ReplaceComponent(AxisScaleX);
|
|
if (FullAxisScaleX)
|
|
{
|
|
FullAxisScaleX->DestroyComponent();
|
|
FullAxisScaleX = nullptr;
|
|
}
|
|
break;
|
|
case ETransformGizmoSubElements::ScaleAxisY:
|
|
ReplaceComponent(AxisScaleY);
|
|
if (FullAxisScaleY)
|
|
{
|
|
FullAxisScaleY->DestroyComponent();
|
|
FullAxisScaleY = nullptr;
|
|
}
|
|
break;
|
|
case ETransformGizmoSubElements::ScaleAxisZ:
|
|
ReplaceComponent(AxisScaleZ);
|
|
if (FullAxisScaleZ)
|
|
{
|
|
FullAxisScaleZ->DestroyComponent();
|
|
FullAxisScaleZ = nullptr;
|
|
}
|
|
break;
|
|
case ETransformGizmoSubElements::ScalePlaneXY:
|
|
ReplaceComponent(PlaneScaleXY);
|
|
break;
|
|
case ETransformGizmoSubElements::ScalePlaneXZ:
|
|
ReplaceComponent(PlaneScaleXZ);
|
|
break;
|
|
case ETransformGizmoSubElements::ScalePlaneYZ:
|
|
ReplaceComponent(PlaneScaleYZ);
|
|
break;
|
|
case ETransformGizmoSubElements::ScaleUniform:
|
|
ReplaceComponent(UniformScale);
|
|
break;
|
|
// We use the RotateAllAxes identifier for replacing the rotation sphere
|
|
case ETransformGizmoSubElements::RotateAllAxes:
|
|
ReplaceComponent(RotationSphere);
|
|
break;
|
|
case ETransformGizmoSubElements::FreeRotate:
|
|
ReplaceComponent(FreeRotateHandle);
|
|
break;
|
|
case ETransformGizmoSubElements::FreeTranslate:
|
|
ReplaceComponent(FreeTranslateHandle);
|
|
break;
|
|
default:
|
|
UE_LOG(LogGeometry, Warning, TEXT("UCombinedTransformGizmo::SetSubGizmoComponent currently only supports a "
|
|
"single sub gizmo element at a time."));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ACombinedTransformGizmoActor* ACombinedTransformGizmoActor::ConstructDefault3AxisGizmo(UWorld* World, UGizmoViewContext* GizmoViewContext)
|
|
{
|
|
return ConstructCustom3AxisGizmo(World, GizmoViewContext,
|
|
ETransformGizmoSubElements::TranslateAllAxes |
|
|
ETransformGizmoSubElements::TranslateAllPlanes |
|
|
ETransformGizmoSubElements::RotateAllAxes |
|
|
ETransformGizmoSubElements::ScaleAllAxes |
|
|
ETransformGizmoSubElements::ScaleAllPlanes |
|
|
ETransformGizmoSubElements::ScaleUniform
|
|
);
|
|
}
|
|
|
|
|
|
ACombinedTransformGizmoActor* ACombinedTransformGizmoActor::ConstructCustom3AxisGizmo(
|
|
UWorld* World, UGizmoViewContext* GizmoViewContext,
|
|
ETransformGizmoSubElements Elements)
|
|
{
|
|
using namespace CombinedTransformGizmoLocals;
|
|
using FSubGizmoTransformAdjuster = UE::GizmoRenderingUtil::FSubGizmoTransformAdjuster;
|
|
|
|
FActorSpawnParameters SpawnInfo;
|
|
ACombinedTransformGizmoActor* NewActor = World->SpawnActor<ACombinedTransformGizmoActor>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo);
|
|
|
|
float GizmoLineThickness = 3.0f;
|
|
|
|
enum class EMirror
|
|
{
|
|
Always,
|
|
WhenCombined,
|
|
Never
|
|
};
|
|
|
|
// Helper for adding a mesh-based sub gizmo component (when using DrawModeValue_Meshes)
|
|
auto AddMeshGizmoComponent = [GizmoViewContext, NewActor](const TCHAR* MeshPath, const FLinearColor& Color,
|
|
const FTransform& RelativeTransform, EMirror Mirror, bool bAddHoverMaterial = true) -> UViewAdjustedStaticMeshGizmoComponent*
|
|
{
|
|
UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, MeshPath);
|
|
if (!ensure(Mesh))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UViewAdjustedStaticMeshGizmoComponent* Component = UE::GizmoRenderingUtil::CreateDefaultMaterialGizmoMeshComponent(
|
|
Mesh, GizmoViewContext, NewActor, Color, bAddHoverMaterial);
|
|
if (!ensure(Component))
|
|
{
|
|
return nullptr;
|
|
}
|
|
NewActor->AddInstanceComponent(Component);
|
|
Component->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
Component->SetRelativeTransform(RelativeTransform);
|
|
Component->RegisterComponent();
|
|
TSharedPtr<FSubGizmoTransformAdjuster> Adjuster = FSubGizmoTransformAdjuster::AddTransformAdjuster(
|
|
Component, NewActor->GetRootComponent(), Mirror == EMirror::Always);
|
|
if (Mirror == EMirror::WhenCombined)
|
|
{
|
|
NewActor->AdjustersThatMirrorOnlyInCombinedMode.Add(Adjuster);
|
|
}
|
|
|
|
return Component;
|
|
};
|
|
|
|
auto MakeAxisArrowFunc = [World, NewActor, GizmoViewContext, GizmoLineThickness, &AddMeshGizmoComponent](EAxis::Type ElementAxis) -> UPrimitiveComponent*
|
|
{
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
if (UViewAdjustedStaticMeshGizmoComponent* Component = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoArrowHandle"),
|
|
UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis),
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
// Transform for the X axis, relative to gizmo root
|
|
FTransform(FQuat::Identity, FVector::ZeroVector, FVector::OneVector), ElementAxis),
|
|
EMirror::WhenCombined))
|
|
{
|
|
Component->SetRenderVisibilityFunction(ShouldAxisBeVisible);
|
|
return Component;
|
|
}
|
|
}
|
|
|
|
UGizmoArrowComponent* Component = AddDefaultArrowComponent(World, NewActor, GizmoViewContext,
|
|
AxisToLegacyColor(ElementAxis), AxisToVector(ElementAxis), 60.0f);
|
|
Component->Gap = 20.0f;
|
|
Component->Thickness = GizmoLineThickness;
|
|
Component->NotifyExternalPropertyUpdates();
|
|
return Component;
|
|
};
|
|
if ((Elements & ETransformGizmoSubElements::TranslateAxisX) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateX = MakeAxisArrowFunc(EAxis::X);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::TranslateAxisY) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateY = MakeAxisArrowFunc(EAxis::Y);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::TranslateAxisZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateZ = MakeAxisArrowFunc(EAxis::Z);
|
|
}
|
|
|
|
|
|
auto MakePlaneRectFunc = [World, NewActor, GizmoViewContext, GizmoLineThickness, &AddMeshGizmoComponent](EAxis::Type ElementAxis) -> UPrimitiveComponent*
|
|
{
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
if (UViewAdjustedStaticMeshGizmoComponent* Component = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoPlaneHandle"),
|
|
UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis),
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
// Transform for the X axis, relative to gizmo root
|
|
FTransform(FQuat::Identity, FVector(0, 40, 40), FVector::One()), ElementAxis),
|
|
EMirror::WhenCombined))
|
|
{
|
|
Component->SetRenderVisibilityFunction(ShouldPlaneBeVisible);
|
|
return Component;
|
|
}
|
|
}
|
|
|
|
// If we got to here, then we're creating the PDI drawn rectangle component
|
|
FVector AxisX, AxisY;
|
|
AxisToLegacyPairOfVectors(ElementAxis, AxisX, AxisY);
|
|
UGizmoRectangleComponent* Component = AddDefaultRectangleComponent(World, NewActor, GizmoViewContext,
|
|
AxisToLegacyColor(ElementAxis), AxisX, AxisY);
|
|
Component->LengthX = Component->LengthY = 30.0f;
|
|
Component->SegmentFlags = 0x2 | 0x4;
|
|
Component->Thickness = GizmoLineThickness;
|
|
Component->NotifyExternalPropertyUpdates();
|
|
return Component;
|
|
};
|
|
if ((Elements & ETransformGizmoSubElements::TranslatePlaneYZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateYZ = MakePlaneRectFunc(EAxis::X);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::TranslatePlaneXZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateXZ = MakePlaneRectFunc(EAxis::Y);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::TranslatePlaneXY) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->TranslateXY = MakePlaneRectFunc(EAxis::Z);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::FreeTranslate) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->FreeTranslateHandle = nullptr;
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
NewActor->FreeTranslateHandle = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoSphereHandle"),
|
|
FreeTranslateColor,
|
|
FTransform::Identity,
|
|
EMirror::Never);
|
|
}
|
|
|
|
if (!NewActor->FreeTranslateHandle)
|
|
{
|
|
float BoxSize = 20.0f;
|
|
// We use a box as the backup because it already has hittesting for the inside, unlike our circles
|
|
NewActor->FreeTranslateHandle = AddDefaultBoxComponent(World, NewActor, GizmoViewContext, FLinearColor::Gray,
|
|
FVector(BoxSize / 2, BoxSize / 2, BoxSize / 2), FVector(BoxSize, BoxSize, BoxSize));
|
|
}
|
|
}
|
|
|
|
auto MakeAxisRotateCircleFunc = [World, NewActor, GizmoViewContext, GizmoLineThickness, &AddMeshGizmoComponent](EAxis::Type ElementAxis) -> UPrimitiveComponent*
|
|
{
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
FLinearColor Color = UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis);
|
|
Color.A = 0.75f; // Partially transparent, like editor gizmo
|
|
|
|
if (UViewAdjustedStaticMeshGizmoComponent* Component = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoQuarterCircleHandle"),
|
|
Color,
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
FTransform(FQuat::Identity, FVector::ZeroVector, FVector::OneVector), ElementAxis),
|
|
EMirror::Always))
|
|
{
|
|
Component->SetRenderVisibilityFunction(ShouldPlaneBeVisible);
|
|
|
|
UStaticMesh* SubstituteMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoFullCircleHandle"));
|
|
if (SubstituteMesh)
|
|
{
|
|
|
|
UViewAdjustedStaticMeshGizmoComponent* SubstituteComponent = UE::GizmoRenderingUtil::CreateDefaultMaterialGizmoMeshComponent(
|
|
SubstituteMesh, GizmoViewContext, Component, Color,
|
|
// No need for hover material
|
|
false);
|
|
if (SubstituteComponent)
|
|
{
|
|
Component->SetSubstituteInteractionComponent(SubstituteComponent);
|
|
|
|
UE::GizmoRenderingUtil::FSubGizmoTransformAdjuster::AddTransformAdjuster(
|
|
SubstituteComponent,
|
|
NewActor->GetRootComponent(),
|
|
/*bMirror*/ false);
|
|
}
|
|
}
|
|
|
|
return Component;
|
|
}
|
|
}
|
|
|
|
UGizmoCircleComponent* Component = AddDefaultCircleComponent(World, NewActor, GizmoViewContext,
|
|
AxisToLegacyColor(ElementAxis), AxisToVector(ElementAxis), 120.0f);
|
|
Component->Thickness = GizmoLineThickness;
|
|
Component->NotifyExternalPropertyUpdates();
|
|
return Component;
|
|
};
|
|
|
|
bool bAnyRotate = false;
|
|
if ((Elements & ETransformGizmoSubElements::RotateAxisX) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->RotateX = MakeAxisRotateCircleFunc(EAxis::X);
|
|
bAnyRotate = true;
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::RotateAxisY) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->RotateY = MakeAxisRotateCircleFunc(EAxis::Y);
|
|
bAnyRotate = true;
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::RotateAxisZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->RotateZ = MakeAxisRotateCircleFunc(EAxis::Z);
|
|
bAnyRotate = true;
|
|
}
|
|
|
|
|
|
// add a non-interactive view-aligned circle element, so the axes look like a sphere.
|
|
if (bAnyRotate && GizmoDrawMode != DrawModeValue_Meshes)
|
|
{
|
|
UGizmoCircleComponent* SphereEdge = NewObject<UGizmoCircleComponent>(NewActor);
|
|
NewActor->AddInstanceComponent(SphereEdge);
|
|
SphereEdge->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
|
|
SphereEdge->SetGizmoViewContext(GizmoViewContext);
|
|
SphereEdge->Color = FLinearColor::Gray;
|
|
SphereEdge->Thickness = 1.0f;
|
|
SphereEdge->Radius = 120.0f;
|
|
SphereEdge->bViewAligned = true;
|
|
SphereEdge->RegisterComponent();
|
|
NewActor->RotationSphere = SphereEdge;
|
|
}
|
|
|
|
if ((Elements & ETransformGizmoSubElements::FreeRotate) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->FreeRotateHandle = nullptr;
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
NewActor->FreeRotateHandle = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoSphereHandle"),
|
|
FreeRotateColor,
|
|
FTransform(FQuat::Identity, FVector::ZeroVector, FVector(9)),
|
|
EMirror::Never, /*bAddHoverMaterial*/ false);
|
|
}
|
|
|
|
if (!NewActor->FreeRotateHandle)
|
|
{
|
|
float BoxSize = 20.0f;
|
|
// We use a box as the backup because it already has hittesting for the inside, unlike our circles
|
|
NewActor->FreeRotateHandle = AddDefaultBoxComponent(World, NewActor, GizmoViewContext, FLinearColor::Gray,
|
|
FVector(BoxSize/2, BoxSize/2, BoxSize/2), FVector(BoxSize, BoxSize, BoxSize));
|
|
}
|
|
}
|
|
|
|
|
|
if ((Elements & ETransformGizmoSubElements::ScaleUniform) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->UniformScale = nullptr;
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
NewActor->UniformScale = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoBoxHandle"),
|
|
UniformScaleColor,
|
|
FTransform::Identity,
|
|
EMirror::Never);
|
|
}
|
|
|
|
if (!NewActor->UniformScale)
|
|
{
|
|
float BoxSize = 20.0f;
|
|
UGizmoBoxComponent* ScaleComponent = AddDefaultBoxComponent(World, NewActor, GizmoViewContext, FLinearColor::Black,
|
|
FVector(BoxSize/2, BoxSize/2, BoxSize/2), FVector(BoxSize, BoxSize, BoxSize));
|
|
NewActor->UniformScale = ScaleComponent;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
auto MakeAxisScaleFunc = [World, NewActor, GizmoViewContext, GizmoLineThickness, &AddMeshGizmoComponent](EAxis::Type ElementAxis, const FVector& PerpendicularAxis, bool bLockSinglePlane,
|
|
TObjectPtr<UPrimitiveComponent>& FullHandleOut) -> UPrimitiveComponent*
|
|
{
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
if (UViewAdjustedStaticMeshGizmoComponent* Component = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoBoxHandle"),
|
|
UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis),
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
FTransform(FQuat::Identity, FVector(130, 0, 0), FVector(0.8)),
|
|
ElementAxis),
|
|
EMirror::WhenCombined))
|
|
{
|
|
Component->SetRenderVisibilityFunction(ShouldAxisBeVisible);
|
|
|
|
// Also try to add a full handle to use when we're not using a combined gizmo.
|
|
FullHandleOut = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoBoxArrowHandle"),
|
|
UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis),
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
FTransform(FQuat::Identity, FVector::ZeroVector, FVector::One()),
|
|
ElementAxis),
|
|
EMirror::WhenCombined);
|
|
if (FullHandleOut)
|
|
{
|
|
FullHandleOut->SetVisibility(false);
|
|
}
|
|
|
|
return Component;
|
|
}
|
|
}
|
|
|
|
UGizmoRectangleComponent* ScaleComponent = AddDefaultRectangleComponent(World, NewActor, GizmoViewContext,
|
|
AxisToLegacyColor(ElementAxis), AxisToVector(ElementAxis), PerpendicularAxis);
|
|
ScaleComponent->OffsetX = 140.0f; ScaleComponent->OffsetY = -10.0f;
|
|
ScaleComponent->LengthX = 7.0f; ScaleComponent->LengthY = 20.0f;
|
|
ScaleComponent->Thickness = GizmoLineThickness;
|
|
ScaleComponent->bOrientYAccordingToCamera = !bLockSinglePlane;
|
|
ScaleComponent->NotifyExternalPropertyUpdates();
|
|
ScaleComponent->SegmentFlags = 0x1 | 0x2 | 0x4; // | 0x8;
|
|
return ScaleComponent;
|
|
};
|
|
|
|
// This is designed so we can properly handle the visual orientations of the scale handles under the condition of a
|
|
// planar gizmo (such as in the UV Editor).
|
|
// In this case we want to lock the handle on to the other axis of the plane, rather than use the component's camera orientation option. This requires
|
|
// both tracking how many axes are being requested and also *which* axes are requested, in order to configure the correct planar basis vectors.
|
|
// In the case of a single axis, we have to pick a cross axis arbitrarily, but we also keep the auto orientation mode on the component active, so the initial
|
|
// choice isn't as critical. If we want to some day have a single axis handle that is locked, we may need to revisit this again.
|
|
auto ConfigureAdditionalAxis = [&Elements](ETransformGizmoSubElements AxisToTest, int32& TotalAxisCount, FVector& NewPerpendicularAxis) {
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisX & AxisToTest) != ETransformGizmoSubElements::None)
|
|
{
|
|
TotalAxisCount++;
|
|
NewPerpendicularAxis = FVector(1, 0, 0);
|
|
return;
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisY & AxisToTest) != ETransformGizmoSubElements::None)
|
|
{
|
|
TotalAxisCount++;
|
|
NewPerpendicularAxis = FVector(0, 1, 0);
|
|
return;
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisZ & AxisToTest) != ETransformGizmoSubElements::None)
|
|
{
|
|
TotalAxisCount++;
|
|
NewPerpendicularAxis = FVector(0, 0, 1);
|
|
return;
|
|
}
|
|
};
|
|
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisX) != ETransformGizmoSubElements::None)
|
|
{
|
|
int32 TotalAxisCount = 1;
|
|
FVector PerpendicularAxis(0,1,0);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisY, TotalAxisCount, PerpendicularAxis);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisZ, TotalAxisCount, PerpendicularAxis);
|
|
NewActor->AxisScaleX = MakeAxisScaleFunc(EAxis::X, PerpendicularAxis, TotalAxisCount == 2,
|
|
NewActor->FullAxisScaleX);
|
|
}
|
|
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisY) != ETransformGizmoSubElements::None)
|
|
{
|
|
int32 TotalAxisCount = 1;
|
|
FVector PerpendicularAxis(1, 0, 0);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisX, TotalAxisCount, PerpendicularAxis);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisZ, TotalAxisCount, PerpendicularAxis);
|
|
NewActor->AxisScaleY = MakeAxisScaleFunc(EAxis::Y, PerpendicularAxis, TotalAxisCount == 2,
|
|
NewActor->FullAxisScaleY);
|
|
}
|
|
|
|
if ((Elements & ETransformGizmoSubElements::ScaleAxisZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
int32 TotalAxisCount = 1;
|
|
FVector PerpendicularAxis(1, 0, 0);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisY, TotalAxisCount, PerpendicularAxis);
|
|
ConfigureAdditionalAxis(ETransformGizmoSubElements::ScaleAxisX, TotalAxisCount, PerpendicularAxis);
|
|
NewActor->AxisScaleZ = MakeAxisScaleFunc(EAxis::Z, PerpendicularAxis, TotalAxisCount == 2,
|
|
NewActor->FullAxisScaleZ);
|
|
}
|
|
|
|
|
|
auto MakePlaneScaleFunc = [World, NewActor, GizmoViewContext, GizmoLineThickness, &AddMeshGizmoComponent](EAxis::Type ElementAxis) -> UPrimitiveComponent*
|
|
{
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
if (UViewAdjustedStaticMeshGizmoComponent* Component = AddMeshGizmoComponent(
|
|
TEXT("/Engine/InteractiveToolsFramework/Meshes/GizmoCornerHandle"),
|
|
UE::GizmoRenderingUtil::GetDefaultAxisColor(ElementAxis),
|
|
UE::GizmoUtil::GetRotatedBasisTransform(
|
|
// Transform for the X axis, relative to gizmo root
|
|
FTransform(FQuat::Identity, CornerScalePositionCombined, FVector(CornerScaleHandleScale)), ElementAxis),
|
|
// We actually adjust the transform of the plane scale handles and swap the adjuster inside
|
|
// ApplyGizmoActiveMode, so we don't need this one to be updated.
|
|
EMirror::Always))
|
|
{
|
|
Component->SetRenderVisibilityFunction(ShouldPlaneBeVisible);
|
|
return Component;
|
|
}
|
|
}
|
|
|
|
// If we got to here, then we're creating the PDI drawn rectangle component
|
|
FVector Axis0, Axis1;
|
|
AxisToLegacyPairOfVectors(ElementAxis, Axis0, Axis1);
|
|
UGizmoRectangleComponent* ScaleComponent = AddDefaultRectangleComponent(World, NewActor, GizmoViewContext,
|
|
AxisToLegacyColor(ElementAxis), Axis0, Axis1);
|
|
ScaleComponent->OffsetX = ScaleComponent->OffsetY = 120.0f;
|
|
ScaleComponent->LengthX = ScaleComponent->LengthY = 20.0f;
|
|
ScaleComponent->Thickness = GizmoLineThickness;
|
|
ScaleComponent->NotifyExternalPropertyUpdates();
|
|
ScaleComponent->SegmentFlags = 0x2 | 0x4;
|
|
return ScaleComponent;
|
|
};
|
|
if ((Elements & ETransformGizmoSubElements::ScalePlaneYZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->PlaneScaleYZ = MakePlaneScaleFunc(EAxis::X);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::ScalePlaneXZ) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->PlaneScaleXZ = MakePlaneScaleFunc(EAxis::Y);
|
|
}
|
|
if ((Elements & ETransformGizmoSubElements::ScalePlaneXY) != ETransformGizmoSubElements::None)
|
|
{
|
|
NewActor->PlaneScaleXY = MakePlaneScaleFunc(EAxis::Z);
|
|
}
|
|
|
|
|
|
return NewActor;
|
|
}
|
|
|
|
|
|
|
|
|
|
ACombinedTransformGizmoActor* FCombinedTransformGizmoActorFactory::CreateNewGizmoActor(UWorld* World) const
|
|
{
|
|
return ACombinedTransformGizmoActor::ConstructCustom3AxisGizmo(World, GizmoViewContext, EnableElements);
|
|
}
|
|
|
|
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmoBuilder::BuildGizmo(const FToolBuilderState& SceneState) const
|
|
{
|
|
UCombinedTransformGizmo* NewGizmo = NewObject<UCombinedTransformGizmo>(SceneState.GizmoManager);
|
|
NewGizmo->SetWorld(SceneState.World);
|
|
|
|
UGizmoViewContext* GizmoViewContext = SceneState.ToolManager->GetContextObjectStore()->FindContext<UGizmoViewContext>();
|
|
check(GizmoViewContext && GizmoViewContext->IsValidLowLevel());
|
|
|
|
// use default gizmo actor if client has not given us a new builder
|
|
NewGizmo->SetGizmoActorBuilder(GizmoActorBuilder ? GizmoActorBuilder : MakeShared<FCombinedTransformGizmoActorFactory>(GizmoViewContext));
|
|
|
|
NewGizmo->SetSubGizmoBuilderIdentifiers(AxisPositionBuilderIdentifier, PlanePositionBuilderIdentifier, AxisAngleBuilderIdentifier);
|
|
|
|
// override default hover function if proposed
|
|
if (UpdateHoverFunction)
|
|
{
|
|
NewGizmo->SetUpdateHoverFunction(UpdateHoverFunction);
|
|
}
|
|
|
|
if (UpdateCoordSystemFunction)
|
|
{
|
|
NewGizmo->SetUpdateCoordSystemFunction(UpdateCoordSystemFunction);
|
|
}
|
|
|
|
return NewGizmo;
|
|
}
|
|
|
|
|
|
|
|
void UCombinedTransformGizmo::SetWorld(UWorld* WorldIn)
|
|
{
|
|
this->World = WorldIn;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetGizmoActorBuilder(TSharedPtr<FCombinedTransformGizmoActorFactory> Builder)
|
|
{
|
|
GizmoActorBuilder = Builder;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetSubGizmoBuilderIdentifiers(FString AxisPositionBuilderIdentifierIn, FString PlanePositionBuilderIdentifierIn, FString AxisAngleBuilderIdentifierIn)
|
|
{
|
|
AxisPositionBuilderIdentifier = AxisPositionBuilderIdentifierIn;
|
|
PlanePositionBuilderIdentifier = PlanePositionBuilderIdentifierIn;
|
|
AxisAngleBuilderIdentifier = AxisAngleBuilderIdentifierIn;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetUpdateHoverFunction(TFunction<void(UPrimitiveComponent*, bool)> HoverFunction)
|
|
{
|
|
UpdateHoverFunction = HoverFunction;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetUpdateCoordSystemFunction(TFunction<void(UPrimitiveComponent*, EToolContextCoordinateSystem)> CoordSysFunction)
|
|
{
|
|
UpdateCoordSystemFunction = CoordSysFunction;
|
|
}
|
|
|
|
bool UCombinedTransformGizmo::SetSubGizmoComponent(ETransformGizmoSubElements Element,
|
|
UPrimitiveComponent* NewComponent, const FTransform& SubGizmoToGizmo)
|
|
{
|
|
using namespace UE::Geometry;
|
|
using namespace CombinedTransformGizmoLocals;
|
|
|
|
if (!GizmoActor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EAxis::Type Axis = UE::GizmoUtil::ToAxis(Element);
|
|
|
|
UPrimitiveComponent* ReplacedComponent = nullptr;
|
|
if (!GizmoActor->ReplaceSubGizmoComponent(Element, NewComponent, SubGizmoToGizmo, &ReplacedComponent))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!ActiveTarget)
|
|
{
|
|
// If the target is not set yet, then we're done for now. The rest of the setup
|
|
// should end up being done correctly once SetActiveTarget is called.
|
|
return true;
|
|
}
|
|
|
|
// If we got here, we'll need to do some more work to initialize or reinitialize our gizmo
|
|
|
|
// Look for the existing gizmo through our gizmo info arrays
|
|
if (ReplacedComponent)
|
|
{
|
|
ActiveComponents.RemoveSwap(ReplacedComponent);
|
|
|
|
TArray<FSubGizmoInfo>* ArrayToSearch = nullptr;
|
|
if ((Element & (ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes))
|
|
!= ETransformGizmoSubElements::None)
|
|
{
|
|
ArrayToSearch = &TranslationSubGizmos;
|
|
}
|
|
else if ((Element & ETransformGizmoSubElements::RotateAllAxes) != ETransformGizmoSubElements::None)
|
|
{
|
|
ArrayToSearch = &RotationSubGizmos;
|
|
}
|
|
else if ((Element & (ETransformGizmoSubElements::ScaleAllAxes | ETransformGizmoSubElements::ScaleAllPlanes))
|
|
!= ETransformGizmoSubElements::None)
|
|
{
|
|
ArrayToSearch = &NonUniformScaleSubGizmos;
|
|
}
|
|
else if ((Element & ETransformGizmoSubElements::ScaleUniform) != ETransformGizmoSubElements::None)
|
|
{
|
|
ArrayToSearch = &UniformScaleSubGizmos;
|
|
}
|
|
|
|
int32 GizmoInfoIndex = -1;
|
|
FSubGizmoInfo* ExistingGizmoInfo = nullptr;
|
|
if (ensure(ArrayToSearch))
|
|
{
|
|
GizmoInfoIndex = ArrayToSearch->IndexOfByPredicate(
|
|
[ReplacedComponent](const FSubGizmoInfo& GizmoInfo) { return GizmoInfo.Component == ReplacedComponent; });
|
|
if (GizmoInfoIndex >= 0)
|
|
{
|
|
ExistingGizmoInfo = &(*ArrayToSearch)[GizmoInfoIndex];
|
|
}
|
|
}
|
|
|
|
if (ensure(ExistingGizmoInfo))
|
|
{
|
|
// We could call InitializeAs... on an existing gizmo to swap the component, but then we also need to set
|
|
// our constraint functions, etc. It seems cleaner code-wise to just destroy this gizmo and create a new one
|
|
// to make sure everything is updated. We just have to make sure we do the removal thoroughly.
|
|
if (UInteractiveGizmo* ExistingGizmo = ExistingGizmoInfo->Gizmo.Get())
|
|
{
|
|
GetGizmoManager()->DestroyGizmo(ExistingGizmo);
|
|
ActiveGizmos.Remove(ExistingGizmo);
|
|
}
|
|
ArrayToSearch->RemoveAtSwap(GizmoInfoIndex);
|
|
}
|
|
}
|
|
|
|
if (!NewComponent)
|
|
{
|
|
// If we're replacing with a nullptr, then we just wanted to remove that component.
|
|
// No need to add a gizmo back.
|
|
return true;
|
|
}
|
|
|
|
UE::GizmoUtil::FTransformSubGizmoCommonParams Params;
|
|
Params.TransformProxy = ActiveTarget;
|
|
Params.Axis = Axis;
|
|
Params.Component = NewComponent;
|
|
Params.TransactionProvider = TransactionProviderAtLastSetActiveTarget;
|
|
Params.bManipulatesRootComponent = true;
|
|
|
|
// The shared data struct should have been created during SetActiveTarget
|
|
if (!ensure(SubGizmoSharedState.IsValid()))
|
|
{
|
|
SubGizmoSharedState = MakeUnique<FTransformSubGizmoSharedState>();
|
|
}
|
|
|
|
switch (Element)
|
|
{
|
|
case ETransformGizmoSubElements::TranslateAxisX:
|
|
case ETransformGizmoSubElements::TranslateAxisY:
|
|
case ETransformGizmoSubElements::TranslateAxisZ:
|
|
{
|
|
AddAxisTranslationGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::TranslatePlaneXY:
|
|
case ETransformGizmoSubElements::TranslatePlaneXZ:
|
|
case ETransformGizmoSubElements::TranslatePlaneYZ:
|
|
{
|
|
AddPlaneTranslationGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::RotateAxisX:
|
|
case ETransformGizmoSubElements::RotateAxisY:
|
|
case ETransformGizmoSubElements::RotateAxisZ:
|
|
{
|
|
AddAxisRotationGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::ScaleAxisX:
|
|
case ETransformGizmoSubElements::ScaleAxisY:
|
|
case ETransformGizmoSubElements::ScaleAxisZ:
|
|
{
|
|
AddAxisScaleGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::ScalePlaneXY:
|
|
case ETransformGizmoSubElements::ScalePlaneXZ:
|
|
case ETransformGizmoSubElements::ScalePlaneYZ:
|
|
{
|
|
AddPlaneScaleGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::ScaleUniform:
|
|
{
|
|
AddUniformScaleGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::FreeTranslate:
|
|
{
|
|
AddFreeTranslationGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::FreeRotate:
|
|
{
|
|
AddFreeRotationGizmo(Params, *SubGizmoSharedState);
|
|
break;
|
|
}
|
|
case ETransformGizmoSubElements::RotateAllAxes:
|
|
{
|
|
// no gizmo for the drawn sphere
|
|
if (ensure(GizmoActor->RotationSphere == NewComponent))
|
|
{
|
|
ActiveComponents.Add(GizmoActor->RotationSphere);
|
|
RotationSubGizmos.Add(FSubGizmoInfo{ GizmoActor->RotationSphere, nullptr });
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return ensure(false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetWorldAlignmentFunctions(
|
|
TUniqueFunction<bool()>&& ShouldAlignTranslationIn,
|
|
TUniqueFunction<bool(const FRay&, FVector&)>&& TranslationAlignmentRayCasterIn)
|
|
{
|
|
// Save these so that later changes of gizmo target keep the settings.
|
|
ShouldAlignDestination = MoveTemp(ShouldAlignTranslationIn);
|
|
DestinationAlignmentRayCaster = MoveTemp(TranslationAlignmentRayCasterIn);
|
|
|
|
// We allow this function to be called after Setup(), so modify any existing translation/rotation sub gizmos.
|
|
// Unfortunately we keep all the sub gizmos in one list, and the scaling gizmos are differentiated from the
|
|
// translation ones mainly in the components they use. So this ends up being a slightly messy set of checks,
|
|
// but it didn't seem worth keeping a segregated list for something that will only happen once.
|
|
for (UInteractiveGizmo* SubGizmo : this->ActiveGizmos)
|
|
{
|
|
if (UAxisPositionGizmo* CastGizmo = Cast<UAxisPositionGizmo>(SubGizmo))
|
|
{
|
|
if (UGizmoComponentHitTarget* CastHitTarget = Cast<UGizmoComponentHitTarget>(CastGizmo->HitTarget.GetObject()))
|
|
{
|
|
if (CastHitTarget->Component == GizmoActor->TranslateX
|
|
|| CastHitTarget->Component == GizmoActor->TranslateY
|
|
|| CastHitTarget->Component == GizmoActor->TranslateZ)
|
|
{
|
|
CastGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
CastGizmo->CustomDestinationFunc =
|
|
[this](const UAxisPositionGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
if (UPlanePositionGizmo* CastGizmo = Cast<UPlanePositionGizmo>(SubGizmo))
|
|
{
|
|
if (UGizmoComponentHitTarget* CastHitTarget = Cast<UGizmoComponentHitTarget>(CastGizmo->HitTarget.GetObject()))
|
|
{
|
|
if (CastHitTarget->Component == GizmoActor->TranslateXY
|
|
|| CastHitTarget->Component == GizmoActor->TranslateXZ
|
|
|| CastHitTarget->Component == GizmoActor->TranslateYZ
|
|
|| CastHitTarget->Component == GizmoActor->FreeTranslateHandle)
|
|
{
|
|
CastGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
CastGizmo->CustomDestinationFunc =
|
|
[this](const UPlanePositionGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
if (UAxisAngleGizmo* CastGizmo = Cast<UAxisAngleGizmo>(SubGizmo))
|
|
{
|
|
CastGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
CastGizmo->CustomDestinationFunc =
|
|
[this](const UAxisAngleGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetCustomTranslationDeltaFunctions(
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> XAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> YAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> ZAxis)
|
|
{
|
|
CustomTranslationDeltaConstraintFunctions[0] = XAxis;
|
|
CustomTranslationDeltaConstraintFunctions[1] = YAxis;
|
|
CustomTranslationDeltaConstraintFunctions[2] = ZAxis;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetCustomRotationDeltaFunctions(
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> XAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> YAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> ZAxis)
|
|
{
|
|
CustomRotationDeltaConstraintFunctions[0] = XAxis;
|
|
CustomRotationDeltaConstraintFunctions[1] = YAxis;
|
|
CustomRotationDeltaConstraintFunctions[2] = ZAxis;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetCustomScaleDeltaFunctions(
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> XAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> YAxis,
|
|
TFunction<bool(double AxisDelta, double& SnappedDelta)> ZAxis)
|
|
{
|
|
CustomScaleDeltaConstraintFunctions[0] = XAxis;
|
|
CustomScaleDeltaConstraintFunctions[1] = YAxis;
|
|
CustomScaleDeltaConstraintFunctions[2] = ZAxis;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetDisallowNegativeScaling(bool bDisallow)
|
|
{
|
|
if (bDisallowNegativeScaling != bDisallow)
|
|
{
|
|
bDisallowNegativeScaling = bDisallow;
|
|
for (UInteractiveGizmo* SubGizmo : this->ActiveGizmos)
|
|
{
|
|
if (UAxisPositionGizmo* CastGizmo = Cast<UAxisPositionGizmo>(SubGizmo))
|
|
{
|
|
if (UGizmoAxisScaleParameterSource* ParamSource = Cast<UGizmoAxisScaleParameterSource>(CastGizmo->ParameterSource.GetObject()))
|
|
{
|
|
ParamSource->bClampToZero = bDisallow;
|
|
}
|
|
}
|
|
if (UPlanePositionGizmo* CastGizmo = Cast<UPlanePositionGizmo>(SubGizmo))
|
|
{
|
|
if (UGizmoPlaneScaleParameterSource* ParamSource = Cast<UGizmoPlaneScaleParameterSource>(CastGizmo->ParameterSource.GetObject()))
|
|
{
|
|
ParamSource->bClampToZero = bDisallow;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetIsNonUniformScaleAllowedFunction(TUniqueFunction<bool()>&& IsNonUniformScaleAllowedIn)
|
|
{
|
|
IsNonUniformScaleAllowedFunc = MoveTemp(IsNonUniformScaleAllowedIn);
|
|
}
|
|
|
|
|
|
void UCombinedTransformGizmo::Setup()
|
|
{
|
|
UInteractiveGizmo::Setup();
|
|
|
|
if (!UpdateHoverFunction)
|
|
{
|
|
UpdateHoverFunction = [](UPrimitiveComponent* Component, bool bHovering)
|
|
{
|
|
if (IGizmoBaseComponentInterface* CastComponent = Cast<IGizmoBaseComponentInterface>(Component))
|
|
{
|
|
CastComponent->UpdateHoverState(bHovering);
|
|
}
|
|
};
|
|
}
|
|
|
|
if (!UpdateCoordSystemFunction)
|
|
{
|
|
UpdateCoordSystemFunction = [](UPrimitiveComponent* Component, EToolContextCoordinateSystem CoordSystem)
|
|
{
|
|
if (IGizmoBaseComponentInterface* CastComponent = Cast<IGizmoBaseComponentInterface>(Component))
|
|
{
|
|
CastComponent->UpdateWorldLocalState(CoordSystem == EToolContextCoordinateSystem::World);
|
|
}
|
|
};
|
|
}
|
|
|
|
GizmoActor = GizmoActorBuilder->CreateNewGizmoActor(World);
|
|
|
|
PreviousActiveGizmoMode = ActiveGizmoMode;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::Shutdown()
|
|
{
|
|
ClearActiveTarget();
|
|
|
|
if (GizmoActor)
|
|
{
|
|
GizmoActor->Destroy();
|
|
GizmoActor = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UCombinedTransformGizmo::UpdateCameraAxisSource()
|
|
{
|
|
if (CameraAxisSource != nullptr && GizmoActor != nullptr)
|
|
{
|
|
UE::GizmoUtil::UpdateCameraAxisSource(*CameraAxisSource, GetGizmoManager(),
|
|
GizmoActor->GetTransform().GetLocation());
|
|
}
|
|
}
|
|
|
|
|
|
void UCombinedTransformGizmo::Tick(float DeltaTime)
|
|
{
|
|
if (bUseContextCoordinateSystem)
|
|
{
|
|
CurrentCoordinateSystem = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem();
|
|
}
|
|
check(CurrentCoordinateSystem == EToolContextCoordinateSystem::World || CurrentCoordinateSystem == EToolContextCoordinateSystem::Local)
|
|
|
|
FToolContextSnappingConfiguration SnappingConfig = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentSnappingSettings();
|
|
RelativeTranslationSnapping.UpdateContextValue(SnappingConfig.bEnableAbsoluteWorldSnapping == false);
|
|
|
|
bool bUseLocalAxes = (CurrentCoordinateSystem == EToolContextCoordinateSystem::Local);
|
|
if (AxisXSource != nullptr && AxisYSource != nullptr && AxisZSource != nullptr)
|
|
{
|
|
AxisXSource->bLocalAxes = bUseLocalAxes;
|
|
AxisYSource->bLocalAxes = bUseLocalAxes;
|
|
AxisZSource->bLocalAxes = bUseLocalAxes;
|
|
}
|
|
if (UpdateCoordSystemFunction)
|
|
{
|
|
for (UPrimitiveComponent* Component : ActiveComponents)
|
|
{
|
|
UpdateCoordSystemFunction(Component, CurrentCoordinateSystem);
|
|
}
|
|
}
|
|
|
|
if (bUseContextGizmoMode)
|
|
{
|
|
ActiveGizmoMode = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentTransformGizmoMode();
|
|
}
|
|
|
|
// apply dynamic visibility filtering to sub-gizmos
|
|
if (PreviousActiveGizmoMode != ActiveGizmoMode)
|
|
{
|
|
ApplyGizmoActiveMode();
|
|
}
|
|
|
|
UpdateCameraAxisSource();
|
|
}
|
|
|
|
void UCombinedTransformGizmo::ApplyGizmoActiveMode()
|
|
{
|
|
using namespace CombinedTransformGizmoLocals;
|
|
|
|
ON_SCOPE_EXIT{ PreviousActiveGizmoMode = ActiveGizmoMode; };
|
|
|
|
auto SetSubGizmoTypeVisibility = [this](TArray<FSubGizmoInfo>& GizmoInfos, bool bVisible)
|
|
{
|
|
for (FSubGizmoInfo& GizmoInfo : GizmoInfos)
|
|
{
|
|
if (GizmoInfo.Component.IsValid())
|
|
{
|
|
GizmoInfo.Component->SetVisibility(bVisible);
|
|
}
|
|
}
|
|
};
|
|
bool bShouldShowTranslation =
|
|
(ActiveGizmoMode == EToolContextTransformGizmoMode::Combined || ActiveGizmoMode == EToolContextTransformGizmoMode::Translation);
|
|
bool bShouldShowRotation =
|
|
(ActiveGizmoMode == EToolContextTransformGizmoMode::Combined || ActiveGizmoMode == EToolContextTransformGizmoMode::Rotation);
|
|
bool bShouldShowUniformScale =
|
|
(ActiveGizmoMode == EToolContextTransformGizmoMode::Combined || ActiveGizmoMode == EToolContextTransformGizmoMode::Scale);
|
|
bool bShouldShowNonUniformScale =
|
|
(ActiveGizmoMode == EToolContextTransformGizmoMode::Combined || ActiveGizmoMode == EToolContextTransformGizmoMode::Scale)
|
|
&& IsNonUniformScaleAllowedFunc();
|
|
|
|
SetSubGizmoTypeVisibility(TranslationSubGizmos, bShouldShowTranslation);
|
|
SetSubGizmoTypeVisibility(RotationSubGizmos, bShouldShowRotation);
|
|
SetSubGizmoTypeVisibility(UniformScaleSubGizmos, bShouldShowUniformScale);
|
|
|
|
// The rest of the modifications dereference GizmoActor, so go ahead and do a safety check now.
|
|
if (!ensure(IsValid(GizmoActor)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ActiveGizmoMode == EToolContextTransformGizmoMode::Combined || ActiveGizmoMode == EToolContextTransformGizmoMode::Scale)
|
|
{
|
|
// The scale handles look different in different modes, so swap them if necessary
|
|
auto SwapAxisScaleComponent = [this](UPrimitiveComponent* HandleInCombined, UPrimitiveComponent* HandleInSeparate)
|
|
{
|
|
UPrimitiveComponent* HandleToUse = ActiveGizmoMode == EToolContextTransformGizmoMode::Combined ? HandleInCombined : HandleInSeparate;
|
|
UPrimitiveComponent* HandleToReplace = ActiveGizmoMode == EToolContextTransformGizmoMode::Combined ? HandleInSeparate : HandleInCombined;
|
|
|
|
// Don't swap if we don't have an alternative to use
|
|
if (!HandleToUse || !HandleToReplace) return;
|
|
|
|
FSubGizmoInfo* GizmoInfo = NonUniformScaleSubGizmos.FindByPredicate([HandleToUse, HandleToReplace](const FSubGizmoInfo& Info)
|
|
{
|
|
return Info.Component == HandleToUse || Info.Component == HandleToReplace;
|
|
});
|
|
// Don't swap if we don't have this gizmo or if it's already using the correct one
|
|
if (!GizmoInfo || GizmoInfo->Component == HandleToUse) return;
|
|
|
|
UAxisPositionGizmo* SubGizmo = Cast<UAxisPositionGizmo>(GizmoInfo->Gizmo);
|
|
if (!ensure(SubGizmo)) return;
|
|
UGizmoComponentHitTarget* HitTarget = Cast<UGizmoComponentHitTarget>(SubGizmo->HitTarget.GetObject());
|
|
if (!ensure(HitTarget)) return;
|
|
|
|
HandleToReplace->SetVisibility(false);
|
|
HitTarget->Component = HandleToUse;
|
|
GizmoInfo->Component = HandleToUse;
|
|
};
|
|
|
|
SwapAxisScaleComponent(GizmoActor->AxisScaleX, GizmoActor->FullAxisScaleX);
|
|
SwapAxisScaleComponent(GizmoActor->AxisScaleY, GizmoActor->FullAxisScaleY);
|
|
SwapAxisScaleComponent(GizmoActor->AxisScaleZ, GizmoActor->FullAxisScaleZ);
|
|
|
|
// The plane scale handles look better if they are closer to the gizmo when not combined.
|
|
auto AdjustPlaneScaleComponent = [this](UViewAdjustedStaticMeshGizmoComponent* Component, EAxis::Type ElementAxis)
|
|
{
|
|
if (!Component) return;
|
|
Component->SetRelativeTransform(UE::GizmoUtil::GetRotatedBasisTransform(
|
|
// Transform for the X axis, relative to gizmo root
|
|
FTransform(FQuat::Identity, ActiveGizmoMode == EToolContextTransformGizmoMode::Combined ? CornerScalePositionCombined : CornerScalePositionSeparate,
|
|
FVector(CornerScaleHandleScale)), ElementAxis));
|
|
|
|
// Just replace the adjuster
|
|
// TODO: Maybe keep track of whether this needs doing instead of doing it each time we switch to scale mode
|
|
TSharedPtr<UE::GizmoRenderingUtil::FSubGizmoTransformAdjuster> Adjuster =
|
|
UE::GizmoRenderingUtil::FSubGizmoTransformAdjuster::AddTransformAdjuster(
|
|
Component, GizmoActor->GetRootComponent(), ActiveGizmoMode == EToolContextTransformGizmoMode::Combined);
|
|
};
|
|
AdjustPlaneScaleComponent(Cast<UViewAdjustedStaticMeshGizmoComponent>(GizmoActor->PlaneScaleXY), EAxis::Z);
|
|
AdjustPlaneScaleComponent(Cast<UViewAdjustedStaticMeshGizmoComponent>(GizmoActor->PlaneScaleXZ), EAxis::Y);
|
|
AdjustPlaneScaleComponent(Cast<UViewAdjustedStaticMeshGizmoComponent>(GizmoActor->PlaneScaleYZ), EAxis::X);
|
|
}
|
|
// This is done after the above, since the above affects NonUniformScaleSubGizmos
|
|
SetSubGizmoTypeVisibility(NonUniformScaleSubGizmos, bShouldShowUniformScale);
|
|
|
|
auto SetVisibilityIfExists = [](UPrimitiveComponent* Component, bool bVisible)
|
|
{
|
|
if (Component)
|
|
{
|
|
Component->SetVisibility(bVisible);
|
|
}
|
|
};
|
|
if (GizmoActor->FreeRotateHandle)
|
|
{
|
|
GizmoActor->FreeRotateHandle->SetVisibility(ActiveGizmoMode == EToolContextTransformGizmoMode::Rotation);
|
|
}
|
|
if (GizmoActor->FreeTranslateHandle)
|
|
{
|
|
GizmoActor->FreeTranslateHandle->SetVisibility(ActiveGizmoMode == EToolContextTransformGizmoMode::Translation
|
|
|| (ActiveGizmoMode == EToolContextTransformGizmoMode::Combined && UniformScaleSubGizmos.Num() == 0));
|
|
}
|
|
|
|
if (GizmoDrawMode == DrawModeValue_Meshes)
|
|
{
|
|
// Many components mirror in combined mode but not separate mode, mostly because it is weird for them
|
|
// to not mirror when the rotation components do.
|
|
for (int32 i = GizmoActor->AdjustersThatMirrorOnlyInCombinedMode.Num() - 1; i >= 0; --i)
|
|
{
|
|
TSharedPtr<UE::GizmoRenderingUtil::FSubGizmoTransformAdjuster> Adjuster =
|
|
GizmoActor->AdjustersThatMirrorOnlyInCombinedMode[i].Pin();
|
|
if (ensure(Adjuster))
|
|
{
|
|
Adjuster->SetMirrorBasedOnOctant(ActiveGizmoMode == EToolContextTransformGizmoMode::Combined);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UCombinedTransformGizmo::SetActiveTarget(UTransformProxy* Target, IToolContextTransactionProvider* TransactionProvider)
|
|
{
|
|
if (ActiveTarget != nullptr)
|
|
{
|
|
ClearActiveTarget();
|
|
}
|
|
|
|
ActiveTarget = Target;
|
|
TransactionProviderAtLastSetActiveTarget = TransactionProvider;
|
|
|
|
// move gizmo to target location
|
|
USceneComponent* GizmoComponent = GizmoActor->GetRootComponent();
|
|
|
|
FTransform TargetTransform = Target->GetTransform();
|
|
FTransform GizmoTransform = TargetTransform;
|
|
GizmoTransform.SetScale3D(FVector(1, 1, 1));
|
|
GizmoComponent->SetWorldTransform(GizmoTransform);
|
|
|
|
UE::GizmoUtil::FTransformSubGizmoCommonParams Params;
|
|
Params.TransformProxy = ActiveTarget;
|
|
Params.TransactionProvider = TransactionProvider;
|
|
Params.bManipulatesRootComponent = true;
|
|
|
|
SubGizmoSharedState = MakeUnique<UE::GizmoUtil::FTransformSubGizmoSharedState>();
|
|
|
|
EAxis::Type Axes[3] = { EAxis::X, EAxis::Y, EAxis::Z };
|
|
UPrimitiveComponent* TranslateAxisComponents[3]{ GizmoActor->TranslateX, GizmoActor->TranslateY, GizmoActor->TranslateZ };
|
|
for (int AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
|
|
{
|
|
if (TranslateAxisComponents[AxisIndex])
|
|
{
|
|
Params.Component = TranslateAxisComponents[AxisIndex];
|
|
Params.Axis = Axes[AxisIndex];
|
|
AddAxisTranslationGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
}
|
|
UPrimitiveComponent* TranslatePlaneComponents[3]{ GizmoActor->TranslateYZ, GizmoActor->TranslateXZ, GizmoActor->TranslateXY };
|
|
for (int AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
|
|
{
|
|
if (TranslatePlaneComponents[AxisIndex])
|
|
{
|
|
Params.Component = TranslatePlaneComponents[AxisIndex];
|
|
Params.Axis = Axes[AxisIndex];
|
|
AddPlaneTranslationGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
}
|
|
if (GizmoActor->FreeTranslateHandle != nullptr)
|
|
{
|
|
Params.Component = GizmoActor->FreeTranslateHandle;
|
|
Params.Axis = EAxis::None;
|
|
AddFreeTranslationGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
UPrimitiveComponent* RotationAxisComponents[3]{ GizmoActor->RotateX, GizmoActor->RotateY, GizmoActor->RotateZ };
|
|
for (int AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
|
|
{
|
|
if (RotationAxisComponents[AxisIndex])
|
|
{
|
|
Params.Component = RotationAxisComponents[AxisIndex];
|
|
Params.Axis = Axes[AxisIndex];
|
|
AddAxisRotationGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
}
|
|
if (GizmoActor->RotationSphere != nullptr)
|
|
{
|
|
ActiveComponents.Add(GizmoActor->RotationSphere);
|
|
RotationSubGizmos.Add(FSubGizmoInfo{ GizmoActor->RotationSphere, nullptr });
|
|
}
|
|
if (GizmoActor->FreeRotateHandle != nullptr)
|
|
{
|
|
Params.Component = GizmoActor->FreeRotateHandle;
|
|
Params.Axis = EAxis::None;
|
|
AddFreeRotationGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
if (GizmoActor->UniformScale != nullptr)
|
|
{
|
|
Params.Component = GizmoActor->UniformScale;
|
|
Params.Axis = EAxis::None;
|
|
AddUniformScaleGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
UPrimitiveComponent* ScaleAxisComponents[3]{ GizmoActor->AxisScaleX, GizmoActor->AxisScaleY, GizmoActor->AxisScaleZ };
|
|
for (int AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
|
|
{
|
|
if (ScaleAxisComponents[AxisIndex])
|
|
{
|
|
Params.Component = ScaleAxisComponents[AxisIndex];
|
|
Params.Axis = Axes[AxisIndex];
|
|
AddAxisScaleGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
}
|
|
UPrimitiveComponent* ScalePlaneComponents[3]{ GizmoActor->PlaneScaleYZ, GizmoActor->PlaneScaleXZ, GizmoActor->PlaneScaleXY };
|
|
for (int AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
|
|
{
|
|
if (ScalePlaneComponents[AxisIndex])
|
|
{
|
|
Params.Component = ScalePlaneComponents[AxisIndex];
|
|
Params.Axis = Axes[AxisIndex];
|
|
AddPlaneScaleGizmo(Params, *SubGizmoSharedState);
|
|
}
|
|
}
|
|
|
|
// Unpack the shared state into our properties. It might be nicer to just hold on to the shared state
|
|
// object (in case it is needed later), but we do this for compatibility with existing child classes.
|
|
StateTarget = SubGizmoSharedState->StateTarget;
|
|
AxisXSource = SubGizmoSharedState->CardinalAxisSources[0];
|
|
AxisYSource = SubGizmoSharedState->CardinalAxisSources[1];
|
|
AxisZSource = SubGizmoSharedState->CardinalAxisSources[2];
|
|
CameraAxisSource = SubGizmoSharedState->CameraAxisSource;
|
|
UnitAxisXSource = SubGizmoSharedState->UnitCardinalAxisSources[0];
|
|
UnitAxisYSource = SubGizmoSharedState->UnitCardinalAxisSources[1];
|
|
UnitAxisZSource = SubGizmoSharedState->UnitCardinalAxisSources[2];
|
|
|
|
ApplyGizmoActiveMode();
|
|
|
|
OnSetActiveTarget.Broadcast(this, ActiveTarget);
|
|
}
|
|
|
|
FTransform UCombinedTransformGizmo::GetGizmoTransform() const
|
|
{
|
|
USceneComponent* GizmoComponent = GizmoActor->GetRootComponent();
|
|
return GizmoComponent->GetComponentTransform();
|
|
}
|
|
|
|
void UCombinedTransformGizmo::ReinitializeGizmoTransform(const FTransform& NewTransform, bool bKeepGizmoUnscaled)
|
|
{
|
|
// To update the gizmo location without triggering any callbacks, we temporarily
|
|
// store a copy of the callback list, detach them, reposition, and then reattach
|
|
// the callbacks.
|
|
USceneComponent* GizmoComponent = GizmoActor->GetRootComponent();
|
|
auto temp = GizmoComponent->TransformUpdated;
|
|
GizmoComponent->TransformUpdated.Clear();
|
|
FTransform GizmoTransform = NewTransform;
|
|
if (bKeepGizmoUnscaled)
|
|
{
|
|
GizmoTransform.SetScale3D(FVector(1, 1, 1));
|
|
}
|
|
GizmoComponent->SetWorldTransform(GizmoTransform);
|
|
GizmoComponent->TransformUpdated = temp;
|
|
|
|
// The underlying proxy has an existing way to reinitialize its transform without callbacks.
|
|
bool bSavedSetPivotMode = ActiveTarget->bSetPivotMode;
|
|
ActiveTarget->bSetPivotMode = true;
|
|
ActiveTarget->SetTransform(NewTransform);
|
|
ActiveTarget->bSetPivotMode = bSavedSetPivotMode;
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetNewGizmoTransform(const FTransform& NewTransform, bool bKeepGizmoUnscaled)
|
|
{
|
|
check(ActiveTarget != nullptr);
|
|
|
|
BeginTransformEditSequence();
|
|
UpdateTransformDuringEditSequence(NewTransform, bKeepGizmoUnscaled);
|
|
EndTransformEditSequence();
|
|
}
|
|
|
|
void UCombinedTransformGizmo::BeginTransformEditSequence()
|
|
{
|
|
if (ensure(StateTarget))
|
|
{
|
|
StateTarget->BeginUpdate();
|
|
}
|
|
}
|
|
|
|
void UCombinedTransformGizmo::EndTransformEditSequence()
|
|
{
|
|
if (ensure(StateTarget))
|
|
{
|
|
StateTarget->EndUpdate();
|
|
}
|
|
}
|
|
|
|
void UCombinedTransformGizmo::UpdateTransformDuringEditSequence(const FTransform& NewTransform, bool bKeepGizmoUnscaled)
|
|
{
|
|
check(ActiveTarget != nullptr);
|
|
|
|
USceneComponent* GizmoComponent = GizmoActor->GetRootComponent();
|
|
FTransform GizmoTransform = NewTransform;
|
|
if (bKeepGizmoUnscaled)
|
|
{
|
|
GizmoTransform.SetScale3D(FVector(1, 1, 1));
|
|
}
|
|
GizmoComponent->SetWorldTransform(GizmoTransform);
|
|
ActiveTarget->SetTransform(NewTransform);
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetNewChildScale(const FVector& NewChildScale)
|
|
{
|
|
FTransform NewTransform = ActiveTarget->GetTransform();
|
|
NewTransform.SetScale3D(NewChildScale);
|
|
|
|
bool bSavedSetPivotMode = ActiveTarget->bSetPivotMode;
|
|
ActiveTarget->bSetPivotMode = true;
|
|
ActiveTarget->SetTransform(NewTransform);
|
|
ActiveTarget->bSetPivotMode = bSavedSetPivotMode;
|
|
}
|
|
|
|
|
|
void UCombinedTransformGizmo::SetVisibility(bool bVisible)
|
|
{
|
|
bool bPreviousVisibility = !GizmoActor->IsHidden();
|
|
|
|
GizmoActor->SetActorHiddenInGame(bVisible == false);
|
|
#if WITH_EDITOR
|
|
GizmoActor->SetIsTemporarilyHiddenInEditor(bVisible == false);
|
|
#endif
|
|
|
|
if (bPreviousVisibility != bVisible)
|
|
{
|
|
OnVisibilityChanged.Broadcast(this, bVisible);
|
|
}
|
|
}
|
|
|
|
void UCombinedTransformGizmo::SetDisplaySpaceTransform(TOptional<FTransform> TransformIn)
|
|
{
|
|
if (DisplaySpaceTransform.IsSet() != TransformIn.IsSet()
|
|
|| (TransformIn.IsSet() && !TransformIn.GetValue().Equals(DisplaySpaceTransform.GetValue())))
|
|
{
|
|
DisplaySpaceTransform = TransformIn;
|
|
OnDisplaySpaceTransformChanged.Broadcast(this, TransformIn);
|
|
}
|
|
}
|
|
|
|
ETransformGizmoSubElements UCombinedTransformGizmo::GetGizmoElements()
|
|
{
|
|
using namespace CombinedTransformGizmoLocals;
|
|
|
|
return GetSubElementFlagsFromActor(GizmoActor);
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisTranslationGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UAxisPositionGizmo* Gizmo = Cast<UAxisPositionGizmo>(GetGizmoManager()->CreateGizmo(AxisPositionBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
ensure(Gizmo->InitializeAsTranslateGizmo(Params, &SharedState));
|
|
|
|
if (UGizmoAxisTranslationParameterSource* ParamSource = Cast<UGizmoAxisTranslationParameterSource>(Gizmo->ParameterSource.GetObject()))
|
|
{
|
|
int AxisIndex = Params.GetClampedAxisIndex();
|
|
ParamSource->PositionConstraintFunction = [this](const FVector& Pos, FVector& Snapped) { return PositionSnapFunction(Pos, Snapped); };
|
|
ParamSource->AxisDeltaConstraintFunction = [this, AxisIndex](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, AxisIndex); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
TranslationSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddPlaneTranslationGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UPlanePositionGizmo* Gizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsTranslateGizmo(Params, &SharedState));
|
|
|
|
if (UGizmoPlaneTranslationParameterSource* ParamSource = Cast<UGizmoPlaneTranslationParameterSource>(Gizmo->ParameterSource.GetObject()))
|
|
{
|
|
int AxisIndex = Params.GetClampedAxisIndex();
|
|
int XAxes[3] = {1, 2, 0};
|
|
int YAxes[3] = {2, 0, 1};
|
|
ParamSource->PositionConstraintFunction = [this](const FVector& Pos, FVector& Snapped) { return PositionSnapFunction(Pos, Snapped); };
|
|
ParamSource->AxisXDeltaConstraintFunction = [this, XAxisIndex = XAxes[AxisIndex]](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, XAxisIndex); };
|
|
ParamSource->AxisYDeltaConstraintFunction = [this, YAxisIndex = YAxes[AxisIndex]](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, YAxisIndex); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
TranslationSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisRotationGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UAxisAngleGizmo* Gizmo = Cast<UAxisAngleGizmo>(GetGizmoManager()->CreateGizmo(AxisAngleBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsRotateGizmo(Params, &SharedState));
|
|
|
|
if (UGizmoAxisRotationParameterSource* AngleSource = Cast<UGizmoAxisRotationParameterSource>(Gizmo->AngleSource.GetObject()))
|
|
{
|
|
int AxisIndex = Params.GetClampedAxisIndex();
|
|
AngleSource->AngleDeltaConstraintFunction = [this, AxisIndex](double AngleDelta, double& SnappedDelta) { return RotationAxisAngleSnapFunction(AngleDelta, SnappedDelta, AxisIndex); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
RotationSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisScaleGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UAxisPositionGizmo* Gizmo = Cast<UAxisPositionGizmo>(GetGizmoManager()->CreateGizmo(AxisPositionBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsScaleGizmo(Params, bDisallowNegativeScaling, &SharedState));
|
|
|
|
if (UGizmoAxisScaleParameterSource* ParameterSource = Cast<UGizmoAxisScaleParameterSource>(Gizmo->ParameterSource.GetObject()))
|
|
{
|
|
int AxisIndex = Params.GetClampedAxisIndex();
|
|
ParameterSource->ScaleAxisDeltaConstraintFunction = [this, AxisIndex](const double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta, AxisIndex); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
NonUniformScaleSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddPlaneScaleGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UPlanePositionGizmo* Gizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsScaleGizmo(Params, bDisallowNegativeScaling, &SharedState));
|
|
|
|
if (UGizmoPlaneScaleParameterSource* ParameterSource = Cast<UGizmoPlaneScaleParameterSource>(Gizmo->ParameterSource.GetObject()))
|
|
{
|
|
int AxisIndex = Params.GetClampedAxisIndex();
|
|
int XAxes[3] = { 1, 2, 0 };
|
|
int YAxes[3] = { 2, 0, 1 };
|
|
ParameterSource->ScaleAxisXDeltaConstraintFunction = [this, XAxisIndex = XAxes[AxisIndex]](double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta, XAxisIndex); };
|
|
ParameterSource->ScaleAxisYDeltaConstraintFunction = [this, YAxisIndex = YAxes[AxisIndex]](double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta, YAxisIndex); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
NonUniformScaleSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddUniformScaleGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UPlanePositionGizmo* Gizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsUniformScaleGizmo(Params, bDisallowNegativeScaling, &SharedState));
|
|
|
|
if (UGizmoUniformScaleParameterSource* ParameterSource = Cast<UGizmoUniformScaleParameterSource>(Gizmo->ParameterSource.GetObject()))
|
|
{
|
|
ParameterSource->ScaleAxisDeltaConstraintFunction = [this](const double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta); };
|
|
}
|
|
else
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
UniformScaleSubGizmos.Add(FSubGizmoInfo{ Params.Component, Gizmo });
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddFreeTranslationGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UFreePositionSubGizmo* Gizmo = UE::GizmoUtil::CreateGizmoViaSimpleBuilder<UFreePositionSubGizmo>(
|
|
GetGizmoManager(), FString(), this);
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
ensure(Gizmo->InitializeAsScreenPlaneTranslateGizmo(Params, &SharedState));
|
|
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddFreeRotationGizmo(
|
|
FTransformSubGizmoCommonParams& Params, FTransformSubGizmoSharedState& SharedState)
|
|
{
|
|
UFreeRotationSubGizmo* Gizmo = UE::GizmoUtil::CreateGizmoViaSimpleBuilder<UFreeRotationSubGizmo>(
|
|
GetGizmoManager(), FString(), this);
|
|
if (!ensure(Gizmo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
UGizmoViewContext* GizmoViewContext = UE::GizmoUtil::GetGizmoViewContext(GetGizmoManager());
|
|
ensure(Gizmo->InitializeAsRotationGizmo(Params, GizmoViewContext, &SharedState));
|
|
|
|
ActiveComponents.Add(Params.Component);
|
|
ActiveGizmos.Add(Gizmo);
|
|
|
|
return Gizmo;
|
|
}
|
|
|
|
|
|
// These are deprecated initialization functions that do sub gizmo initialization by hand instead of using the
|
|
// "InitializeAs..." functions that were added to subgizmos to make them simpler to instantiate outside of
|
|
// this class.
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisTranslationGizmo(
|
|
UPrimitiveComponent* AxisComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* AxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn,
|
|
int AxisIndex)
|
|
{
|
|
// create axis-position gizmo, axis-position parameter will drive translation
|
|
UAxisPositionGizmo* TranslateGizmo = Cast<UAxisPositionGizmo>(GetGizmoManager()->CreateGizmo(AxisPositionBuilderIdentifier));
|
|
check(TranslateGizmo);
|
|
|
|
// axis source provides the translation axis
|
|
TranslateGizmo->AxisSource = Cast<UObject>(AxisSource);
|
|
|
|
// parameter source maps axis-parameter-change to translation of TransformSource's transform
|
|
UGizmoAxisTranslationParameterSource* ParamSource = UGizmoAxisTranslationParameterSource::Construct(AxisSource, TransformSource, this);
|
|
ParamSource->PositionConstraintFunction = [this](const FVector& Pos, FVector& Snapped) { return PositionSnapFunction(Pos, Snapped); };
|
|
ParamSource->AxisDeltaConstraintFunction = [this, AxisIndex](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, AxisIndex); };
|
|
TranslateGizmo->ParameterSource = ParamSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(AxisComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [AxisComponent, this](bool bHovering) { this->UpdateHoverFunction(AxisComponent, bHovering); };
|
|
}
|
|
TranslateGizmo->HitTarget = HitTarget;
|
|
|
|
TranslateGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
TranslateGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
TranslateGizmo->CustomDestinationFunc =
|
|
[this](const UAxisPositionGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
|
|
ActiveGizmos.Add(TranslateGizmo);
|
|
return TranslateGizmo;
|
|
}
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddPlaneTranslationGizmo(
|
|
UPrimitiveComponent* AxisComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* AxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn,
|
|
int XAxisIndex, int YAxisIndex)
|
|
{
|
|
// create axis-position gizmo, axis-position parameter will drive translation
|
|
UPlanePositionGizmo* TranslateGizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
check(TranslateGizmo);
|
|
|
|
// axis source provides the translation axis
|
|
TranslateGizmo->AxisSource = Cast<UObject>(AxisSource);
|
|
|
|
// parameter source maps axis-parameter-change to translation of TransformSource's transform
|
|
UGizmoPlaneTranslationParameterSource* ParamSource = UGizmoPlaneTranslationParameterSource::Construct(AxisSource, TransformSource, this);
|
|
ParamSource->PositionConstraintFunction = [this](const FVector& Pos, FVector& Snapped) { return PositionSnapFunction(Pos, Snapped); };
|
|
ParamSource->AxisXDeltaConstraintFunction = [this, XAxisIndex](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, XAxisIndex); };
|
|
ParamSource->AxisYDeltaConstraintFunction = [this, YAxisIndex](double AxisDelta, double& SnappedAxisDelta) { return PositionAxisDeltaSnapFunction(AxisDelta, SnappedAxisDelta, YAxisIndex); };
|
|
TranslateGizmo->ParameterSource = ParamSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(AxisComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [AxisComponent, this](bool bHovering) { this->UpdateHoverFunction(AxisComponent, bHovering); };
|
|
}
|
|
TranslateGizmo->HitTarget = HitTarget;
|
|
|
|
TranslateGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
TranslateGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
TranslateGizmo->CustomDestinationFunc =
|
|
[this](const UPlanePositionGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
|
|
ActiveGizmos.Add(TranslateGizmo);
|
|
return TranslateGizmo;
|
|
}
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisRotationGizmo(
|
|
UPrimitiveComponent* AxisComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* AxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn)
|
|
{
|
|
// create axis-angle gizmo, angle will drive axis-rotation
|
|
UAxisAngleGizmo* RotateGizmo = Cast<UAxisAngleGizmo>(GetGizmoManager()->CreateGizmo(AxisAngleBuilderIdentifier));
|
|
check(RotateGizmo);
|
|
|
|
// axis source provides the rotation axis
|
|
RotateGizmo->AxisSource = Cast<UObject>(AxisSource);
|
|
|
|
// parameter source maps angle-parameter-change to rotation of TransformSource's transform
|
|
UGizmoAxisRotationParameterSource* AngleSource = UGizmoAxisRotationParameterSource::Construct(AxisSource, TransformSource, this);
|
|
// axis rotation is currently only relative so it should only ever snap angle-deltas
|
|
//AngleSource->RotationConstraintFunction = [this](const FQuat& DeltaRotation){ return RotationSnapFunction(DeltaRotation); };
|
|
AngleSource->AngleDeltaConstraintFunction = [this](double AngleDelta, double& SnappedDelta){ return RotationAxisAngleSnapFunction(AngleDelta, SnappedDelta, 0); };
|
|
RotateGizmo->AngleSource = AngleSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(AxisComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [AxisComponent, this](bool bHovering) { this->UpdateHoverFunction(AxisComponent, bHovering); };
|
|
}
|
|
RotateGizmo->HitTarget = HitTarget;
|
|
|
|
RotateGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
RotateGizmo->ShouldUseCustomDestinationFunc = [this]() { return ShouldAlignDestination(); };
|
|
RotateGizmo->CustomDestinationFunc =
|
|
[this](const UAxisAngleGizmo::FCustomDestinationParams& Params, FVector& OutputPoint) {
|
|
return DestinationAlignmentRayCaster(*Params.WorldRay, OutputPoint);
|
|
};
|
|
|
|
ActiveGizmos.Add(RotateGizmo);
|
|
|
|
return RotateGizmo;
|
|
}
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddAxisScaleGizmo(
|
|
UPrimitiveComponent* AxisComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* GizmoAxisSource, IGizmoAxisSource* ParameterAxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn)
|
|
{
|
|
// create axis-position gizmo, axis-position parameter will drive scale
|
|
UAxisPositionGizmo* ScaleGizmo = Cast<UAxisPositionGizmo>(GetGizmoManager()->CreateGizmo(AxisPositionBuilderIdentifier));
|
|
ScaleGizmo->bEnableSignedAxis = true;
|
|
check(ScaleGizmo);
|
|
|
|
// axis source provides the translation axis
|
|
ScaleGizmo->AxisSource = Cast<UObject>(GizmoAxisSource);
|
|
|
|
// parameter source maps axis-parameter-change to translation of TransformSource's transform
|
|
UGizmoAxisScaleParameterSource* ParamSource = UGizmoAxisScaleParameterSource::Construct(ParameterAxisSource, TransformSource, this);
|
|
ParamSource->ScaleAxisDeltaConstraintFunction = [this](const double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta); };
|
|
ParamSource->bClampToZero = bDisallowNegativeScaling;
|
|
ScaleGizmo->ParameterSource = ParamSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(AxisComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [AxisComponent, this](bool bHovering) { this->UpdateHoverFunction(AxisComponent, bHovering); };
|
|
}
|
|
ScaleGizmo->HitTarget = HitTarget;
|
|
|
|
ScaleGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
ActiveGizmos.Add(ScaleGizmo);
|
|
return ScaleGizmo;
|
|
}
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddPlaneScaleGizmo(
|
|
UPrimitiveComponent* AxisComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* GizmoAxisSource, IGizmoAxisSource* ParameterAxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn)
|
|
{
|
|
// create axis-position gizmo, axis-position parameter will drive scale
|
|
UPlanePositionGizmo* ScaleGizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
ScaleGizmo->bEnableSignedAxis = true;
|
|
check(ScaleGizmo);
|
|
|
|
// axis source provides the translation axis
|
|
ScaleGizmo->AxisSource = Cast<UObject>(GizmoAxisSource);
|
|
|
|
// parameter source maps axis-parameter-change to translation of TransformSource's transform
|
|
UGizmoPlaneScaleParameterSource* ParamSource = UGizmoPlaneScaleParameterSource::Construct(ParameterAxisSource, TransformSource, this);
|
|
ParamSource->ScaleAxisXDeltaConstraintFunction = [this](double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta); };
|
|
ParamSource->ScaleAxisYDeltaConstraintFunction = [this](double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta); };
|
|
ParamSource->bClampToZero = bDisallowNegativeScaling;
|
|
ParamSource->bUseEqualScaling = true;
|
|
ScaleGizmo->ParameterSource = ParamSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(AxisComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [AxisComponent, this](bool bHovering) { this->UpdateHoverFunction(AxisComponent, bHovering); };
|
|
}
|
|
ScaleGizmo->HitTarget = HitTarget;
|
|
|
|
ScaleGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
ActiveGizmos.Add(ScaleGizmo);
|
|
return ScaleGizmo;
|
|
}
|
|
UInteractiveGizmo* UCombinedTransformGizmo::AddUniformScaleGizmo(
|
|
UPrimitiveComponent* ScaleComponent, USceneComponent* RootComponent,
|
|
IGizmoAxisSource* GizmoAxisSource, IGizmoAxisSource* ParameterAxisSource,
|
|
IGizmoTransformSource* TransformSource,
|
|
IGizmoStateTarget* StateTargetIn)
|
|
{
|
|
// create plane-position gizmo, plane-position parameter will drive scale
|
|
UPlanePositionGizmo* ScaleGizmo = Cast<UPlanePositionGizmo>(GetGizmoManager()->CreateGizmo(PlanePositionBuilderIdentifier));
|
|
check(ScaleGizmo);
|
|
|
|
// axis source provides the translation plane
|
|
ScaleGizmo->AxisSource = Cast<UObject>(GizmoAxisSource);
|
|
|
|
// parameter source maps axis-parameter-change to translation of TransformSource's transform
|
|
UGizmoUniformScaleParameterSource* ParamSource = UGizmoUniformScaleParameterSource::Construct(ParameterAxisSource, TransformSource, this);
|
|
ParamSource->ScaleAxisDeltaConstraintFunction = [this](const double ScaleAxisDelta, double& SnappedScaleAxisDelta) { return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedScaleAxisDelta); };
|
|
ScaleGizmo->ParameterSource = ParamSource;
|
|
|
|
// sub-component provides hit target
|
|
UGizmoComponentHitTarget* HitTarget = UGizmoComponentHitTarget::Construct(ScaleComponent, this);
|
|
if (this->UpdateHoverFunction)
|
|
{
|
|
HitTarget->UpdateHoverFunction = [ScaleComponent, this](bool bHovering) { this->UpdateHoverFunction(ScaleComponent, bHovering); };
|
|
}
|
|
ScaleGizmo->HitTarget = HitTarget;
|
|
|
|
ScaleGizmo->StateTarget = Cast<UObject>(StateTargetIn);
|
|
|
|
ActiveGizmos.Add(ScaleGizmo);
|
|
return ScaleGizmo;
|
|
}
|
|
|
|
|
|
|
|
void UCombinedTransformGizmo::ClearActiveTarget()
|
|
{
|
|
OnAboutToClearActiveTarget.Broadcast(this, ActiveTarget);
|
|
|
|
for (UInteractiveGizmo* Gizmo : ActiveGizmos)
|
|
{
|
|
GetGizmoManager()->DestroyGizmo(Gizmo);
|
|
}
|
|
ActiveGizmos.SetNum(0);
|
|
ActiveComponents.SetNum(0);
|
|
TranslationSubGizmos.SetNum(0);
|
|
RotationSubGizmos.SetNum(0);
|
|
UniformScaleSubGizmos.SetNum(0);
|
|
NonUniformScaleSubGizmos.SetNum(0);
|
|
|
|
CameraAxisSource = nullptr;
|
|
AxisXSource = nullptr;
|
|
AxisYSource = nullptr;
|
|
AxisZSource = nullptr;
|
|
UnitAxisXSource = nullptr;
|
|
UnitAxisYSource = nullptr;
|
|
UnitAxisZSource = nullptr;
|
|
StateTarget = nullptr;
|
|
|
|
ActiveTarget = nullptr;
|
|
TransactionProviderAtLastSetActiveTarget = nullptr;
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UCombinedTransformGizmo::PositionSnapFunction(const FVector& WorldPosition, FVector& SnappedPositionOut) const
|
|
{
|
|
SnappedPositionOut = WorldPosition;
|
|
|
|
// only snap world positions if we want world position snapping...
|
|
if (bSnapToWorldGrid == false || RelativeTranslationSnapping.IsEnabled() == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// we can only snap positions in world coordinate system
|
|
EToolContextCoordinateSystem CoordSystem = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem();
|
|
if (CoordSystem != EToolContextCoordinateSystem::World)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// need a snapping manager
|
|
if ( USceneSnappingManager* SnapManager = USceneSnappingManager::Find(GetGizmoManager()) )
|
|
{
|
|
FSceneSnapQueryRequest Request;
|
|
Request.RequestType = ESceneSnapQueryType::Position;
|
|
Request.TargetTypes = ESceneSnapQueryTargetType::Grid;
|
|
if ( bGridSizeIsExplicit )
|
|
{
|
|
Request.GridSize = ExplicitGridSize;
|
|
}
|
|
TArray<FSceneSnapQueryResult> Results;
|
|
Results.Reserve(1);
|
|
|
|
Request.Position = WorldPosition;
|
|
if (SnapManager->ExecuteSceneSnapQuery(Request, Results))
|
|
{
|
|
SnappedPositionOut = Results[0].Position;
|
|
return true;
|
|
};
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UCombinedTransformGizmo::PositionAxisDeltaSnapFunction(double AxisDelta, double& SnappedDeltaOut, int AxisIndex) const
|
|
{
|
|
if (CustomTranslationDeltaConstraintFunctions[AxisIndex])
|
|
{
|
|
return CustomTranslationDeltaConstraintFunctions[AxisIndex](AxisDelta, SnappedDeltaOut);
|
|
}
|
|
|
|
if (!bSnapToWorldGrid) return false;
|
|
|
|
EToolContextCoordinateSystem CoordSystem = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem();
|
|
bool bUseRelativeSnapping = RelativeTranslationSnapping.IsEnabled() || (CoordSystem != EToolContextCoordinateSystem::World);
|
|
if (!bUseRelativeSnapping)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( USceneSnappingManager* SnapManager = USceneSnappingManager::Find(GetGizmoManager()) )
|
|
{
|
|
FSceneSnapQueryRequest Request;
|
|
Request.RequestType = ESceneSnapQueryType::Position;
|
|
Request.TargetTypes = ESceneSnapQueryTargetType::Grid;
|
|
if ( bGridSizeIsExplicit )
|
|
{
|
|
Request.GridSize = ExplicitGridSize;
|
|
}
|
|
TArray<FSceneSnapQueryResult> Results;
|
|
Results.Reserve(1);
|
|
|
|
// this is a bit of a hack, since the snap query only snaps world points, and the grid may not be
|
|
// uniform. A point on the specified X/Y/Z at the delta-distance is snapped, this is ideally
|
|
// equivalent to actually computing a snap of the axis-delta
|
|
Request.Position = FVector::Zero();
|
|
Request.Position[AxisIndex] = AxisDelta;
|
|
if (SnapManager->ExecuteSceneSnapQuery(Request, Results))
|
|
{
|
|
SnappedDeltaOut = Results[0].Position[AxisIndex];
|
|
return true;
|
|
};
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
FQuat UCombinedTransformGizmo::RotationSnapFunction(const FQuat& DeltaRotation) const
|
|
{
|
|
// note: this is currently unused. Although we can snap to the "rotation grid", since the
|
|
// gizmo only supports axis rotations, it doesn't make sense. Leaving in for now in case
|
|
// a "tumble" handle is added, in which case it makes sense to snap to the world rotation grid...
|
|
|
|
FQuat SnappedDeltaRotation = DeltaRotation;
|
|
|
|
// only snap world positions if we want world position snapping...
|
|
if (bSnapToWorldRotGrid == false )
|
|
{
|
|
return SnappedDeltaRotation;
|
|
}
|
|
|
|
// can only snap absolute rotations in World coordinates
|
|
EToolContextCoordinateSystem CoordSystem = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentCoordinateSystem();
|
|
if (CoordSystem != EToolContextCoordinateSystem::World)
|
|
{
|
|
return SnappedDeltaRotation;
|
|
}
|
|
|
|
// need a snapping manager
|
|
if ( USceneSnappingManager* SnapManager = USceneSnappingManager::Find(GetGizmoManager()) )
|
|
{
|
|
FSceneSnapQueryRequest Request;
|
|
Request.RequestType = ESceneSnapQueryType::Rotation;
|
|
Request.TargetTypes = ESceneSnapQueryTargetType::Grid;
|
|
Request.DeltaRotation = DeltaRotation;
|
|
if ( bRotationGridSizeIsExplicit )
|
|
{
|
|
Request.RotGridSize = ExplicitRotationGridSize;
|
|
}
|
|
TArray<FSceneSnapQueryResult> Results;
|
|
if (SnapManager->ExecuteSceneSnapQuery(Request, Results))
|
|
{
|
|
SnappedDeltaRotation = Results[0].DeltaRotation;
|
|
};
|
|
}
|
|
|
|
return SnappedDeltaRotation;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UCombinedTransformGizmo::RotationAxisAngleSnapFunction(double AxisAngleDelta, double& SnappedAxisAngleDeltaOut, int AxisIndex) const
|
|
{
|
|
if (CustomRotationDeltaConstraintFunctions[AxisIndex])
|
|
{
|
|
return CustomRotationDeltaConstraintFunctions[AxisIndex](AxisAngleDelta, SnappedAxisAngleDeltaOut);
|
|
}
|
|
|
|
if (!bSnapToWorldRotGrid) return false;
|
|
|
|
FToolContextSnappingConfiguration SnappingConfig = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentSnappingSettings();
|
|
if ( SnappingConfig.bEnableRotationGridSnapping )
|
|
{
|
|
double SnapDelta = SnappingConfig.RotationGridAngles.Yaw; // could use AxisIndex here?
|
|
if ( bRotationGridSizeIsExplicit )
|
|
{
|
|
SnapDelta = ExplicitRotationGridSize.Yaw;
|
|
}
|
|
AxisAngleDelta *= FMathd::RadToDeg;
|
|
SnappedAxisAngleDeltaOut = UE::Geometry::SnapToIncrement(AxisAngleDelta, SnapDelta);
|
|
SnappedAxisAngleDeltaOut *= FMathd::DegToRad;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UCombinedTransformGizmo::ScaleAxisDeltaSnapFunction(const double ScaleAxisDelta, double& SnappedAxisScaleDeltaOut, int AxisIndex) const
|
|
{
|
|
if (CustomScaleDeltaConstraintFunctions[AxisIndex])
|
|
{
|
|
return CustomScaleDeltaConstraintFunctions[AxisIndex](ScaleAxisDelta, SnappedAxisScaleDeltaOut);
|
|
}
|
|
|
|
return ScaleAxisDeltaSnapFunction(ScaleAxisDelta, SnappedAxisScaleDeltaOut);
|
|
}
|
|
|
|
bool UCombinedTransformGizmo::ScaleAxisDeltaSnapFunction(const double ScaleAxisDelta, double & SnappedAxisScaleDeltaOut) const
|
|
{
|
|
if (!bSnapToScaleGrid) return false;
|
|
|
|
const FToolContextSnappingConfiguration SnappingConfig = GetGizmoManager()->GetContextQueriesAPI()->GetCurrentSnappingSettings();
|
|
if ( SnappingConfig.bEnableScaleGridSnapping )
|
|
{
|
|
const double SnapDelta = SnappingConfig.ScaleGridSize;
|
|
SnappedAxisScaleDeltaOut = UE::Geometry::SnapToIncrement(ScaleAxisDelta, SnapDelta);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|