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

275 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "IMeshPainter.h"
#include "Editor.h"
#include "IMeshPaintGeometryAdapter.h"
#include "MeshPaintSettings.h"
#include "MeshPainterCommands.h"
#include "MeshPaintHelpers.h"
#include "ScopedTransaction.h"
#include "Components/MeshComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UICommandList.h"
#include "EditorViewportClient.h"
IMeshPainter::IMeshPainter() :
bArePainting(false),
TimeSinceStartedPainting(0.0f),
Time(0.0f),
WidgetLineThickness(1.0f),
VertexPointColor(FLinearColor::White),
HoverVertexPointColor(0.3f, 1.0f, 0.3f),
PaintTransaction(nullptr)
{
}
IMeshPainter::~IMeshPainter()
{
UICommandList.Reset();
}
void IMeshPainter::RenderInteractors(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI, bool bRenderVertices, ESceneDepthPriorityGroup DepthGroup/* = SDPG_World*/)
{
TArray<MeshPaintHelpers::FPaintRay> PaintRays;
MeshPaintHelpers::RetrieveViewportPaintRays(View, Viewport, PDI, PaintRays);
// Apply paint pressure and start painting (or if not currently painting, draw a preview of where paint will be applied)
for (const MeshPaintHelpers::FPaintRay& PaintRay : PaintRays)
{
EMeshPaintAction RayPaintAction = (Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl)) ? EMeshPaintAction::Erase : EMeshPaintAction::Paint;
RenderInteractorWidget(PaintRay.CameraLocation, PaintRay.RayStart, PaintRay.RayDirection, PDI, RayPaintAction, bRenderVertices, DepthGroup);
}
}
void IMeshPainter::Tick(FEditorViewportClient* ViewportClient, float DeltaTime)
{
if (bArePainting)
{
TimeSinceStartedPainting += DeltaTime;
}
if (ViewportClient->IsPerspective())
{
// Make sure perspective viewports are still set to real-time
MeshPaintHelpers::SetRealtimeViewport(true);
// Set viewport show flags
MeshPaintHelpers::SetViewportColorMode(GetBrushSettings()->ColorViewMode, ViewportClient);
}
Time += DeltaTime;
}
void IMeshPainter::ChangeBrushRadius(float Multiplier)
{
const float ChangePercentage = 0.05f;
const float OldValue = GetBrushSettings()->GetBrushRadius();
float NewValue = OldValue * (1 + ChangePercentage * Multiplier);
GetBrushSettings()->SetBrushRadius(NewValue);
}
void IMeshPainter::ChangeBrushStrength(float Multiplier)
{
const float ChangeAmount = 0.02f;
const float OldValue = GetBrushSettings()->GetBrushStrength();
float NewValue = OldValue + ChangeAmount * Multiplier;
GetBrushSettings()->SetBrushStrength(NewValue);
}
void IMeshPainter::ChangeBrushFalloff(float Multiplier)
{
const float ChangeAmount = 0.02f;
const float OldValue = GetBrushSettings()->GetBrushFalloff();
float NewValue = OldValue + ChangeAmount * Multiplier;
GetBrushSettings()->SetBrushFalloff(NewValue);
}
void IMeshPainter::RegisterCommands(TSharedRef<FUICommandList> CommandList)
{
const FMeshPainterCommands& Commands = FMeshPainterCommands::Get();
CommandList->MapAction(Commands.IncreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
CommandList->MapAction(Commands.DecreaseBrushRadius, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushRadius, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
CommandList->MapAction(Commands.IncreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
CommandList->MapAction(Commands.DecreaseBrushStrength, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushStrength, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
CommandList->MapAction(Commands.IncreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, 1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
CommandList->MapAction(Commands.DecreaseBrushFalloff, FExecuteAction::CreateRaw(this, &IMeshPainter::ChangeBrushFalloff, -1.0f), FCanExecuteAction(), EUIActionRepeatMode::RepeatEnabled);
UICommandList = CommandList;
}
void IMeshPainter::UnregisterCommands(TSharedRef<FUICommandList> CommandList)
{
UICommandList.Reset();
const FMeshPainterCommands& Commands = FMeshPainterCommands::Get();
for (const TSharedPtr<const FUICommandInfo> Action : Commands.Commands)
{
CommandList->UnmapAction(Action);
}
}
bool IMeshPainter::Paint(FViewport* Viewport, const FVector& InCameraOrigin, const FVector& InRayOrigin, const FVector& InRayDirection)
{
// Determine paint action according to whether or not shift is held down
const EMeshPaintAction PaintAction = (Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift)) ? EMeshPaintAction::Erase : EMeshPaintAction::Paint;
const float PaintStrength = Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f;
// Handle internal painting functionality
TPair<FVector, FVector> Ray(InRayOrigin, InRayDirection);
return PaintInternal(InCameraOrigin, MakeArrayView(&Ray, 1), PaintAction, PaintStrength);
}
bool IMeshPainter::Paint(FViewport* Viewport, const FVector& InCameraOrigin, const TArrayView<TPair<FVector, FVector>>& Rays)
{
// Determine paint action according to whether or not shift is held down
const EMeshPaintAction PaintAction = (Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift)) ? EMeshPaintAction::Erase : EMeshPaintAction::Paint;
const float PaintStrength = Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f;
// Handle internal painting functionality
return PaintInternal(InCameraOrigin, Rays, PaintAction, PaintStrength);
}
bool IMeshPainter::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent)
{
bool bHandled = false;
if (UICommandList.IsValid())
{
bHandled = UICommandList->ProcessCommandBindings(InKey, FSlateApplication::Get().GetModifierKeys(), InEvent == IE_Repeat);
}
return bHandled;
}
void IMeshPainter::FinishPainting()
{
if (bArePainting)
{
bArePainting = false;
if (PaintTransaction)
{
EndTransaction();
}
}
}
void IMeshPainter::RenderInteractorWidget(const FVector& InCameraOrigin, const FVector& InRayOrigin, const FVector& InRayDirection, FPrimitiveDrawInterface* PDI, EMeshPaintAction PaintAction, bool bRenderVertices, ESceneDepthPriorityGroup DepthGroup /*= SDPG_World*/)
{
const UPaintBrushSettings* BrushSettings = GetBrushSettings();
const float BrushRadius = BrushSettings->GetBrushRadius();
const FHitResult& HitResult = GetHitResult(InRayOrigin, InRayDirection);
const float DrawThickness = WidgetLineThickness;
const float DrawIntensity = 1.0f;
if (HitResult.Component != nullptr)
{
// Brush properties
const float BrushFalloffAmount = BrushSettings->BrushFalloffAmount;
// Display settings
const float VisualBiasDistance = 0.15f;
const float NormalLineSize(BrushRadius * 0.35f); // Make the normal line length a function of brush size
const FLinearColor NormalLineColor(0.3f, 1.0f, 0.3f);
const FLinearColor BrushCueColor = (bArePainting ? FLinearColor(1.0f, 1.0f, 0.3f) : FLinearColor(0.3f, 1.0f, 0.3f));
const FLinearColor InnerBrushCueColor = (bArePainting ? FLinearColor(0.5f, 0.5f, 0.1f) : FLinearColor(0.1f, 0.5f, 0.1f));
const UMeshPaintSettings* const PaintSettings = GetPainterSettings();
const float PointDrawSize = PaintSettings ? PaintSettings->VertexPreviewSize : GetDefault<UMeshPaintSettings>()->VertexPreviewSize;
FVector BrushXAxis, BrushYAxis;
HitResult.Normal.FindBestAxisVectors(BrushXAxis, BrushYAxis);
const FVector BrushVisualPosition = HitResult.Location + HitResult.Normal * VisualBiasDistance;
if (PDI != NULL)
{
// Draw brush circle
const int32 NumCircleSides = 128;
DrawCircle(PDI, BrushVisualPosition, BrushXAxis, BrushYAxis, BrushCueColor, BrushRadius, NumCircleSides, DepthGroup, DrawThickness);
// Also draw the inner brush radius
const float InnerBrushRadius = (BrushRadius * BrushFalloffAmount);
DrawCircle(PDI, BrushVisualPosition, BrushXAxis, BrushYAxis, InnerBrushCueColor, InnerBrushRadius, NumCircleSides, DepthGroup, DrawThickness);
// If we just started painting then also draw a little brush effect
if (bArePainting)
{
const float EffectDuration = 0.2f;
const double CurTime = FPlatformTime::Seconds();
if (TimeSinceStartedPainting <= EffectDuration)
{
// Invert the effect if we're currently erasing
float EffectAlpha = TimeSinceStartedPainting / EffectDuration;
if (PaintAction == EMeshPaintAction::Erase)
{
EffectAlpha = 1.0f - EffectAlpha;
}
const FLinearColor EffectColor(0.1f + EffectAlpha * 0.4f, 0.1f + EffectAlpha * 0.4f, 0.1f + EffectAlpha * 0.4f);
const float EffectRadius = BrushRadius * EffectAlpha * EffectAlpha; // Squared curve here (looks more interesting)
DrawCircle(PDI, BrushVisualPosition, BrushXAxis, BrushYAxis, EffectColor, EffectRadius, NumCircleSides, DepthGroup);
}
}
// Draw trace surface normal
const FVector NormalLineEnd(BrushVisualPosition + HitResult.Normal * NormalLineSize);
PDI->DrawLine(BrushVisualPosition, NormalLineEnd, NormalLineColor, DepthGroup, DrawThickness);
TSharedPtr<IMeshPaintGeometryAdapter> MeshAdapter = GetMeshAdapterForComponent(Cast<UMeshComponent>(HitResult.Component.Get()));
if (MeshAdapter->IsValid() && bRenderVertices && MeshAdapter->SupportsVertexPaint())
{
const FMatrix ComponentToWorldMatrix = MeshAdapter->GetComponentToWorldMatrix();
const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(InCameraOrigin));
const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(HitResult.Location));
// @todo MeshPaint: Input vector doesn't work well with non-uniform scale
const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushRadius, 0.0f, 0.0f)).Size();
const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius;
const TArray<FVector>& InRangeVertices = MeshAdapter->SphereIntersectVertices(ComponentSpaceSquaredBrushRadius, ComponentSpaceBrushPosition, ComponentSpaceCameraPosition, GetBrushSettings()->bOnlyFrontFacingTriangles);
for (const FVector& Vertex : InRangeVertices)
{
const FVector WorldPositionVertex = ComponentToWorldMatrix.TransformPosition(Vertex);
if (( HitResult.Location - WorldPositionVertex).Size() <= BrushRadius)
{
const FVector VertexVisualPosition = WorldPositionVertex + HitResult.Normal * VisualBiasDistance;
PDI->DrawPoint(VertexVisualPosition, HoverVertexPointColor, PointDrawSize, DepthGroup);
}
}
}
}
}
}
void IMeshPainter::BeginTransaction(const FText Description)
{
// In paint mode we only allow the BeginTransaction to be called with the EndTransaction pair. We should never be
// in a state where a second transaction was started before the first was ended.
checkf(PaintTransaction == NULL, TEXT("Cannot create Transaction while another one is still active"));
if (PaintTransaction == NULL)
{
PaintTransaction = new FScopedTransaction(Description);
}
}
void IMeshPainter::EndTransaction()
{
checkf(PaintTransaction != NULL, TEXT("Cannot end Transaction since there isn't one Active"));
delete PaintTransaction;
PaintTransaction = NULL;
}