You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb david.hill #preflight 627d2b99a82f3adab87fb87f [CL 20165573 by Jimmy Andrews in ue5-main branch]
1446 lines
45 KiB
C++
1446 lines
45 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Sculpting/MeshSculptToolBase.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "InteractiveGizmoManager.h"
|
|
#include "BaseGizmos/BrushStampIndicator.h"
|
|
#include "ToolSetupUtil.h"
|
|
#include "ToolSceneQueriesUtil.h"
|
|
#include "PreviewMesh.h"
|
|
#include "BaseGizmos/GizmoComponents.h"
|
|
#include "BaseGizmos/TransformGizmoUtil.h"
|
|
#include "Drawing/MeshDebugDrawing.h"
|
|
|
|
#include "Sculpting/StampFalloffs.h"
|
|
|
|
#include "Generators/SphereGenerator.h"
|
|
|
|
#include "ModelingToolTargetUtil.h"
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "UMeshSculptToolBase"
|
|
|
|
|
|
namespace
|
|
{
|
|
const FString VertexSculptIndicatorGizmoType = TEXT("VertexSculptIndicatorGizmoType");
|
|
}
|
|
|
|
|
|
void FBrushToolRadius::InitializeWorldSizeRange(TInterval<float> Range, bool bValidateWorldRadius)
|
|
{
|
|
WorldSizeRange = Range;
|
|
if (WorldRadius < WorldSizeRange.Min)
|
|
{
|
|
WorldRadius = WorldSizeRange.Interpolate(0.2);
|
|
}
|
|
else if (WorldRadius > WorldSizeRange.Max)
|
|
{
|
|
WorldRadius = WorldSizeRange.Interpolate(0.8);
|
|
}
|
|
}
|
|
|
|
float FBrushToolRadius::GetWorldRadius() const
|
|
{
|
|
if (SizeType == EBrushToolSizeType::Adaptive)
|
|
{
|
|
return 0.5 * WorldSizeRange.Interpolate( FMath::Max(0, AdaptiveSize) );
|
|
}
|
|
else
|
|
{
|
|
return WorldRadius;
|
|
}
|
|
}
|
|
|
|
void FBrushToolRadius::IncreaseRadius(bool bSmallStep)
|
|
{
|
|
float StepSize = (bSmallStep) ? 0.005f : 0.025f;
|
|
if (SizeType == EBrushToolSizeType::Adaptive)
|
|
{
|
|
AdaptiveSize = FMath::Clamp(AdaptiveSize + StepSize, 0.0f, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
float dt = StepSize * 0.5 * WorldSizeRange.Size();
|
|
WorldRadius = FMath::Clamp(WorldRadius + dt, WorldSizeRange.Min, WorldSizeRange.Max);
|
|
}
|
|
}
|
|
|
|
void FBrushToolRadius::DecreaseRadius(bool bSmallStep)
|
|
{
|
|
float StepSize = (bSmallStep) ? 0.005f : 0.025f;
|
|
if (SizeType == EBrushToolSizeType::Adaptive)
|
|
{
|
|
AdaptiveSize = FMath::Clamp(AdaptiveSize - StepSize, 0.0f, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
float dt = StepSize * 0.5 * WorldSizeRange.Size();
|
|
WorldRadius = FMath::Clamp(WorldRadius - dt, WorldSizeRange.Min, WorldSizeRange.Max);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::SetWorld(UWorld* World)
|
|
{
|
|
this->TargetWorld = World;
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::Setup()
|
|
{
|
|
UMeshSurfacePointTool::Setup();
|
|
|
|
BrushProperties = NewObject<USculptBrushProperties>(this);
|
|
BrushProperties->RestoreProperties(this);
|
|
// Note that brush properties includes BrushRadius, which, when not used as a constant,
|
|
// serves as an output property based on target size and brush size, and so it would need
|
|
// updating after the RestoreProperties() call. But deriving classes will call
|
|
// InitializeBrushSizeRange after this Setup() call to finish the brush setup, which will
|
|
// update the output property if necessary.
|
|
|
|
// work plane
|
|
GizmoProperties = NewObject<UWorkPlaneProperties>();
|
|
GizmoProperties->RestoreProperties(this);
|
|
|
|
// create proxy for plane gizmo, but not gizmo itself, as it only appears in FixedPlane brush mode
|
|
// listen for changes to the proxy and update the plane when that happens
|
|
PlaneTransformProxy = NewObject<UTransformProxy>(this);
|
|
PlaneTransformProxy->OnTransformChanged.AddUObject(this, &UMeshSculptToolBase::PlaneTransformChanged);
|
|
|
|
//GizmoProperties->WatchProperty(GizmoProperties->Position,
|
|
// [this](FVector NewPosition) { UpdateGizmoFromProperties(); });
|
|
//GizmoProperties->WatchProperty(GizmoProperties->Rotation,
|
|
// [this](FQuat NewRotation) { UpdateGizmoFromProperties(); });
|
|
GizmoPositionWatcher.Initialize(
|
|
[this]() { return GizmoProperties->Position; },
|
|
[this](FVector NewPosition) { UpdateGizmoFromProperties(); }, GizmoProperties->Position);
|
|
GizmoRotationWatcher.Initialize(
|
|
[this]() { return GizmoProperties->Rotation; },
|
|
[this](FQuat NewRotation) { UpdateGizmoFromProperties(); }, GizmoProperties->Rotation);
|
|
|
|
|
|
|
|
// display
|
|
ViewProperties = NewObject<UMeshEditingViewProperties>();
|
|
ViewProperties->RestoreProperties(this);
|
|
|
|
ViewProperties->WatchProperty(ViewProperties->bShowWireframe,
|
|
[this](bool bNewValue) { UpdateWireframeVisibility(bNewValue); });
|
|
ViewProperties->WatchProperty(ViewProperties->MaterialMode,
|
|
[this](EMeshEditingMaterialModes NewMode) { UpdateMaterialMode(NewMode); });
|
|
ViewProperties->WatchProperty(ViewProperties->CustomMaterial,
|
|
[this](TWeakObjectPtr<UMaterialInterface> NewMaterial) { UpdateCustomMaterial(NewMaterial); });
|
|
ViewProperties->WatchProperty(ViewProperties->bFlatShading,
|
|
[this](bool bNewValue) { UpdateFlatShadingSetting(bNewValue); });
|
|
ViewProperties->WatchProperty(ViewProperties->Color,
|
|
[this](FLinearColor NewColor) { UpdateColorSetting(NewColor); });
|
|
// This can actually use the same function since the parameter names for the material are the same
|
|
ViewProperties->WatchProperty(ViewProperties->TransparentMaterialColor,
|
|
[this](FLinearColor NewColor) { UpdateTransparentColorSetting(NewColor); });
|
|
ViewProperties->WatchProperty(ViewProperties->Opacity,
|
|
[this](double NewValue) { UpdateOpacitySetting(NewValue); });
|
|
ViewProperties->WatchProperty(ViewProperties->bTwoSided,
|
|
[this](bool bOn) { UpdateTwoSidedSetting(bOn); });
|
|
ViewProperties->WatchProperty(ViewProperties->Image,
|
|
[this](UTexture2D* NewImage) { UpdateImageSetting(NewImage); });
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::OnCompleteSetup()
|
|
{
|
|
RestoreAllBrushTypeProperties(this);
|
|
|
|
for (auto Pair : BrushOpPropSets)
|
|
{
|
|
SetToolPropertySourceEnabled(Pair.Value, false);
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
if (ShutdownType == EToolShutdownType::Accept && AreAllTargetsValid() == false)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Tool Target has become Invalid (possibly it has been Force Deleted). Aborting Tool."));
|
|
ShutdownType = EToolShutdownType::Cancel;
|
|
}
|
|
|
|
UMeshSurfacePointTool::Shutdown(ShutdownType);
|
|
|
|
BrushIndicatorMesh->Disconnect();
|
|
BrushIndicatorMesh = nullptr;
|
|
|
|
GetToolManager()->GetPairedGizmoManager()->DestroyAllGizmosByOwner(this);
|
|
BrushIndicator = nullptr;
|
|
GetToolManager()->GetPairedGizmoManager()->DeregisterGizmoType(VertexSculptIndicatorGizmoType);
|
|
|
|
BrushProperties->SaveProperties(this);
|
|
if (GizmoProperties)
|
|
{
|
|
GizmoProperties->SaveProperties(this);
|
|
}
|
|
|
|
ViewProperties->SaveProperties(this);
|
|
|
|
SaveAllBrushTypeProperties(this);
|
|
|
|
|
|
// bake result
|
|
UBaseDynamicMeshComponent* DynamicMeshComponent = GetSculptMeshComponent();
|
|
if (DynamicMeshComponent != nullptr)
|
|
{
|
|
UE::ToolTarget::ShowSourceObject(Target);
|
|
|
|
if (ShutdownType == EToolShutdownType::Accept)
|
|
{
|
|
// safe to do this here because we are about to destroy componeont
|
|
DynamicMeshComponent->ApplyTransform(InitialTargetTransform, true);
|
|
|
|
// this block bakes the modified DynamicMeshComponent back into the StaticMeshComponent inside an undo transaction
|
|
CommitResult(DynamicMeshComponent, false);
|
|
}
|
|
|
|
DynamicMeshComponent->UnregisterComponent();
|
|
DynamicMeshComponent->DestroyComponent();
|
|
DynamicMeshComponent = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::CommitResult(UBaseDynamicMeshComponent* Component, bool bModifiedTopology)
|
|
{
|
|
GetToolManager()->BeginUndoTransaction(LOCTEXT("SculptMeshToolTransactionName", "Sculpt Mesh"));
|
|
Component->ProcessMesh([&](const FDynamicMesh3& CurMesh)
|
|
{
|
|
UE::ToolTarget::CommitDynamicMeshUpdate(Target, CurMesh, bModifiedTopology);
|
|
});
|
|
GetToolManager()->EndUndoTransaction();
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::OnTick(float DeltaTime)
|
|
{
|
|
GizmoPositionWatcher.CheckAndUpdate();
|
|
GizmoRotationWatcher.CheckAndUpdate();
|
|
|
|
ActivePressure = GetCurrentDevicePressure();
|
|
|
|
if (InStroke() == false)
|
|
{
|
|
SaveActiveStrokeModifiers();
|
|
}
|
|
else
|
|
{
|
|
AccumulateStrokeTime(DeltaTime);
|
|
}
|
|
|
|
// update cached falloff
|
|
CurrentBrushFalloff = 0.5;
|
|
if (GetActiveBrushOp()->PropertySet.IsValid())
|
|
{
|
|
CurrentBrushFalloff = FMathd::Clamp(GetActiveBrushOp()->PropertySet->GetFalloff(), 0.0, 1.0);
|
|
}
|
|
|
|
UpdateHoverStamp(GetBrushFrameWorld());
|
|
|
|
if (bInStroke)
|
|
{
|
|
BrushIndicator->Update((float)GetCurrentBrushRadius(),
|
|
CurrentStamp.WorldFrame.ToFTransform(), 1.0f - (float)GetCurrentBrushFalloff());
|
|
}
|
|
else
|
|
{
|
|
BrushIndicator->Update((float)GetCurrentBrushRadius(),
|
|
HoverStamp.WorldFrame.ToFTransform(), 1.0f - (float)GetCurrentBrushFalloff());
|
|
}
|
|
|
|
UpdateWorkPlane();
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
UMeshSurfacePointTool::Render(RenderAPI);
|
|
// Cache here for usage during interaction, should probably happen in ::Tick() or elsewhere
|
|
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState);
|
|
|
|
FViewCameraState RenderCameraState = RenderAPI->GetCameraState();
|
|
|
|
if (BrushIndicatorMaterial)
|
|
{
|
|
double FixedDimScale = ToolSceneQueriesUtil::CalculateDimensionFromVisualAngleD(RenderCameraState, HoverStamp.WorldFrame.Origin, 1.5f);
|
|
BrushIndicatorMaterial->SetScalarParameterValue(TEXT("FalloffWidth"), FixedDimScale);
|
|
}
|
|
|
|
if (ShowWorkPlane())
|
|
{
|
|
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
|
|
FColor GridColor(128, 128, 128, 32);
|
|
float GridThickness = 0.5f*RenderCameraState.GetPDIScalingFactor();
|
|
int NumGridLines = 10;
|
|
FFrame3d DrawFrame(GizmoProperties->Position, GizmoProperties->Rotation);
|
|
MeshDebugDraw::DrawSimpleFixedScreenAreaGrid(RenderCameraState, DrawFrame, NumGridLines, 45.0, GridThickness, GridColor, false, PDI, FTransform::Identity);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::InitializeSculptMeshComponent(UBaseDynamicMeshComponent* Component, AActor* Actor)
|
|
{
|
|
ToolSetupUtil::ApplyRenderingConfigurationToPreview(Component, nullptr);
|
|
|
|
// disable shadows initially, as changing shadow settings invalidates the SceneProxy
|
|
Component->SetShadowsEnabled(false);
|
|
Component->SetupAttachment(Actor->GetRootComponent());
|
|
Component->RegisterComponent();
|
|
|
|
// initialize from LOD-0 MeshDescription
|
|
Component->SetMesh(UE::ToolTarget::GetDynamicMeshCopy(Target));
|
|
double MaxDimension = Component->GetMesh()->GetBounds(true).MaxDim();
|
|
|
|
// bake rotation and scaling into mesh because handling these inside sculpting is a mess
|
|
// Note: this transform does not include translation ( so only the 3x3 transform)
|
|
InitialTargetTransform = UE::ToolTarget::GetLocalToWorldTransform(Target);
|
|
// clamp scaling because if we allow zero-scale we cannot invert this transform on Accept
|
|
InitialTargetTransform.ClampMinimumScale(0.01);
|
|
FVector3d Translation = InitialTargetTransform.GetTranslation();
|
|
InitialTargetTransform.SetTranslation(FVector3d::Zero());
|
|
Component->ApplyTransform(InitialTargetTransform, false);
|
|
CurTargetTransform = FTransform3d(Translation);
|
|
Component->SetWorldTransform((FTransform)CurTargetTransform);
|
|
|
|
// hide input Component
|
|
UE::ToolTarget::HideSourceObject(Target);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::RegisterBrushType(int32 Identifier, FText Name, TUniquePtr<FMeshSculptBrushOpFactory> Factory, UMeshSculptBrushOpProps* PropSet)
|
|
{
|
|
FBrushTypeInfo TypeInfo;
|
|
TypeInfo.Name = Name;
|
|
TypeInfo.Identifier = Identifier;
|
|
RegisteredPrimaryBrushTypes.Add(TypeInfo);
|
|
|
|
check(BrushOpPropSets.Contains(Identifier) == false && BrushOpFactories.Contains(Identifier) == false);
|
|
BrushOpPropSets.Add(Identifier, PropSet);
|
|
BrushOpFactories.Add(Identifier, MoveTemp(Factory));
|
|
|
|
AddToolPropertySource(PropSet);
|
|
SetToolPropertySourceEnabled(PropSet, false);
|
|
}
|
|
|
|
void UMeshSculptToolBase::RegisterSecondaryBrushType(int32 Identifier, FText Name, TUniquePtr<FMeshSculptBrushOpFactory> Factory, UMeshSculptBrushOpProps* PropSet)
|
|
{
|
|
FBrushTypeInfo TypeInfo;
|
|
TypeInfo.Name = Name;
|
|
TypeInfo.Identifier = Identifier;
|
|
RegisteredSecondaryBrushTypes.Add(TypeInfo);
|
|
|
|
check(SecondaryBrushOpPropSets.Contains(Identifier) == false && SecondaryBrushOpFactories.Contains(Identifier) == false);
|
|
SecondaryBrushOpPropSets.Add(Identifier, PropSet);
|
|
SecondaryBrushOpFactories.Add(Identifier, MoveTemp(Factory));
|
|
|
|
AddToolPropertySource(PropSet);
|
|
SetToolPropertySourceEnabled(PropSet, false);
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::SaveAllBrushTypeProperties(UInteractiveTool* SaveFromTool)
|
|
{
|
|
for (auto Pair : BrushOpPropSets)
|
|
{
|
|
Pair.Value->SaveProperties(SaveFromTool);
|
|
}
|
|
for (auto Pair : SecondaryBrushOpPropSets)
|
|
{
|
|
Pair.Value->SaveProperties(SaveFromTool);
|
|
}
|
|
}
|
|
void UMeshSculptToolBase::RestoreAllBrushTypeProperties(UInteractiveTool* RestoreToTool)
|
|
{
|
|
for (auto Pair : BrushOpPropSets)
|
|
{
|
|
Pair.Value->RestoreProperties(RestoreToTool);
|
|
}
|
|
for (auto Pair : SecondaryBrushOpPropSets)
|
|
{
|
|
Pair.Value->RestoreProperties(RestoreToTool);
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::SetActivePrimaryBrushType(int32 Identifier)
|
|
{
|
|
TUniquePtr<FMeshSculptBrushOpFactory>* Factory = BrushOpFactories.Find(Identifier);
|
|
if (Factory == nullptr)
|
|
{
|
|
check(false);
|
|
return;
|
|
}
|
|
|
|
if (PrimaryVisiblePropSet != nullptr)
|
|
{
|
|
SetToolPropertySourceEnabled(PrimaryVisiblePropSet, false);
|
|
PrimaryVisiblePropSet = nullptr;
|
|
}
|
|
|
|
PrimaryBrushOp = (*Factory)->Build();
|
|
PrimaryBrushOp->Falloff = PrimaryFalloff;
|
|
|
|
TObjectPtr<UMeshSculptBrushOpProps>* FoundProps = BrushOpPropSets.Find(Identifier);
|
|
if (FoundProps != nullptr)
|
|
{
|
|
SetToolPropertySourceEnabled(*FoundProps, bBrushOpPropsVisible);
|
|
PrimaryVisiblePropSet = *FoundProps;
|
|
|
|
PrimaryBrushOp->PropertySet = PrimaryVisiblePropSet;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::SetActiveSecondaryBrushType(int32 Identifier)
|
|
{
|
|
TUniquePtr<FMeshSculptBrushOpFactory>* Factory = SecondaryBrushOpFactories.Find(Identifier);
|
|
if (Factory == nullptr)
|
|
{
|
|
check(false);
|
|
return;
|
|
}
|
|
|
|
if (SecondaryVisiblePropSet != nullptr)
|
|
{
|
|
SetToolPropertySourceEnabled(SecondaryVisiblePropSet, false);
|
|
SecondaryVisiblePropSet = nullptr;
|
|
}
|
|
|
|
SecondaryBrushOp = (*Factory)->Build();
|
|
TSharedPtr<FMeshSculptFallofFunc> SecondaryFalloff = MakeShared<FMeshSculptFallofFunc>();;
|
|
SecondaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeStandardSmoothFalloff();
|
|
SecondaryBrushOp->Falloff = SecondaryFalloff;
|
|
|
|
TObjectPtr<UMeshSculptBrushOpProps>* FoundProps = SecondaryBrushOpPropSets.Find(Identifier);
|
|
if (FoundProps != nullptr)
|
|
{
|
|
SetToolPropertySourceEnabled(*FoundProps, bBrushOpPropsVisible);
|
|
SecondaryVisiblePropSet = *FoundProps;
|
|
|
|
SecondaryBrushOp->PropertySet = SecondaryVisiblePropSet;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TUniquePtr<FMeshSculptBrushOp>& UMeshSculptToolBase::GetActiveBrushOp()
|
|
{
|
|
if (GetInSmoothingStroke())
|
|
{
|
|
return SecondaryBrushOp;
|
|
}
|
|
else
|
|
{
|
|
return PrimaryBrushOp;
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::SetBrushOpPropsVisibility(bool bVisible)
|
|
{
|
|
bBrushOpPropsVisible = bVisible;
|
|
if (PrimaryVisiblePropSet)
|
|
{
|
|
SetToolPropertySourceEnabled(PrimaryVisiblePropSet, bVisible);
|
|
}
|
|
if (SecondaryVisiblePropSet)
|
|
{
|
|
SetToolPropertySourceEnabled(SecondaryVisiblePropSet, bVisible);
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::RegisterStandardFalloffTypes()
|
|
{
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("Smooth", "Smooth"), TEXT("Smooth"), (int32)EMeshSculptFalloffType::Smooth});
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("Linear", "Linear"), TEXT("Linear"), (int32)EMeshSculptFalloffType::Linear } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("Inverse", "Inverse"), TEXT("Inverse"), (int32)EMeshSculptFalloffType::Inverse } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("Round", "Round"), TEXT("Round"), (int32)EMeshSculptFalloffType::Round } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("BoxSmooth", "BoxSmooth"), TEXT("BoxSmooth"), (int32)EMeshSculptFalloffType::BoxSmooth } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("BoxLinear", "BoxLinear"), TEXT("BoxLinear"), (int32)EMeshSculptFalloffType::BoxLinear } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("BoxInverse", "BoxInverse"), TEXT("BoxInverse"), (int32)EMeshSculptFalloffType::BoxInverse } );
|
|
RegisteredPrimaryFalloffTypes.Add( FFalloffTypeInfo{ LOCTEXT("BoxRound", "BoxRound"), TEXT("BoxRound"), (int32)EMeshSculptFalloffType::BoxRound } );
|
|
}
|
|
|
|
void UMeshSculptToolBase::SetPrimaryFalloffType(EMeshSculptFalloffType FalloffType)
|
|
{
|
|
PrimaryFalloff = MakeShared<FMeshSculptFallofFunc>();
|
|
switch (FalloffType)
|
|
{
|
|
default:
|
|
case EMeshSculptFalloffType::Smooth:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeStandardSmoothFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::Linear:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeLinearFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::Inverse:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeInverseFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::Round:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeRoundFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::BoxSmooth:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeSmoothBoxFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::BoxLinear:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeLinearBoxFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::BoxInverse:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeInverseBoxFalloff();
|
|
break;
|
|
case EMeshSculptFalloffType::BoxRound:
|
|
PrimaryFalloff->FalloffFunc = UE::SculptFalloffs::MakeRoundBoxFalloff();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UMeshSculptToolBase::HitTest(const FRay& Ray, FHitResult& OutHit)
|
|
{
|
|
FRay3d LocalRay = GetLocalRay(Ray);
|
|
|
|
int HitTID = FindHitSculptMeshTriangle(LocalRay);
|
|
if (HitTID != IndexConstants::InvalidID)
|
|
{
|
|
FTriangle3d Triangle;
|
|
FDynamicMesh3* Mesh = GetSculptMesh();
|
|
Mesh->GetTriVertices(HitTID, Triangle.V[0], Triangle.V[1], Triangle.V[2]);
|
|
FIntrRay3Triangle3d Query(LocalRay, Triangle);
|
|
Query.Find();
|
|
|
|
OutHit.FaceIndex = HitTID;
|
|
OutHit.Distance = Query.RayParameter;
|
|
OutHit.Normal = (FVector)CurTargetTransform.TransformNormal(Mesh->GetTriNormal(HitTID));
|
|
OutHit.ImpactPoint = (FVector)CurTargetTransform.TransformPosition(LocalRay.PointAt(Query.RayParameter));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::OnBeginDrag(const FRay& WorldRay)
|
|
{
|
|
SaveActiveStrokeModifiers();
|
|
|
|
FHitResult OutHit;
|
|
if (HitTest(WorldRay, OutHit))
|
|
{
|
|
bInStroke = true;
|
|
ResetStrokeTime();
|
|
|
|
UpdateBrushTargetPlaneFromHit(WorldRay, OutHit);
|
|
|
|
// initialize first stamp
|
|
PendingStampRay = WorldRay;
|
|
bIsStampPending = true;
|
|
|
|
// set falloff
|
|
PrimaryBrushOp->Falloff = PrimaryFalloff;
|
|
|
|
OnBeginStroke(WorldRay);
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::OnUpdateDrag(const FRay& WorldRay)
|
|
{
|
|
if (InStroke())
|
|
{
|
|
PendingStampRay = WorldRay;
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::OnEndDrag(const FRay& Ray)
|
|
{
|
|
bInStroke = false;
|
|
|
|
// cancel any outstanding stamps! otherwise change record could become invalid
|
|
bIsStampPending = false;
|
|
|
|
OnEndStroke();
|
|
|
|
}
|
|
|
|
|
|
|
|
FRay3d UMeshSculptToolBase::GetLocalRay(const FRay& WorldRay) const
|
|
{
|
|
FRay3d LocalRay(CurTargetTransform.InverseTransformPosition((FVector3d)WorldRay.Origin),
|
|
CurTargetTransform.InverseTransformVector((FVector3d)WorldRay.Direction));
|
|
UE::Geometry::Normalize(LocalRay.Direction);
|
|
return LocalRay;
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateBrushFrameWorld(const FVector3d& NewPosition, const FVector3d& NewNormal)
|
|
{
|
|
FFrame3d PrevBrushFrameWorld = LastBrushFrameWorld;
|
|
|
|
bool bTriedFrameRepair = false;
|
|
retry_frame_update:
|
|
FFrame3d NewFrame = LastBrushFrameWorld;
|
|
NewFrame.Origin = NewPosition;
|
|
NewFrame.AlignAxis(2, NewNormal);
|
|
FVector3d CameraUp = (FVector3d)CameraState.Up();
|
|
|
|
if (FMathd::Abs(CameraUp.Dot(NewNormal)) < 0.98)
|
|
{
|
|
NewFrame.ConstrainedAlignAxis(1, CameraUp, NewFrame.Z());
|
|
}
|
|
|
|
if ( (NewFrame.Rotation.Length() - 1.0) > 0.1 ) // try to recover from normalization failure
|
|
{
|
|
LastBrushFrameWorld = FFrame3d(LastBrushFrameWorld.Origin);
|
|
if (bTriedFrameRepair == false)
|
|
{
|
|
bTriedFrameRepair = true;
|
|
goto retry_frame_update;
|
|
}
|
|
}
|
|
|
|
if (InStroke() && BrushProperties->Lazyness > 0)
|
|
{
|
|
double t = FMathd::Lerp(1.0, 0.1, (double)BrushProperties->Lazyness);
|
|
LastBrushFrameWorld.Origin = UE::Geometry::Lerp(LastBrushFrameWorld.Origin, NewFrame.Origin, t);
|
|
LastBrushFrameWorld.Rotation = FQuaterniond(LastBrushFrameWorld.Rotation, NewFrame.Rotation, t);
|
|
}
|
|
else
|
|
{
|
|
LastBrushFrameWorld = NewFrame;
|
|
}
|
|
|
|
ActiveStrokePathArcLen += Distance(LastBrushFrameWorld.Origin, PrevBrushFrameWorld.Origin);
|
|
|
|
LastBrushFrameLocal = LastBrushFrameWorld;
|
|
LastBrushFrameLocal.Transform(CurTargetTransform.InverseUnsafe()); // Note: Unsafe inverse used because we cannot handle scales on a frame regardless.
|
|
// TODO: in the case of a non-uniform scale, consider whether we should do additional work to align the Z axis?
|
|
}
|
|
|
|
void UMeshSculptToolBase::AlignBrushToView()
|
|
{
|
|
UpdateBrushFrameWorld(GetBrushFrameWorld().Origin, -(FVector3d)CameraState.Forward());
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateBrushTargetPlaneFromHit(const FRay& WorldRayIn, const FHitResult& Hit)
|
|
{
|
|
FRay3d WorldRay(WorldRayIn);
|
|
FVector3d WorldPosWithBrushDepth = WorldRay.PointAt(Hit.Distance) + GetCurrentBrushDepth() * GetCurrentBrushRadius() * WorldRay.Direction;
|
|
ActiveBrushTargetPlaneWorld = FFrame3d(WorldPosWithBrushDepth, -WorldRay.Direction);
|
|
}
|
|
|
|
bool UMeshSculptToolBase::UpdateBrushPositionOnActivePlane(const FRay& WorldRayIn)
|
|
{
|
|
LastBrushTriangleID = IndexConstants::InvalidID;
|
|
|
|
FRay3d WorldRay(WorldRayIn);
|
|
FVector3d NewHitPosWorld;
|
|
ActiveBrushTargetPlaneWorld.RayPlaneIntersection(WorldRay.Origin, WorldRay.Direction, 2, NewHitPosWorld);
|
|
UpdateBrushFrameWorld(NewHitPosWorld, ActiveBrushTargetPlaneWorld.Z());
|
|
return true;
|
|
}
|
|
|
|
bool UMeshSculptToolBase::UpdateBrushPositionOnTargetMesh(const FRay& WorldRayIn, bool bFallbackToViewPlane)
|
|
{
|
|
LastBrushTriangleID = IndexConstants::InvalidID;
|
|
|
|
FRay3d WorldRay(WorldRayIn);
|
|
FRay3d LocalRay = GetLocalRay(WorldRayIn);
|
|
int32 HitTID = FindHitTargetMeshTriangle(LocalRay);
|
|
if (HitTID != IndexConstants::InvalidID)
|
|
{
|
|
const FDynamicMesh3* BaseMesh = GetBaseMesh();
|
|
FIntrRay3Triangle3d Query = TMeshQueries<FDynamicMesh3>::TriangleIntersection(*BaseMesh, HitTID, LocalRay);
|
|
FVector3d WorldNormal = CurTargetTransform.TransformNormal(BaseMesh->GetTriNormal(HitTID));
|
|
FVector3d WorldPos = CurTargetTransform.TransformPosition(LocalRay.PointAt(Query.RayParameter));
|
|
LastBrushTriangleID = HitTID;
|
|
UpdateBrushFrameWorld(WorldPos, WorldNormal);
|
|
return true;
|
|
}
|
|
|
|
if (bFallbackToViewPlane)
|
|
{
|
|
FFrame3d BrushPlane(GetBrushFrameWorld().Origin, (FVector3d)CameraState.Forward());
|
|
FVector3d NewHitPosWorld;
|
|
BrushPlane.RayPlaneIntersection(WorldRay.Origin, WorldRay.Direction, 2, NewHitPosWorld);
|
|
UpdateBrushFrameWorld(NewHitPosWorld, ActiveBrushTargetPlaneWorld.Z());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UMeshSculptToolBase::UpdateBrushPositionOnSculptMesh(const FRay& WorldRayIn, bool bFallbackToViewPlane)
|
|
{
|
|
LastBrushTriangleID = IndexConstants::InvalidID;
|
|
|
|
FRay3d WorldRay(WorldRayIn);
|
|
FRay3d LocalRay = GetLocalRay(WorldRayIn);
|
|
int32 HitTID = FindHitSculptMeshTriangle(LocalRay);
|
|
if (HitTID != IndexConstants::InvalidID)
|
|
{
|
|
const FDynamicMesh3* SculptMesh = GetSculptMesh();
|
|
FIntrRay3Triangle3d Query = TMeshQueries<FDynamicMesh3>::TriangleIntersection(*SculptMesh, HitTID, LocalRay);
|
|
FVector3d WorldNormal = CurTargetTransform.TransformNormal(SculptMesh->GetTriNormal(HitTID));
|
|
FVector3d WorldPos = CurTargetTransform.TransformPosition(LocalRay.PointAt(Query.RayParameter));
|
|
LastBrushTriangleID = HitTID;
|
|
UpdateBrushFrameWorld(WorldPos, WorldNormal);
|
|
return true;
|
|
}
|
|
|
|
if (bFallbackToViewPlane)
|
|
{
|
|
FFrame3d BrushPlane(GetBrushFrameWorld().Origin, (FVector3d)CameraState.Forward());
|
|
FVector3d NewHitPosWorld;
|
|
BrushPlane.RayPlaneIntersection(WorldRay.Origin, WorldRay.Direction, 2, NewHitPosWorld);
|
|
UpdateBrushFrameWorld(NewHitPosWorld, ActiveBrushTargetPlaneWorld.Z());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::SaveActiveStrokeModifiers()
|
|
{
|
|
bSmoothing = GetShiftToggle();
|
|
bInvert = GetCtrlToggle();
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateHoverStamp(const FFrame3d& StampFrame)
|
|
{
|
|
HoverStamp.WorldFrame = StampFrame;
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateStampPendingState()
|
|
{
|
|
if (InStroke() == false) return;
|
|
|
|
bool bFlowStampPending = false;
|
|
if (BrushProperties->FlowRate >= 1.0)
|
|
{
|
|
bFlowStampPending = true;
|
|
}
|
|
else if (BrushProperties->FlowRate == 0.0)
|
|
{
|
|
bFlowStampPending = (LastFlowTimeStamp++ == 0);
|
|
}
|
|
else
|
|
{
|
|
double dt = (1.0 - BrushProperties->FlowRate);
|
|
int FlowTimestamp = (int)(ActiveStrokeTime / dt);
|
|
if (FlowTimestamp > LastFlowTimeStamp)
|
|
{
|
|
LastFlowTimeStamp = FlowTimestamp;
|
|
bFlowStampPending = true;
|
|
}
|
|
}
|
|
|
|
// if brush does not support variable spacing, we only use flow
|
|
if (GetActiveBrushOp()->SupportsVariableSpacing() == false)
|
|
{
|
|
bIsStampPending = bFlowStampPending;
|
|
return;
|
|
}
|
|
|
|
bool bSpacingStampPending = false;
|
|
if (BrushProperties->Spacing == 0)
|
|
{
|
|
bSpacingStampPending = true;
|
|
}
|
|
else
|
|
{
|
|
double ArcSpacing = BrushProperties->Spacing * (2 * GetCurrentBrushRadius());
|
|
int SpacingTimestamp = 1 + (int)(ActiveStrokePathArcLen / ArcSpacing);
|
|
if (SpacingTimestamp > LastSpacingTimestamp)
|
|
{
|
|
LastSpacingTimestamp = SpacingTimestamp;
|
|
bSpacingStampPending = true;
|
|
}
|
|
}
|
|
|
|
bIsStampPending = bFlowStampPending && bSpacingStampPending;
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::ResetStrokeTime()
|
|
{
|
|
ActiveStrokeTime = 0.0;
|
|
LastFlowTimeStamp = 0;
|
|
ActiveStrokePathArcLen = 0;
|
|
LastSpacingTimestamp = 0;
|
|
}
|
|
|
|
void UMeshSculptToolBase::AccumulateStrokeTime(float DeltaTime)
|
|
{
|
|
ActiveStrokeTime += DeltaTime;
|
|
}
|
|
|
|
|
|
FFrame3d UMeshSculptToolBase::ComputeStampRegionPlane(const FFrame3d& StampFrame, const TSet<int32>& StampTriangles, bool bIgnoreDepth, bool bViewAligned, bool bInvDistFalloff)
|
|
{
|
|
check(false);
|
|
return FFrame3d();
|
|
}
|
|
|
|
FFrame3d UMeshSculptToolBase::ComputeStampRegionPlane(const FFrame3d& StampFrame, const TArray<int32>& StampTriangles, bool bIgnoreDepth, bool bViewAligned, bool bInvDistFalloff)
|
|
{
|
|
const FDynamicMesh3* Mesh = GetSculptMesh();
|
|
double FalloffRadius = GetCurrentBrushRadius();
|
|
if (bInvDistFalloff)
|
|
{
|
|
FalloffRadius *= 0.5;
|
|
}
|
|
FVector3d StampNormal = StampFrame.Z();
|
|
|
|
FVector3d AverageNormal(0, 0, 0);
|
|
FVector3d AveragePos(0, 0, 0);
|
|
double WeightSum = 0;
|
|
for (int TriID : StampTriangles)
|
|
{
|
|
FVector3d Normal, Centroid; double Area;
|
|
Mesh->GetTriInfo(TriID, Normal, Area, Centroid);
|
|
if (Normal.Dot(StampNormal) < -0.2) // ignore back-facing (heuristic to avoid "other side")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
double Distance = UE::Geometry::Distance(StampFrame.Origin, Centroid);
|
|
double NormalizedDistance = (Distance / FalloffRadius) + 0.0001;
|
|
|
|
double Weight = Area;
|
|
if (bInvDistFalloff)
|
|
{
|
|
double RampT = FMathd::Clamp(1.0 - NormalizedDistance, 0.0, 1.0);
|
|
Weight *= FMathd::Clamp(RampT * RampT * RampT, 0.0, 1.0);
|
|
}
|
|
else
|
|
{
|
|
if (NormalizedDistance > 0.5)
|
|
{
|
|
double d = FMathd::Clamp((NormalizedDistance - 0.5) / (1.0 - 0.5), 0.0, 1.0);
|
|
double t = (1.0 - d * d);
|
|
Weight *= (t * t * t);
|
|
}
|
|
}
|
|
|
|
AverageNormal += Weight * Mesh->GetTriNormal(TriID);
|
|
AveragePos += Weight * Centroid;
|
|
WeightSum += Weight;
|
|
}
|
|
UE::Geometry::Normalize(AverageNormal);
|
|
AveragePos /= WeightSum;
|
|
|
|
if (bViewAligned)
|
|
{
|
|
AverageNormal = -(FVector3d)CameraState.Forward();
|
|
}
|
|
|
|
FFrame3d Result = FFrame3d(AveragePos, AverageNormal);
|
|
if (bIgnoreDepth == false)
|
|
{
|
|
Result.Origin -= GetCurrentBrushDepth() * GetCurrentBrushRadius() * Result.Z();
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateStrokeReferencePlaneForROI(const FFrame3d& StampFrame, const TArray<int32>& TriangleROI, bool bViewAligned)
|
|
{
|
|
StrokePlane = ComputeStampRegionPlane(GetBrushFrameLocal(), TriangleROI, false, bViewAligned);
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateStrokeReferencePlaneFromWorkPlane()
|
|
{
|
|
StrokePlane = FFrame3d(
|
|
CurTargetTransform.InverseTransformPosition((FVector3d)GizmoProperties->Position),
|
|
CurTargetTransform.GetRotation().Inverse() * (FQuaterniond)GizmoProperties->Rotation);
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::InitializeBrushSizeRange(const FAxisAlignedBox3d& TargetBounds)
|
|
{
|
|
double MaxDimension = TargetBounds.MaxDim();
|
|
BrushRelativeSizeRange = FInterval1d(MaxDimension * 0.01, MaxDimension);
|
|
BrushProperties->BrushSize.InitializeWorldSizeRange(
|
|
TInterval<float>((float)BrushRelativeSizeRange.Min, (float)BrushRelativeSizeRange.Max));
|
|
CalculateBrushRadius();
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::CalculateBrushRadius()
|
|
{
|
|
CurrentBrushRadius = BrushProperties->BrushSize.GetWorldRadius();
|
|
}
|
|
|
|
|
|
|
|
double UMeshSculptToolBase::GetCurrentBrushStrength()
|
|
{
|
|
TUniquePtr<FMeshSculptBrushOp>& BrushOp = GetActiveBrushOp();
|
|
if (BrushOp->PropertySet.IsValid())
|
|
{
|
|
return FMathd::Clamp(BrushOp->PropertySet->GetStrength(), 0.0, 1.0);
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
double UMeshSculptToolBase::GetCurrentBrushDepth()
|
|
{
|
|
TUniquePtr<FMeshSculptBrushOp>& BrushOp = GetActiveBrushOp();
|
|
if (BrushOp->PropertySet.IsValid())
|
|
{
|
|
return FMathd::Clamp(BrushOp->PropertySet->GetDepth(), -1.0, 1.0);
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::IncreaseBrushRadiusAction()
|
|
{
|
|
BrushProperties->BrushSize.IncreaseRadius(false);
|
|
NotifyOfPropertyChangeByTool(BrushProperties);
|
|
CalculateBrushRadius();
|
|
}
|
|
|
|
void UMeshSculptToolBase::DecreaseBrushRadiusAction()
|
|
{
|
|
BrushProperties->BrushSize.DecreaseRadius(false);
|
|
NotifyOfPropertyChangeByTool(BrushProperties);
|
|
CalculateBrushRadius();
|
|
}
|
|
|
|
void UMeshSculptToolBase::IncreaseBrushRadiusSmallStepAction()
|
|
{
|
|
BrushProperties->BrushSize.IncreaseRadius(true);
|
|
NotifyOfPropertyChangeByTool(BrushProperties);
|
|
CalculateBrushRadius();
|
|
}
|
|
|
|
void UMeshSculptToolBase::DecreaseBrushRadiusSmallStepAction()
|
|
{
|
|
BrushProperties->BrushSize.DecreaseRadius(true);
|
|
NotifyOfPropertyChangeByTool(BrushProperties);
|
|
CalculateBrushRadius();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::SetViewPropertiesEnabled(bool bNewValue)
|
|
{
|
|
if (ViewProperties)
|
|
{
|
|
SetToolPropertySourceEnabled(ViewProperties, bNewValue);
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateWireframeVisibility(bool bNewValue)
|
|
{
|
|
GetSculptMeshComponent()->SetEnableWireframeRenderPass(bNewValue);
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateFlatShadingSetting(bool bNewValue)
|
|
{
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetScalarParameterValue(TEXT("FlatShading"), (bNewValue) ? 1.0f : 0.0f);
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateColorSetting(FLinearColor NewColor)
|
|
{
|
|
if (ViewProperties->MaterialMode != EMeshEditingMaterialModes::Transparent)
|
|
{
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetVectorParameterValue(TEXT("Color"), NewColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateTransparentColorSetting(FLinearColor NewColor)
|
|
{
|
|
// only want to update the active material if it is the transparent one...
|
|
if (ViewProperties->MaterialMode == EMeshEditingMaterialModes::Transparent)
|
|
{
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetVectorParameterValue(TEXT("Color"), NewColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateImageSetting(UTexture2D* NewImage)
|
|
{
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetTextureParameterValue(TEXT("ImageTexture"), NewImage);
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateOpacitySetting(double Opacity)
|
|
{
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetScalarParameterValue(TEXT("Opacity"), Opacity);
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateTwoSidedSetting(bool bOn)
|
|
{
|
|
if (ViewProperties->MaterialMode == EMeshEditingMaterialModes::Transparent)
|
|
{
|
|
ActiveOverrideMaterial = ToolSetupUtil::GetTransparentSculptMaterial(GetToolManager(),
|
|
ViewProperties->TransparentMaterialColor, ViewProperties->Opacity, bOn);
|
|
if (ActiveOverrideMaterial)
|
|
{
|
|
GetSculptMeshComponent()->SetOverrideRenderMaterial(ActiveOverrideMaterial);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateCustomMaterial(TWeakObjectPtr<UMaterialInterface> NewMaterial)
|
|
{
|
|
if (ViewProperties->MaterialMode == EMeshEditingMaterialModes::Custom)
|
|
{
|
|
if (NewMaterial.IsValid())
|
|
{
|
|
ActiveOverrideMaterial = UMaterialInstanceDynamic::Create(NewMaterial.Get(), this);
|
|
GetSculptMeshComponent()->SetOverrideRenderMaterial(ActiveOverrideMaterial);
|
|
}
|
|
else
|
|
{
|
|
GetSculptMeshComponent()->ClearOverrideRenderMaterial();
|
|
ActiveOverrideMaterial = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateMaterialMode(EMeshEditingMaterialModes MaterialMode)
|
|
{
|
|
if (MaterialMode == EMeshEditingMaterialModes::ExistingMaterial)
|
|
{
|
|
GetSculptMeshComponent()->ClearOverrideRenderMaterial();
|
|
GetSculptMeshComponent()->SetShadowsEnabled(UE::ToolTarget::GetTargetComponent(Target)->bCastDynamicShadow);
|
|
ActiveOverrideMaterial = nullptr;
|
|
}
|
|
else
|
|
{
|
|
if (MaterialMode == EMeshEditingMaterialModes::Custom)
|
|
{
|
|
if (ViewProperties->CustomMaterial.IsValid())
|
|
{
|
|
ActiveOverrideMaterial = UMaterialInstanceDynamic::Create(ViewProperties->CustomMaterial.Get(), this);
|
|
}
|
|
else
|
|
{
|
|
GetSculptMeshComponent()->ClearOverrideRenderMaterial();
|
|
ActiveOverrideMaterial = nullptr;
|
|
}
|
|
}
|
|
else if (MaterialMode == EMeshEditingMaterialModes::CustomImage)
|
|
{
|
|
ActiveOverrideMaterial = ToolSetupUtil::GetCustomImageBasedSculptMaterial(GetToolManager(), ViewProperties->Image);
|
|
if (ViewProperties->Image != nullptr)
|
|
{
|
|
ActiveOverrideMaterial->SetTextureParameterValue(TEXT("ImageTexture"), ViewProperties->Image);
|
|
}
|
|
}
|
|
else if (MaterialMode == EMeshEditingMaterialModes::VertexColor)
|
|
{
|
|
ActiveOverrideMaterial = ToolSetupUtil::GetVertexColorMaterial(GetToolManager());
|
|
}
|
|
else if (MaterialMode == EMeshEditingMaterialModes::Transparent)
|
|
{
|
|
ActiveOverrideMaterial = ToolSetupUtil::GetTransparentSculptMaterial(GetToolManager(),
|
|
ViewProperties->TransparentMaterialColor, ViewProperties->Opacity, ViewProperties->bTwoSided);
|
|
}
|
|
else
|
|
{
|
|
UMaterialInterface* SculptMaterial = nullptr;
|
|
switch (MaterialMode)
|
|
{
|
|
case EMeshEditingMaterialModes::Diffuse:
|
|
SculptMaterial = ToolSetupUtil::GetDefaultSculptMaterial(GetToolManager());
|
|
break;
|
|
case EMeshEditingMaterialModes::Grey:
|
|
SculptMaterial = ToolSetupUtil::GetImageBasedSculptMaterial(GetToolManager(), ToolSetupUtil::ImageMaterialType::DefaultBasic);
|
|
break;
|
|
case EMeshEditingMaterialModes::Soft:
|
|
SculptMaterial = ToolSetupUtil::GetImageBasedSculptMaterial(GetToolManager(), ToolSetupUtil::ImageMaterialType::DefaultSoft);
|
|
break;
|
|
case EMeshEditingMaterialModes::TangentNormal:
|
|
SculptMaterial = ToolSetupUtil::GetImageBasedSculptMaterial(GetToolManager(), ToolSetupUtil::ImageMaterialType::TangentNormalFromView);
|
|
break;
|
|
}
|
|
if (SculptMaterial != nullptr )
|
|
{
|
|
ActiveOverrideMaterial = UMaterialInstanceDynamic::Create(SculptMaterial, this);
|
|
}
|
|
}
|
|
|
|
if (ActiveOverrideMaterial != nullptr)
|
|
{
|
|
GetSculptMeshComponent()->SetOverrideRenderMaterial(ActiveOverrideMaterial);
|
|
ActiveOverrideMaterial->SetScalarParameterValue(TEXT("FlatShading"), (ViewProperties->bFlatShading) ? 1.0f : 0.0f);
|
|
}
|
|
|
|
GetSculptMeshComponent()->SetShadowsEnabled(false);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::InitializeIndicator()
|
|
{
|
|
// register and spawn brush indicator gizmo
|
|
GetToolManager()->GetPairedGizmoManager()->RegisterGizmoType(VertexSculptIndicatorGizmoType, NewObject<UBrushStampIndicatorBuilder>());
|
|
BrushIndicator = GetToolManager()->GetPairedGizmoManager()->CreateGizmo<UBrushStampIndicator>(VertexSculptIndicatorGizmoType, FString(), this);
|
|
BrushIndicatorMesh = MakeBrushIndicatorMesh(this, TargetWorld);
|
|
BrushIndicator->AttachedComponent = BrushIndicatorMesh->GetRootComponent();
|
|
BrushIndicator->LineThickness = 1.0;
|
|
BrushIndicator->bDrawIndicatorLines = true;
|
|
BrushIndicator->bDrawRadiusCircle = false;
|
|
BrushIndicator->LineColor = FLinearColor(0.9f, 0.4f, 0.4f);
|
|
|
|
bIsVolumetricIndicator = true;
|
|
}
|
|
|
|
bool UMeshSculptToolBase::GetIsVolumetricIndicator()
|
|
{
|
|
return bIsVolumetricIndicator;
|
|
}
|
|
|
|
void UMeshSculptToolBase::ConfigureIndicator(bool bVolumetric)
|
|
{
|
|
if (bIsVolumetricIndicator == bVolumetric) return;
|
|
bIsVolumetricIndicator = bVolumetric;
|
|
BrushIndicatorMesh->SetVisible(GetIndicatorVisibility() && bIsVolumetricIndicator);
|
|
if (bVolumetric)
|
|
{
|
|
BrushIndicator->bDrawRadiusCircle = false;
|
|
}
|
|
else
|
|
{
|
|
BrushIndicator->bDrawRadiusCircle = true;
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::SetIndicatorVisibility(bool bVisible)
|
|
{
|
|
if (GetIndicatorVisibility() != bVisible)
|
|
{
|
|
BrushIndicator->bVisible = bVisible;
|
|
BrushIndicatorMesh->SetVisible(bVisible && bIsVolumetricIndicator);
|
|
}
|
|
}
|
|
|
|
bool UMeshSculptToolBase::GetIndicatorVisibility() const
|
|
{
|
|
return BrushIndicator->bVisible;
|
|
}
|
|
|
|
UPreviewMesh* UMeshSculptToolBase::MakeBrushIndicatorMesh(UObject* Parent, UWorld* World)
|
|
{
|
|
UPreviewMesh* SphereMesh = NewObject<UPreviewMesh>(Parent);
|
|
SphereMesh->CreateInWorld(World, FTransform::Identity);
|
|
FSphereGenerator SphereGen;
|
|
SphereGen.NumPhi = SphereGen.NumTheta = 32;
|
|
SphereGen.Generate();
|
|
FDynamicMesh3 Mesh(&SphereGen);
|
|
SphereMesh->UpdatePreview(&Mesh);
|
|
|
|
BrushIndicatorMaterial = ToolSetupUtil::GetDefaultBrushVolumeMaterial(GetToolManager());
|
|
if (BrushIndicatorMaterial)
|
|
{
|
|
SphereMesh->SetMaterial(BrushIndicatorMaterial);
|
|
}
|
|
|
|
return SphereMesh;
|
|
}
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::UpdateWorkPlane()
|
|
{
|
|
bool bGizmoVisible = ShowWorkPlane() && (GizmoProperties->bShowGizmo);
|
|
UpdateFixedPlaneGizmoVisibility(bGizmoVisible);
|
|
GizmoProperties->bPropertySetEnabled = ShowWorkPlane();
|
|
|
|
if (PendingWorkPlaneUpdate != EPendingWorkPlaneUpdate::NoUpdatePending)
|
|
{
|
|
// raycast into scene and current sculpt and place plane at closest hit point
|
|
FRay CursorWorldRay = UMeshSurfacePointTool::LastWorldRay;
|
|
FHitResult Result;
|
|
bool bWorldHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, CursorWorldRay);
|
|
FRay3d LocalRay = GetLocalRay(CursorWorldRay);
|
|
bool bObjectHit = ( FindHitSculptMeshTriangle(LocalRay) != IndexConstants::InvalidID );
|
|
if (bWorldHit &&
|
|
(bObjectHit == false || (CursorWorldRay.GetParameter(Result.ImpactPoint) < CursorWorldRay.GetParameter((FVector)HoverStamp.WorldFrame.Origin))))
|
|
{
|
|
SetFixedSculptPlaneFromWorldPos(Result.ImpactPoint, Result.ImpactNormal, PendingWorkPlaneUpdate);
|
|
}
|
|
else
|
|
{
|
|
SetFixedSculptPlaneFromWorldPos((FVector)HoverStamp.WorldFrame.Origin, (FVector)HoverStamp.WorldFrame.Z(), PendingWorkPlaneUpdate);
|
|
}
|
|
PendingWorkPlaneUpdate = EPendingWorkPlaneUpdate::NoUpdatePending;
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshSculptToolBase::SetFixedSculptPlaneFromWorldPos(const FVector& Position, const FVector& Normal, EPendingWorkPlaneUpdate UpdateType)
|
|
{
|
|
if (UpdateType == EPendingWorkPlaneUpdate::MoveToHitPositionNormal)
|
|
{
|
|
UpdateFixedSculptPlanePosition(Position);
|
|
FFrame3d CurFrame(FVector::ZeroVector, GizmoProperties->Rotation);
|
|
CurFrame.AlignAxis(2, (FVector3d)Normal);
|
|
UpdateFixedSculptPlaneRotation((FQuat)CurFrame.Rotation);
|
|
}
|
|
else if (UpdateType == EPendingWorkPlaneUpdate::MoveToHitPositionViewAligned)
|
|
{
|
|
UpdateFixedSculptPlanePosition(Position);
|
|
FFrame3d CurFrame(FVector::ZeroVector, GizmoProperties->Rotation);
|
|
CurFrame.AlignAxis(2, -(FVector3d)CameraState.Forward());
|
|
UpdateFixedSculptPlaneRotation((FQuat)CurFrame.Rotation);
|
|
}
|
|
else
|
|
{
|
|
UpdateFixedSculptPlanePosition(Position);
|
|
}
|
|
|
|
if (PlaneTransformGizmo != nullptr)
|
|
{
|
|
PlaneTransformGizmo->SetNewGizmoTransform(FTransform(GizmoProperties->Rotation, GizmoProperties->Position));
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::PlaneTransformChanged(UTransformProxy* Proxy, FTransform Transform)
|
|
{
|
|
UpdateFixedSculptPlaneRotation(Transform.GetRotation());
|
|
UpdateFixedSculptPlanePosition(Transform.GetLocation());
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateFixedSculptPlanePosition(const FVector& Position)
|
|
{
|
|
GizmoProperties->Position = Position;
|
|
GizmoPositionWatcher.SilentUpdate();
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateFixedSculptPlaneRotation(const FQuat& Rotation)
|
|
{
|
|
GizmoProperties->Rotation = Rotation;
|
|
GizmoRotationWatcher.SilentUpdate();
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateGizmoFromProperties()
|
|
{
|
|
if (PlaneTransformGizmo != nullptr)
|
|
{
|
|
PlaneTransformGizmo->SetNewGizmoTransform(FTransform(GizmoProperties->Rotation, GizmoProperties->Position));
|
|
}
|
|
}
|
|
|
|
void UMeshSculptToolBase::UpdateFixedPlaneGizmoVisibility(bool bVisible)
|
|
{
|
|
if (bVisible == false)
|
|
{
|
|
if (PlaneTransformGizmo != nullptr)
|
|
{
|
|
GetToolManager()->GetPairedGizmoManager()->DestroyGizmo(PlaneTransformGizmo);
|
|
PlaneTransformGizmo = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PlaneTransformGizmo == nullptr)
|
|
{
|
|
PlaneTransformGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GetToolManager(),
|
|
ETransformGizmoSubElements::StandardTranslateRotate, this);
|
|
PlaneTransformGizmo->bUseContextCoordinateSystem = false;
|
|
PlaneTransformGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local;
|
|
PlaneTransformGizmo->SetActiveTarget(PlaneTransformProxy, GetToolManager());
|
|
PlaneTransformGizmo->ReinitializeGizmoTransform(FTransform(GizmoProperties->Rotation, GizmoProperties->Position));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void UMeshSculptToolBase::RegisterActions(FInteractiveToolActionSet& ActionSet)
|
|
{
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 1,
|
|
TEXT("NextBrushMode"),
|
|
LOCTEXT("SculptNextBrushMode", "Next Brush Type"),
|
|
LOCTEXT("SculptNextBrushModeTooltip", "Cycle to next Brush Type"),
|
|
EModifierKey::None, EKeys::A,
|
|
[this]() { NextBrushModeAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 2,
|
|
TEXT("PreviousBrushMode"),
|
|
LOCTEXT("SculptPreviousBrushMode", "Previous Brush Type"),
|
|
LOCTEXT("SculptPreviousBrushModeTooltip", "Cycle to previous Brush Type"),
|
|
EModifierKey::None, EKeys::Q,
|
|
[this]() { PreviousBrushModeAction(); });
|
|
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 60,
|
|
TEXT("SculptIncreaseSpeed"),
|
|
LOCTEXT("SculptIncreaseSpeed", "Increase Speed"),
|
|
LOCTEXT("SculptIncreaseSpeedTooltip", "Increase Brush Speed"),
|
|
EModifierKey::None, EKeys::E,
|
|
[this]() { IncreaseBrushSpeedAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 61,
|
|
TEXT("SculptDecreaseSpeed"),
|
|
LOCTEXT("SculptDecreaseSpeed", "Decrease Speed"),
|
|
LOCTEXT("SculptDecreaseSpeedTooltip", "Decrease Brush Speed"),
|
|
EModifierKey::None, EKeys::W,
|
|
[this]() { DecreaseBrushSpeedAction(); });
|
|
|
|
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 50,
|
|
TEXT("SculptIncreaseSize"),
|
|
LOCTEXT("SculptIncreaseSize", "Increase Size"),
|
|
LOCTEXT("SculptIncreaseSizeTooltip", "Increase Brush Size"),
|
|
EModifierKey::None, EKeys::D,
|
|
[this]() { IncreaseBrushRadiusAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 51,
|
|
TEXT("SculptDecreaseSize"),
|
|
LOCTEXT("SculptDecreaseSize", "Decrease Size"),
|
|
LOCTEXT("SculptDecreaseSizeTooltip", "Decrease Brush Size"),
|
|
EModifierKey::None, EKeys::S,
|
|
[this]() { DecreaseBrushRadiusAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 52,
|
|
TEXT("SculptIncreaseSizeSmallStep"),
|
|
LOCTEXT("SculptIncreaseSize", "Increase Size"),
|
|
LOCTEXT("SculptIncreaseSizeTooltip", "Increase Brush Size"),
|
|
EModifierKey::Shift, EKeys::D,
|
|
[this]() { IncreaseBrushRadiusSmallStepAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 53,
|
|
TEXT("SculptDecreaseSizeSmallStemp"),
|
|
LOCTEXT("SculptDecreaseSize", "Decrease Size"),
|
|
LOCTEXT("SculptDecreaseSizeTooltip", "Decrease Brush Size"),
|
|
EModifierKey::Shift, EKeys::S,
|
|
[this]() { DecreaseBrushRadiusSmallStepAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::IncreaseBrushSize,
|
|
TEXT("SculptIncreaseRadius"),
|
|
LOCTEXT("SculptIncreaseRadius", "Increase Radius"),
|
|
LOCTEXT("SculptIncreaseRadiusTooltip", "Increase Brush Radius"),
|
|
EModifierKey::None, EKeys::RightBracket,
|
|
[this]() { IncreaseBrushRadiusAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::DecreaseBrushSize,
|
|
TEXT("SculptDecreaseRadius"),
|
|
LOCTEXT("SculptDecreaseRadius", "Decrease Radius"),
|
|
LOCTEXT("SculptDecreaseRadiusTooltip", "Decrease Brush Radius"),
|
|
EModifierKey::None, EKeys::LeftBracket,
|
|
[this]() { DecreaseBrushRadiusAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 54,
|
|
TEXT("SculptIncreaseRadiusSmallStep"),
|
|
LOCTEXT("SculptIncreaseRadius", "Increase Radius"),
|
|
LOCTEXT("SculptIncreaseRadiusTooltip", "Increase Brush Radius"),
|
|
EModifierKey::Shift, EKeys::RightBracket,
|
|
[this]() { IncreaseBrushRadiusSmallStepAction(); });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 55,
|
|
TEXT("SculptDecreaseRadiusSmallStemp"),
|
|
LOCTEXT("SculptDecreaseRadius", "Decrease Radius"),
|
|
LOCTEXT("SculptDecreaseRadiusTooltip", "Decrease Brush Radius"),
|
|
EModifierKey::Shift, EKeys::LeftBracket,
|
|
[this]() { DecreaseBrushRadiusSmallStepAction(); });
|
|
|
|
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::ToggleWireframe,
|
|
TEXT("ToggleWireframe"),
|
|
LOCTEXT("ToggleWireframe", "Toggle Wireframe"),
|
|
LOCTEXT("ToggleWireframeTooltip", "Toggle visibility of wireframe overlay"),
|
|
EModifierKey::Alt, EKeys::W,
|
|
[this]() { ViewProperties->bShowWireframe = !ViewProperties->bShowWireframe; });
|
|
|
|
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 100,
|
|
TEXT("SetSculptWorkSurfacePosNormal"),
|
|
LOCTEXT("SetSculptWorkSurfacePosNormal", "Reorient Work Surface"),
|
|
LOCTEXT("SetSculptWorkSurfacePosNormalTooltip", "Move the Sculpting Work Plane/Surface to Position and Normal of World hit point under cursor"),
|
|
EModifierKey::Shift, EKeys::T,
|
|
[this]() { PendingWorkPlaneUpdate = EPendingWorkPlaneUpdate::MoveToHitPositionNormal; });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 101,
|
|
TEXT("SetSculptWorkSurfacePos"),
|
|
LOCTEXT("SetSculptWorkSurfacePos", "Reposition Work Surface"),
|
|
LOCTEXT("SetSculptWorkSurfacePosTooltip", "Move the Sculpting Work Plane/Surface to World hit point under cursor (keep current Orientation)"),
|
|
EModifierKey::None, EKeys::T,
|
|
[this]() { PendingWorkPlaneUpdate = EPendingWorkPlaneUpdate::MoveToHitPosition; });
|
|
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 102,
|
|
TEXT("SetSculptWorkSurfaceView"),
|
|
LOCTEXT("SetSculptWorkSurfaceView", "View-Align Work Surface"),
|
|
LOCTEXT("SetSculptWorkSurfaceViewTooltip", "Move the Sculpting Work Plane/Surface to World hit point under cursor and align to View"),
|
|
EModifierKey::Control | EModifierKey::Shift, EKeys::T,
|
|
[this]() { PendingWorkPlaneUpdate = EPendingWorkPlaneUpdate::MoveToHitPositionViewAligned; });
|
|
}
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|