1686 lines
68 KiB
C++
1686 lines
68 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshTexturePaintingTool.h"
|
|
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "BaseGizmos/BrushStampIndicator.h"
|
|
#include "CanvasItem.h"
|
|
#include "CanvasTypes.h"
|
|
#include "Components/MeshComponent.h"
|
|
#include "Components/RuntimeVirtualTextureComponent.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Editor/TransBuffer.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "IMeshPaintComponentAdapter.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "MaterialShared.h"
|
|
#include "MeshPaintHelpers.h"
|
|
#include "MeshVertexPaintingTool.h"
|
|
#include "ObjectCacheContext.h"
|
|
#include "RenderingThread.h"
|
|
#include "RHIUtilities.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "TextureCompiler.h"
|
|
#include "TexturePaintToolset.h"
|
|
#include "TextureResource.h"
|
|
#include "ToolDataVisualizer.h"
|
|
#include "VT/VirtualTextureAdapter.h"
|
|
#include "VT/VirtualTextureBuildSettings.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
|
|
extern UNREALED_API class UEditorEngine* GEditor;
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(MeshTexturePaintingTool)
|
|
|
|
#define LOCTEXT_NAMESPACE "MeshTextureBrush"
|
|
|
|
/*
|
|
* ToolBuilder
|
|
*/
|
|
|
|
bool UMeshTextureColorPaintingToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetSelectionSupportsTextureColorPaint();
|
|
}
|
|
|
|
UInteractiveTool* UMeshTextureColorPaintingToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return NewObject<UMeshTextureColorPaintingTool>(SceneState.ToolManager);
|
|
}
|
|
|
|
bool UMeshTextureAssetPaintingToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetSelectionSupportsTextureAssetPaint();
|
|
}
|
|
|
|
UInteractiveTool* UMeshTextureAssetPaintingToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return NewObject<UMeshTextureAssetPaintingTool>(SceneState.ToolManager);
|
|
}
|
|
|
|
|
|
/*
|
|
* Tool
|
|
*/
|
|
|
|
UMeshTexturePaintingTool::UMeshTexturePaintingTool()
|
|
{
|
|
PropertyClass = UMeshTexturePaintingToolProperties::StaticClass();
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::Setup()
|
|
{
|
|
Super::Setup();
|
|
|
|
bResultValid = false;
|
|
bStampPending = false;
|
|
|
|
FMeshPaintToolSettingHelpers::RestorePropertiesForClassHeirachy(this, BrushProperties);
|
|
TextureProperties = Cast<UMeshTexturePaintingToolProperties>(BrushProperties);
|
|
|
|
// Needed after restoring properties because the brush radius may be an output
|
|
// property based on selection, so we shouldn't use the last stored value there.
|
|
// We wouldn't have this problem if we restore properties before getting
|
|
// BrushRelativeSizeRange, but that happens in the Super::Setup() call earlier.
|
|
RecalculateBrushRadius();
|
|
|
|
BrushStampIndicator->LineColor = FLinearColor::Green;
|
|
|
|
SelectionMechanic = NewObject<UMeshPaintSelectionMechanic>(this);
|
|
SelectionMechanic->Setup(this);
|
|
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
MeshPaintingSubsystem->Refresh();
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
FinishPainting();
|
|
|
|
ClearAllTextureOverrides();
|
|
|
|
PaintTargetData.Empty();
|
|
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
MeshPaintingSubsystem->Refresh();
|
|
}
|
|
|
|
FMeshPaintToolSettingHelpers::SavePropertiesForClassHeirachy(this, BrushProperties);
|
|
|
|
Super::Shutdown(ShutdownType);
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
Super::Render(RenderAPI);
|
|
FToolDataVisualizer Draw;
|
|
Draw.BeginFrame(RenderAPI);
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem && LastBestHitResult.Component != nullptr)
|
|
{
|
|
BrushStampIndicator->bDrawIndicatorLines = true;
|
|
static float WidgetLineThickness = 1.0f;
|
|
static FLinearColor VertexPointColor = FLinearColor::White;
|
|
static FLinearColor HoverVertexPointColor = FLinearColor(0.3f, 1.0f, 0.3f);
|
|
const float NormalLineSize(BrushProperties->BrushRadius * 0.35f); // Make the normal line length a function of brush size
|
|
static 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));
|
|
// Draw trace surface normal
|
|
const FVector NormalLineEnd(LastBestHitResult.Location + LastBestHitResult.Normal * NormalLineSize);
|
|
Draw.DrawLine(FVector(LastBestHitResult.Location), NormalLineEnd, NormalLineColor, WidgetLineThickness);
|
|
|
|
for (UMeshComponent* CurrentComponent : MeshPaintingSubsystem->GetPaintableMeshComponents())
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(Cast<UMeshComponent>(CurrentComponent));
|
|
if (IsMeshAdapterSupported(MeshAdapter))
|
|
{
|
|
const FMatrix ComponentToWorldMatrix = MeshAdapter->GetComponentToWorldMatrix();
|
|
FViewCameraState CameraState;
|
|
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState);
|
|
const FVector ComponentSpaceCameraPosition(ComponentToWorldMatrix.InverseTransformPosition(CameraState.Position));
|
|
const FVector ComponentSpaceBrushPosition(ComponentToWorldMatrix.InverseTransformPosition(LastBestHitResult.Location));
|
|
|
|
// @todo MeshPaint: Input vector doesn't work well with non-uniform scale
|
|
const float ComponentSpaceBrushRadius = ComponentToWorldMatrix.InverseTransformVector(FVector(BrushProperties->BrushRadius, 0.0f, 0.0f)).Size();
|
|
const float ComponentSpaceSquaredBrushRadius = ComponentSpaceBrushRadius * ComponentSpaceBrushRadius;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BrushStampIndicator->bDrawIndicatorLines = false;
|
|
}
|
|
Draw.EndFrame();
|
|
UpdateResult();
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnTick(float DeltaTime)
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem)
|
|
{
|
|
TArray<UMeshComponent*> SelectedMeshComponents = MeshPaintingSubsystem->GetSelectedMeshComponents();
|
|
|
|
if (bRequestPaintBucketFill)
|
|
{
|
|
FMeshPaintParameters BucketFillParams;
|
|
|
|
// NOTE: We square the brush strength to maximize slider precision in the low range
|
|
const float BrushStrength = BrushProperties->BrushStrength * BrushProperties->BrushStrength;
|
|
|
|
// Mesh paint settings; Only fill out relevant parameters
|
|
{
|
|
BucketFillParams.PaintAction = EMeshPaintModeAction::Paint;
|
|
BucketFillParams.BrushColor = TextureProperties->PaintColor;
|
|
BucketFillParams.BrushStrength = BrushStrength;
|
|
BucketFillParams.bWriteRed = TextureProperties->bWriteRed;
|
|
BucketFillParams.bWriteGreen = TextureProperties->bWriteGreen;
|
|
BucketFillParams.bWriteBlue = TextureProperties->bWriteBlue;
|
|
BucketFillParams.bWriteAlpha = TextureProperties->bWriteAlpha;
|
|
BucketFillParams.bUseFillBucket = true;
|
|
}
|
|
|
|
for (int32 j = 0; j < SelectedMeshComponents.Num(); ++j)
|
|
{
|
|
UMeshComponent* SelectedComponent = SelectedMeshComponents[j];
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(SelectedComponent);
|
|
if (!IsMeshAdapterSupported(MeshAdapter))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 UVChannel = GetSelectedUVChannel(SelectedComponent);
|
|
if (UVChannel >= MeshAdapter->GetNumUVChannels())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<UTexture const*> Textures;
|
|
const UTexture2D* TargetTexture2D = GetSelectedPaintTexture(SelectedComponent);
|
|
if (TargetTexture2D == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Textures.Add(TargetTexture2D);
|
|
|
|
FPaintTexture2DData* TextureData = GetPaintTargetData(TargetTexture2D);
|
|
if (TextureData)
|
|
{
|
|
Textures.Add(TextureData->PaintRenderTargetTexture);
|
|
}
|
|
|
|
TArray<FTexturePaintMeshSectionInfo> MaterialSections;
|
|
UTexturePaintToolset::RetrieveMeshSectionsForTextures(SelectedComponent, 0 /*CachedLODIndex*/, Textures, MaterialSections);
|
|
|
|
TArray<FTexturePaintTriangleInfo> TrianglePaintInfoArray;
|
|
FPerTrianglePaintAction TempAction = FPerTrianglePaintAction::CreateUObject(this, &UMeshTexturePaintingTool::GatherTextureTriangles, &TrianglePaintInfoArray, &MaterialSections, UVChannel);
|
|
|
|
// We are flooding the texture, so all triangles are influenced
|
|
|
|
const TArray<uint32>& MeshIndices = MeshAdapter->GetMeshIndices();
|
|
int32 TriangleIndices[3];
|
|
|
|
for (int32 i = 0; i < MeshIndices.Num(); i += 3)
|
|
{
|
|
TriangleIndices[0] = MeshIndices[i + 0];
|
|
TriangleIndices[1] = MeshIndices[i + 1];
|
|
TriangleIndices[2] = MeshIndices[i + 2];
|
|
TempAction.Execute(MeshAdapter.Get(), i / 3, TriangleIndices);
|
|
}
|
|
|
|
// Painting textures
|
|
UTexture2D* SelectedPaintTexure = GetSelectedPaintTexture(SelectedComponent);
|
|
if (PaintingTexture2D != nullptr && PaintingTexture2D != SelectedPaintTexure)
|
|
{
|
|
// Texture has changed, so finish up with our previous texture
|
|
FinishPaintingTexture();
|
|
}
|
|
|
|
if (PaintingTexture2D == nullptr)
|
|
{
|
|
StartPaintingTexture(SelectedComponent, *MeshAdapter);
|
|
}
|
|
|
|
FMeshPaintParameters* LastParams = nullptr;
|
|
PaintTexture(BucketFillParams, UVChannel, TrianglePaintInfoArray, SelectedComponent, *MeshAdapter, LastParams);
|
|
}
|
|
}
|
|
|
|
UMeshComponent* FirstSelectedComponent = SelectedMeshComponents.IsValidIndex(0) ? SelectedMeshComponents[0] : nullptr;
|
|
if (MeshPaintingSubsystem->bNeedsRecache || (PaintableTextures.Num() > 0 && GetSelectedPaintTexture(FirstSelectedComponent) == nullptr))
|
|
{
|
|
ClearAllTextureOverrides();
|
|
|
|
CacheSelectionData();
|
|
CacheTexturePaintData();
|
|
|
|
SetAllTextureOverrides();
|
|
}
|
|
}
|
|
|
|
if (bStampPending)
|
|
{
|
|
Paint(PendingStampRay.Origin, PendingStampRay.Direction);
|
|
bStampPending = false;
|
|
|
|
// flow
|
|
if (bInDrag && TextureProperties && TextureProperties->bEnableFlow)
|
|
{
|
|
bStampPending = true;
|
|
}
|
|
}
|
|
|
|
// Wait till end of the tick to finish painting so all systems in-between know if we've painted this frame
|
|
if (bRequestPaintBucketFill)
|
|
{
|
|
if (PaintingTexture2D != nullptr)
|
|
{
|
|
FinishPaintingTexture();
|
|
FinishPainting();
|
|
}
|
|
|
|
bRequestPaintBucketFill = false;
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
|
|
{
|
|
Super::OnPropertyModified(PropertySet, Property);
|
|
bResultValid = false;
|
|
}
|
|
|
|
|
|
double UMeshTexturePaintingTool::EstimateMaximumTargetDimension()
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem)
|
|
{
|
|
FBoxSphereBounds::Builder ExtentsBuilder;
|
|
for (UMeshComponent* SelectedComponent : MeshPaintingSubsystem->GetSelectedMeshComponents())
|
|
{
|
|
ExtentsBuilder += SelectedComponent->Bounds;
|
|
}
|
|
|
|
if (ExtentsBuilder.IsValid())
|
|
{
|
|
return FBoxSphereBounds(ExtentsBuilder).BoxExtent.GetAbsMax();
|
|
}
|
|
}
|
|
|
|
return Super::EstimateMaximumTargetDimension();
|
|
}
|
|
|
|
double UMeshTexturePaintingTool::CalculateTargetEdgeLength(int TargetTriCount)
|
|
{
|
|
double TargetTriArea = InitialMeshArea / (double)TargetTriCount;
|
|
double EdgeLen = (TargetTriArea);
|
|
return (double)FMath::RoundToInt(EdgeLen*100.0) / 100.0;
|
|
}
|
|
|
|
bool UMeshTexturePaintingTool::Paint(const FVector& InRayOrigin, const FVector& InRayDirection)
|
|
{
|
|
// Determine paint action according to whether or not shift is held down
|
|
const EMeshPaintModeAction PaintAction = GetShiftToggle() ? EMeshPaintModeAction::Erase : EMeshPaintModeAction::Paint;
|
|
const float PaintStrength = 1.0f; //Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f;
|
|
// Handle internal painting functionality
|
|
TPair<FVector, FVector> Ray(InRayOrigin, InRayDirection);
|
|
return PaintInternal(MakeArrayView(&Ray, 1), PaintAction, PaintStrength);
|
|
}
|
|
|
|
bool UMeshTexturePaintingTool::Paint(const TArrayView<TPair<FVector, FVector>>& Rays)
|
|
{
|
|
// Determine paint action according to whether or not shift is held down
|
|
const EMeshPaintModeAction PaintAction = GetShiftToggle() ? EMeshPaintModeAction::Erase : EMeshPaintModeAction::Paint;
|
|
|
|
const float PaintStrength = 1.0f; //Viewport->IsPenActive() ? Viewport->GetTabletPressure() : 1.f;
|
|
// Handle internal painting functionality
|
|
return PaintInternal(Rays, PaintAction, PaintStrength);
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::CacheSelectionData()
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem)
|
|
{
|
|
MeshPaintingSubsystem->ClearPaintableMeshComponents();
|
|
|
|
//Determine LOD level to use for painting(can only paint on LODs in vertex mode)
|
|
const int32 PaintLODIndex = 0;
|
|
//Determine UV channel to use while painting textures
|
|
const int32 UVChannel = 0;
|
|
|
|
MeshPaintingSubsystem->CacheSelectionData(PaintLODIndex, UVChannel);
|
|
}
|
|
}
|
|
|
|
bool UMeshTexturePaintingTool::PaintInternal(const TArrayView<TPair<FVector, FVector>>& Rays, EMeshPaintModeAction PaintAction, float PaintStrength)
|
|
{
|
|
TArray<FPaintRayResults> PaintRayResults;
|
|
PaintRayResults.AddDefaulted(Rays.Num());
|
|
|
|
bool bAnyPaintApplied = false;
|
|
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
TMap<UMeshComponent*, TArray<int32>> HoveredComponents;
|
|
|
|
const float BrushRadius = BrushProperties->BrushRadius;
|
|
const bool bIsPainting = (PaintAction == EMeshPaintModeAction::Paint);
|
|
const float InStrengthScale = PaintStrength;;
|
|
|
|
// Fire out a ray to see if there is a *selected* component under the mouse cursor that can be painted.
|
|
for (int32 i = 0; i < Rays.Num(); ++i)
|
|
{
|
|
const FVector& RayOrigin = Rays[i].Key;
|
|
const FVector& RayDirection = Rays[i].Value;
|
|
FHitResult& BestTraceResult = PaintRayResults[i].BestTraceResult;
|
|
|
|
const FVector TraceStart(RayOrigin);
|
|
const FVector TraceEnd(RayOrigin + RayDirection * HALF_WORLD_MAX);
|
|
|
|
for (UMeshComponent* MeshComponent : MeshPaintingSubsystem->GetPaintableMeshComponents())
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(MeshComponent);
|
|
|
|
// Ray trace
|
|
FHitResult TraceHitResult(1.0f);
|
|
|
|
if (MeshAdapter->LineTraceComponent(TraceHitResult, TraceStart, TraceEnd, FCollisionQueryParams(SCENE_QUERY_STAT(Paint), true)))
|
|
{
|
|
// Find the closest impact
|
|
if ((BestTraceResult.GetComponent() == nullptr) || (TraceHitResult.Time < BestTraceResult.Time))
|
|
{
|
|
BestTraceResult = TraceHitResult;
|
|
}
|
|
}
|
|
}
|
|
|
|
UMeshComponent* BestTraceMeshComponent = Cast<UMeshComponent>(BestTraceResult.GetComponent());
|
|
// If painting texture assets, just use the BestTraceMeshComponent as we only support painting a single mesh at a time in that mode.
|
|
const bool bAllowMultiselect = AllowsMultiselect();
|
|
|
|
bool bUsed = false;
|
|
for (UMeshComponent* MeshComponent : MeshPaintingSubsystem->GetPaintableMeshComponents())
|
|
{
|
|
if (MeshComponent == BestTraceMeshComponent)
|
|
{
|
|
HoveredComponents.FindOrAdd(MeshComponent).Add(i);
|
|
bUsed = true;
|
|
}
|
|
else if (bAllowMultiselect)
|
|
{
|
|
FSphere Sphere(BestTraceResult.Location, BrushRadius);
|
|
if (MeshComponent->GetLocalBounds().GetSphere().TransformBy(MeshComponent->GetComponentTransform()).Intersects(Sphere))
|
|
{
|
|
HoveredComponents.FindOrAdd(MeshComponent).Add(i);
|
|
bUsed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bUsed)
|
|
{
|
|
FVector BrushXAxis, BrushYAxis;
|
|
BestTraceResult.Normal.FindBestAxisVectors(BrushXAxis, BrushYAxis);
|
|
// Display settings
|
|
const float VisualBiasDistance = 0.15f;
|
|
const FVector BrushVisualPosition = BestTraceResult.Location + BestTraceResult.Normal * VisualBiasDistance;
|
|
|
|
const FLinearColor PaintColor = TextureProperties->PaintColor;
|
|
const FLinearColor EraseColor = TextureProperties->EraseColor;
|
|
|
|
// NOTE: We square the brush strength to maximize slider precision in the low range
|
|
const float BrushStrength = BrushProperties->BrushStrength * BrushProperties->BrushStrength * InStrengthScale;
|
|
|
|
const float BrushDepth = BrushRadius;
|
|
|
|
// Mesh paint settings
|
|
FMeshPaintParameters& Params = PaintRayResults[i].Params;
|
|
{
|
|
Params.PaintAction = PaintAction;
|
|
Params.BrushPosition = BestTraceResult.Location;
|
|
Params.BrushNormal = BestTraceResult.Normal;
|
|
Params.BrushColor = bIsPainting ? PaintColor : EraseColor;
|
|
Params.SquaredBrushRadius = BrushRadius * BrushRadius;
|
|
Params.BrushRadialFalloffRange = BrushProperties->BrushFalloffAmount * BrushRadius;
|
|
Params.InnerBrushRadius = BrushRadius - Params.BrushRadialFalloffRange;
|
|
Params.BrushDepth = BrushDepth;
|
|
Params.BrushDepthFalloffRange = BrushProperties->BrushFalloffAmount * BrushDepth;
|
|
Params.InnerBrushDepth = BrushDepth - Params.BrushDepthFalloffRange;
|
|
Params.BrushStrength = BrushStrength;
|
|
Params.BrushToWorldMatrix = FMatrix(BrushXAxis, BrushYAxis, Params.BrushNormal, Params.BrushPosition);
|
|
Params.InverseBrushToWorldMatrix = Params.BrushToWorldMatrix.InverseFast();
|
|
Params.bWriteRed = TextureProperties->bWriteRed;
|
|
Params.bWriteGreen = TextureProperties->bWriteGreen;
|
|
Params.bWriteBlue = TextureProperties->bWriteBlue;
|
|
Params.bWriteAlpha = TextureProperties->bWriteAlpha;
|
|
FVector BrushSpaceVertexPosition = Params.InverseBrushToWorldMatrix.TransformVector(FVector4(Params.BrushPosition, 1.0f));
|
|
Params.BrushPosition2D = FVector2f(BrushSpaceVertexPosition.X, BrushSpaceVertexPosition.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HoveredComponents.Num() > 0)
|
|
{
|
|
if (bArePainting == false)
|
|
{
|
|
bArePainting = true;
|
|
}
|
|
|
|
// Iterate over the selected meshes under the cursor and paint them!
|
|
for (auto& Entry : HoveredComponents)
|
|
{
|
|
UMeshComponent* HoveredComponent = Entry.Key;
|
|
TArray<int32>& PaintRayResultIds = Entry.Value;
|
|
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(HoveredComponent);
|
|
if (!IsMeshAdapterSupported(MeshAdapter))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 UVChannel = GetSelectedUVChannel(HoveredComponent);
|
|
if (UVChannel >= MeshAdapter->GetNumUVChannels())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<UTexture const*> Textures;
|
|
const UTexture2D* TargetTexture2D = GetSelectedPaintTexture(HoveredComponent);
|
|
if (TargetTexture2D == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Textures.Add(TargetTexture2D);
|
|
|
|
FPaintTexture2DData* TextureData = GetPaintTargetData(TargetTexture2D);
|
|
if (TextureData)
|
|
{
|
|
Textures.Add(TextureData->PaintRenderTargetTexture);
|
|
}
|
|
|
|
TArray<FTexturePaintMeshSectionInfo> MaterialSections;
|
|
UTexturePaintToolset::RetrieveMeshSectionsForTextures(HoveredComponent, 0/*CachedLODIndex*/, Textures, MaterialSections);
|
|
|
|
bool bPaintApplied = false;
|
|
TArray<FTexturePaintTriangleInfo> TrianglePaintInfoArray;
|
|
if (PaintRayResultIds.Num() > 0)
|
|
{
|
|
const int32 PaintRayResultId = PaintRayResultIds[0];
|
|
const FVector& BestTraceResultLocation = PaintRayResults[PaintRayResultId].BestTraceResult.Location;
|
|
FViewCameraState CameraState;
|
|
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState);
|
|
bPaintApplied |= MeshPaintingSubsystem->ApplyPerTrianglePaintAction(MeshAdapter.Get(), CameraState.Position, BestTraceResultLocation, BrushProperties, FPerTrianglePaintAction::CreateUObject(this, &UMeshTexturePaintingTool::GatherTextureTriangles, &TrianglePaintInfoArray, &MaterialSections, UVChannel), TextureProperties->bOnlyFrontFacingTriangles);
|
|
}
|
|
|
|
if (!bPaintApplied)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Painting textures
|
|
bAnyPaintApplied = true;
|
|
|
|
UTexture2D* SelectedPaintTexure = GetSelectedPaintTexture(HoveredComponent);
|
|
if (PaintingTexture2D != nullptr && PaintingTexture2D != SelectedPaintTexure)
|
|
{
|
|
// Texture has changed, so finish up with our previous texture
|
|
FinishPaintingTexture();
|
|
}
|
|
|
|
if (PaintingTexture2D == nullptr)
|
|
{
|
|
StartPaintingTexture(HoveredComponent, *MeshAdapter);
|
|
}
|
|
|
|
if (PaintingTexture2D != nullptr)
|
|
{
|
|
if (PaintRayResultIds.Num() > 0)
|
|
{
|
|
const int32 PaintRayResultId = PaintRayResultIds[0];
|
|
FMeshPaintParameters& Params = PaintRayResults[PaintRayResultId].Params;
|
|
FMeshPaintParameters* LastParams = nullptr;
|
|
if (LastPaintRayResults.Num() > PaintRayResultId)
|
|
{
|
|
LastParams = &LastPaintRayResults[PaintRayResultId].Params;
|
|
}
|
|
|
|
PaintTexture(Params, UVChannel, TrianglePaintInfoArray, HoveredComponent, *MeshAdapter, LastParams);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LastPaintRayResults = MoveTemp(PaintRayResults);
|
|
return bAnyPaintApplied;
|
|
}
|
|
|
|
/** Painting texture to use in material override should be the virtual texture adapter if it exists. */
|
|
static UTexture* GetTextureForMaterialOverride(FPaintTexture2DData const& TextureData)
|
|
{
|
|
UTexture* RenderTarget = TextureData.PaintRenderTargetTexture;
|
|
UTexture* RenderTargetAdapter = TextureData.PaintRenderTargetTextureAdapter;
|
|
return RenderTargetAdapter != nullptr ? RenderTargetAdapter : RenderTarget;
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::AddTextureOverrideToComponent(FPaintTexture2DData& TextureData, UMeshComponent* MeshComponent, const IMeshPaintComponentAdapter* MeshPaintAdapter, FMaterialUpdateContext& MaterialUpdateContext)
|
|
{
|
|
if (MeshComponent && MeshPaintAdapter)
|
|
{
|
|
if (!TextureData.TextureOverrideComponents.Contains(MeshComponent))
|
|
{
|
|
TextureData.TextureOverrideComponents.AddUnique(MeshComponent);
|
|
|
|
MeshPaintAdapter->ApplyOrRemoveTextureOverride(TextureData.PaintingTexture2D, GetTextureForMaterialOverride(TextureData), MaterialUpdateContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::UpdateResult()
|
|
{
|
|
GetToolManager()->PostInvalidation();
|
|
|
|
bResultValid = true;
|
|
}
|
|
|
|
FInputRayHit UMeshTexturePaintingTool::CanBeginClickDragSequence(const FInputDeviceRay& PressPos)
|
|
{
|
|
FHitResult OutHit;
|
|
bCachedClickRay = false;
|
|
if (!HitTest(PressPos.WorldRay, OutHit))
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
const bool bFallbackClick = MeshPaintingSubsystem->GetSelectedMeshComponents().Num() > 0;
|
|
if (SelectionMechanic->IsHitByClick(PressPos, bFallbackClick).bHit)
|
|
{
|
|
bCachedClickRay = true;
|
|
PendingClickRay = PressPos.WorldRay;
|
|
PendingClickScreenPosition = PressPos.ScreenPosition;
|
|
return FInputRayHit(0.0);
|
|
}
|
|
}
|
|
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem && LastBestHitResult.Component != nullptr && MeshPaintingSubsystem->LastPaintedComponent != LastBestHitResult.Component)
|
|
{
|
|
MeshPaintingSubsystem->LastPaintedComponent = (UMeshComponent*)LastBestHitResult.Component.Get();
|
|
}
|
|
|
|
return Super::CanBeginClickDragSequence(PressPos);
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnUpdateModifierState(int ModifierID, bool bIsOn)
|
|
{
|
|
Super::OnUpdateModifierState(ModifierID, bIsOn);
|
|
SelectionMechanic->SetAddToSelectionSet(bShiftToggle);
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnBeginDrag(const FRay& Ray)
|
|
{
|
|
Super::OnBeginDrag(Ray);
|
|
FHitResult OutHit;
|
|
if (HitTest(Ray, OutHit))
|
|
{
|
|
bInDrag = true;
|
|
|
|
// apply initial stamp
|
|
PendingStampRay = Ray;
|
|
bStampPending = true;
|
|
}
|
|
else if (bCachedClickRay)
|
|
{
|
|
FInputDeviceRay InputDeviceRay = FInputDeviceRay(PendingClickRay, PendingClickScreenPosition);
|
|
SelectionMechanic->SetAddToSelectionSet(bShiftToggle);
|
|
SelectionMechanic->OnClicked(InputDeviceRay);
|
|
bCachedClickRay = false;
|
|
RecalculateBrushRadius();
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnUpdateDrag(const FRay& Ray)
|
|
{
|
|
Super::OnUpdateDrag(Ray);
|
|
if (bInDrag)
|
|
{
|
|
PendingStampRay = Ray;
|
|
bStampPending = true;
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::OnEndDrag(const FRay& Ray)
|
|
{
|
|
FinishPaintingTexture();
|
|
FinishPainting();
|
|
bStampPending = false;
|
|
bInDrag = false;
|
|
}
|
|
|
|
bool UMeshTexturePaintingTool::HitTest(const FRay& Ray, FHitResult& OutHit)
|
|
{
|
|
bool bUsed = false;
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
MeshPaintingSubsystem->FindHitResult(Ray, OutHit);
|
|
LastBestHitResult = OutHit;
|
|
bUsed = OutHit.bBlockingHit;
|
|
}
|
|
return bUsed;
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::FinishPainting()
|
|
{
|
|
PaintingTransaction.Reset();
|
|
bArePainting = false;
|
|
bRequiresRuntimeVirtualTextureUpdates = false;
|
|
}
|
|
|
|
FPaintTexture2DData* UMeshTexturePaintingTool::GetPaintTargetData(const UTexture2D* InTexture)
|
|
{
|
|
return PaintTargetData.Find(InTexture);
|
|
}
|
|
|
|
FPaintTexture2DData* UMeshTexturePaintingTool::AddPaintTargetData(UTexture2D* InTexture)
|
|
{
|
|
checkf(InTexture != nullptr, TEXT("Invalid Texture ptr"));
|
|
|
|
/** Only create new target if we haven't gotten one already */
|
|
FPaintTexture2DData* TextureData = GetPaintTargetData(InTexture);
|
|
if (TextureData == nullptr)
|
|
{
|
|
// If we didn't find data associated with this texture we create a new entry and return a reference to it.
|
|
// Note: This reference is only valid until the next change to any key in the map.
|
|
TextureData = &PaintTargetData.Add(InTexture, FPaintTexture2DData(InTexture));
|
|
}
|
|
return TextureData;
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::GatherTextureTriangles(IMeshPaintComponentAdapter* Adapter, int32 TriangleIndex, const int32 VertexIndices[3], TArray<FTexturePaintTriangleInfo>* TriangleInfo, TArray<FTexturePaintMeshSectionInfo>* SectionInfos, int32 UVChannelIndex)
|
|
{
|
|
/** Retrieve triangles eligible for texture painting */
|
|
bool bAdd = SectionInfos->Num() == 0;
|
|
for (const FTexturePaintMeshSectionInfo& SectionInfo : *SectionInfos)
|
|
{
|
|
if (TriangleIndex >= SectionInfo.FirstIndex && TriangleIndex < SectionInfo.LastIndex)
|
|
{
|
|
bAdd = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bAdd)
|
|
{
|
|
FTexturePaintTriangleInfo Info;
|
|
Adapter->GetVertexPosition(VertexIndices[0], Info.TriVertices[0]);
|
|
Adapter->GetVertexPosition(VertexIndices[1], Info.TriVertices[1]);
|
|
Adapter->GetVertexPosition(VertexIndices[2], Info.TriVertices[2]);
|
|
Info.TriVertices[0] = Adapter->GetComponentToWorldMatrix().TransformPosition(Info.TriVertices[0]);
|
|
Info.TriVertices[1] = Adapter->GetComponentToWorldMatrix().TransformPosition(Info.TriVertices[1]);
|
|
Info.TriVertices[2] = Adapter->GetComponentToWorldMatrix().TransformPosition(Info.TriVertices[2]);
|
|
Adapter->GetTextureCoordinate(VertexIndices[0], UVChannelIndex, Info.TriUVs[0]);
|
|
Adapter->GetTextureCoordinate(VertexIndices[1], UVChannelIndex, Info.TriUVs[1]);
|
|
Adapter->GetTextureCoordinate(VertexIndices[2], UVChannelIndex, Info.TriUVs[2]);
|
|
TriangleInfo->Add(Info);
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::StartPaintingTexture(UMeshComponent* InMeshComponent, const IMeshPaintComponentAdapter& GeometryInfo)
|
|
{
|
|
check(InMeshComponent != nullptr);
|
|
check(PaintingTexture2D == nullptr);
|
|
|
|
// Only start new transaction if not in one currently
|
|
if (!PaintingTransaction.IsValid())
|
|
{
|
|
PaintingTransaction = MakeUnique<FScopedTransaction>(LOCTEXT("MeshPaintMode_TexturePaint_Transaction", "Texture Paint"));
|
|
}
|
|
|
|
const auto FeatureLevel = InMeshComponent->GetWorld()->GetFeatureLevel();
|
|
|
|
UTexture2D* Texture2D = GetSelectedPaintTexture(InMeshComponent);
|
|
if (Texture2D == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bStartedPainting = false;
|
|
FPaintTexture2DData* TextureData = GetPaintTargetData(Texture2D);
|
|
|
|
// Check all the materials on the mesh to see if the user texture is there
|
|
int32 MaterialIndex = 0;
|
|
UMaterialInterface* MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
|
|
|
|
Texture2D->BlockOnAnyAsyncBuild();
|
|
bool bIsSourceTextureStreamedIn = Texture2D->IsFullyStreamedIn() && !Texture2D->HasPendingInitOrStreaming();
|
|
|
|
// IMeshPaintComponentAdapter::DefaultQueryPaintableTextures already filters out un-used textures
|
|
if (!bIsSourceTextureStreamedIn)
|
|
{
|
|
Texture2D->SetForceMipLevelsToBeResident(30.0f);
|
|
Texture2D->bForceMiplevelsToBeResident = true;
|
|
Texture2D->WaitForStreaming();
|
|
bIsSourceTextureStreamedIn = Texture2D->IsFullyStreamedIn() && !Texture2D->HasPendingInitOrStreaming();
|
|
}
|
|
|
|
while (MaterialToCheck != nullptr)
|
|
{
|
|
if (!bStartedPainting)
|
|
{
|
|
const int32 TextureWidth = Texture2D->Source.GetSizeX();
|
|
const int32 TextureHeight = Texture2D->Source.GetSizeY();
|
|
|
|
check(TextureData != nullptr);
|
|
|
|
const int32 BrushTargetTextureWidth = TextureWidth;
|
|
const int32 BrushTargetTextureHeight = TextureHeight;
|
|
|
|
// Create the rendertarget used to store our paint delta
|
|
if (TextureData->BrushRenderTargetTexture == nullptr ||
|
|
TextureData->BrushRenderTargetTexture->GetSurfaceWidth() != BrushTargetTextureWidth ||
|
|
TextureData->BrushRenderTargetTexture->GetSurfaceHeight() != BrushTargetTextureHeight)
|
|
{
|
|
TextureData->BrushRenderTargetTexture = nullptr;
|
|
TextureData->BrushRenderTargetTexture = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
const bool bForceLinearGamma = true;
|
|
TextureData->BrushRenderTargetTexture->ClearColor = FLinearColor::Black;
|
|
TextureData->BrushRenderTargetTexture->bNeedsTwoCopies = true;
|
|
TextureData->BrushRenderTargetTexture->InitCustomFormat(BrushTargetTextureWidth, BrushTargetTextureHeight, PF_A16B16G16R16, bForceLinearGamma);
|
|
TextureData->BrushRenderTargetTexture->UpdateResourceImmediate();
|
|
TextureData->BrushRenderTargetTexture->AddressX = TextureData->PaintRenderTargetTexture->AddressX;
|
|
TextureData->BrushRenderTargetTexture->AddressY = TextureData->PaintRenderTargetTexture->AddressY;
|
|
}
|
|
|
|
if (TextureProperties->bEnableSeamPainting)
|
|
{
|
|
// Create the rendertarget used to store a mask for our paint delta area
|
|
if (TextureData->BrushMaskRenderTargetTexture == nullptr ||
|
|
TextureData->BrushMaskRenderTargetTexture->GetSurfaceWidth() != BrushTargetTextureWidth ||
|
|
TextureData->BrushMaskRenderTargetTexture->GetSurfaceHeight() != BrushTargetTextureHeight)
|
|
{
|
|
TextureData->BrushMaskRenderTargetTexture = nullptr;
|
|
TextureData->BrushMaskRenderTargetTexture = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
const bool bForceLinearGamma = true;
|
|
TextureData->BrushMaskRenderTargetTexture->ClearColor = FLinearColor::Black;
|
|
TextureData->BrushMaskRenderTargetTexture->bNeedsTwoCopies = true;
|
|
TextureData->BrushMaskRenderTargetTexture->InitCustomFormat(BrushTargetTextureWidth, BrushTargetTextureHeight, PF_G8, bForceLinearGamma);
|
|
TextureData->BrushMaskRenderTargetTexture->UpdateResourceImmediate();
|
|
TextureData->BrushMaskRenderTargetTexture->AddressX = TextureData->PaintRenderTargetTexture->AddressX;
|
|
TextureData->BrushMaskRenderTargetTexture->AddressY = TextureData->PaintRenderTargetTexture->AddressY;
|
|
}
|
|
|
|
// Create the rendertarget used to store a texture seam mask
|
|
if (TextureData->SeamMaskRenderTargetTexture == nullptr ||
|
|
TextureData->SeamMaskRenderTargetTexture->GetSurfaceWidth() != TextureWidth ||
|
|
TextureData->SeamMaskRenderTargetTexture->GetSurfaceHeight() != TextureHeight)
|
|
{
|
|
TextureData->SeamMaskRenderTargetTexture = nullptr;
|
|
TextureData->SeamMaskRenderTargetTexture = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
const bool bForceLinearGamma = true;
|
|
TextureData->SeamMaskRenderTargetTexture->ClearColor = FLinearColor::Black;
|
|
TextureData->SeamMaskRenderTargetTexture->bNeedsTwoCopies = true;
|
|
TextureData->SeamMaskRenderTargetTexture->InitCustomFormat(BrushTargetTextureWidth, BrushTargetTextureHeight, PF_G8, bForceLinearGamma);
|
|
TextureData->SeamMaskRenderTargetTexture->UpdateResourceImmediate();
|
|
TextureData->SeamMaskRenderTargetTexture->AddressX = TextureData->PaintRenderTargetTexture->AddressX;
|
|
TextureData->SeamMaskRenderTargetTexture->AddressY = TextureData->PaintRenderTargetTexture->AddressY;
|
|
TextureData->SeamMaskComponent = nullptr;
|
|
}
|
|
}
|
|
|
|
bStartedPainting = true;
|
|
UTexture2D* Texture2DPaintBrush = TextureProperties->PaintBrush;
|
|
if (Texture2DPaintBrush)
|
|
{
|
|
const int32 PaintBrushTextureWidth = Texture2DPaintBrush->Source.GetSizeX();
|
|
const int32 PaintBrushTextureHeight = Texture2DPaintBrush->Source.GetSizeY();
|
|
if (TextureData->PaintBrushRenderTargetTexture == nullptr ||
|
|
TextureData->PaintBrushRenderTargetTexture->GetSurfaceWidth() != PaintBrushTextureWidth ||
|
|
TextureData->PaintBrushRenderTargetTexture->GetSurfaceHeight() != PaintBrushTextureHeight)
|
|
{
|
|
TextureData->PaintBrushRenderTargetTexture = nullptr;
|
|
TextureData->PaintBrushRenderTargetTexture = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
TextureData->PaintBrushRenderTargetTexture->bNeedsTwoCopies = true;
|
|
const bool bForceLinearGamma = true;
|
|
TextureData->PaintBrushRenderTargetTexture->ClearColor = FLinearColor::Black;
|
|
TextureData->PaintBrushRenderTargetTexture->InitCustomFormat(PaintBrushTextureWidth, PaintBrushTextureHeight, PF_A16B16G16R16, bForceLinearGamma);
|
|
TextureData->PaintBrushRenderTargetTexture->UpdateResourceImmediate();
|
|
}
|
|
TextureData->PaintBrushRenderTargetTexture->AddressX = Texture2DPaintBrush->AddressX;
|
|
TextureData->PaintBrushRenderTargetTexture->AddressY = Texture2DPaintBrush->AddressY;
|
|
}
|
|
else
|
|
{
|
|
TextureData->PaintBrushRenderTargetTexture = nullptr;
|
|
}
|
|
|
|
check(Texture2D != nullptr);
|
|
PaintingTexture2D = Texture2D;
|
|
}
|
|
|
|
MaterialIndex++;
|
|
MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
|
|
}
|
|
|
|
if (bIsSourceTextureStreamedIn && bStartedPainting)
|
|
{
|
|
check(Texture2D != nullptr);
|
|
PaintingTexture2D = Texture2D;
|
|
|
|
if (TextureProperties->PaintBrush != nullptr && TextureData->PaintBrushRenderTargetTexture != nullptr)
|
|
{
|
|
UTexturePaintToolset::SetupInitialRenderTargetData(TextureProperties->PaintBrush, TextureData->PaintBrushRenderTargetTexture);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Set flag for whether to update runtime virtual textures while painting.
|
|
bRequiresRuntimeVirtualTextureUpdates = false;
|
|
FObjectCacheContextScope ObjectCacheScope;
|
|
for (UMaterialInterface* Material : ObjectCacheScope.GetContext().GetMaterialsAffectedByTexture(Texture2D))
|
|
{
|
|
bRequiresRuntimeVirtualTextureUpdates |= Material->WritesToRuntimeVirtualTexture();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::PaintTexture(FMeshPaintParameters& InParams, int32 UVChannel, TArray<FTexturePaintTriangleInfo>& InInfluencedTriangles, UMeshComponent* MeshComponent, const IMeshPaintComponentAdapter& GeometryInfo, FMeshPaintParameters* LastParams)
|
|
{
|
|
// We bail early if there are no influenced triangles
|
|
if (InInfluencedTriangles.Num() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(GEditor && GEditor->GetEditorWorldContext().World());
|
|
const auto FeatureLevel = GEditor->GetEditorWorldContext().World()->GetFeatureLevel();
|
|
|
|
FPaintTexture2DData* TextureData = GetPaintTargetData(PaintingTexture2D);
|
|
check(TextureData != nullptr && TextureData->PaintRenderTargetTexture != nullptr);
|
|
|
|
// Copy the current image to the brush rendertarget texture.
|
|
{
|
|
check(TextureData->BrushRenderTargetTexture != nullptr);
|
|
UTexturePaintToolset::CopyTextureToRenderTargetTexture(TextureData->PaintRenderTargetTexture, TextureData->BrushRenderTargetTexture, FeatureLevel);
|
|
}
|
|
|
|
const bool bEnableSeamPainting = TextureProperties->bEnableSeamPainting;
|
|
const FMatrix WorldToBrushMatrix = InParams.InverseBrushToWorldMatrix;
|
|
|
|
// Grab the actual render target resource from the textures. Note that we're absolutely NOT ALLOWED to
|
|
// dereference these pointers. We're just passing them along to other functions that will use them on the render
|
|
// thread. The only thing we're allowed to do is check to see if they are nullptr or not.
|
|
FTextureRenderTargetResource* BrushRenderTargetResource = TextureData->BrushRenderTargetTexture->GameThread_GetRenderTargetResource();
|
|
check(BrushRenderTargetResource != nullptr);
|
|
|
|
// Create a canvas for the brush render target.
|
|
FCanvas BrushPaintCanvas(BrushRenderTargetResource, nullptr, FGameTime(), FeatureLevel);
|
|
|
|
// Parameters for brush paint
|
|
TRefCountPtr< FMeshPaintBatchedElementParameters > MeshPaintBatchedElementParameters(new FMeshPaintBatchedElementParameters());
|
|
{
|
|
MeshPaintBatchedElementParameters->ShaderParams.PaintBrushTexture = TextureData->PaintBrushRenderTargetTexture;
|
|
if (LastParams)
|
|
{
|
|
MeshPaintBatchedElementParameters->ShaderParams.PaintBrushDirectionVector = InParams.BrushPosition2D - LastParams->BrushPosition2D;
|
|
MeshPaintBatchedElementParameters->ShaderParams.bRotateBrushTowardsDirection = TextureProperties->bRotateBrushTowardsDirection;
|
|
}
|
|
else
|
|
{
|
|
MeshPaintBatchedElementParameters->ShaderParams.PaintBrushDirectionVector = FVector2f(0.0f, 0.0f);
|
|
MeshPaintBatchedElementParameters->ShaderParams.bRotateBrushTowardsDirection = false;
|
|
}
|
|
MeshPaintBatchedElementParameters->ShaderParams.PaintBrushRotationOffset = TextureProperties->PaintBrushRotationOffset;
|
|
MeshPaintBatchedElementParameters->ShaderParams.bUseFillBucket = InParams.bUseFillBucket;
|
|
MeshPaintBatchedElementParameters->ShaderParams.CloneTexture = TextureData->BrushRenderTargetTexture;
|
|
MeshPaintBatchedElementParameters->ShaderParams.WorldToBrushMatrix = WorldToBrushMatrix;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushRadius = InParams.InnerBrushRadius + InParams.BrushRadialFalloffRange;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushRadialFalloffRange = InParams.BrushRadialFalloffRange;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushDepth = InParams.InnerBrushDepth + InParams.BrushDepthFalloffRange;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushDepthFalloffRange = InParams.BrushDepthFalloffRange;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushStrength = InParams.BrushStrength;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BrushColor = InParams.BrushColor;
|
|
MeshPaintBatchedElementParameters->ShaderParams.RedChannelFlag = InParams.bWriteRed;
|
|
MeshPaintBatchedElementParameters->ShaderParams.GreenChannelFlag = InParams.bWriteGreen;
|
|
MeshPaintBatchedElementParameters->ShaderParams.BlueChannelFlag = InParams.bWriteBlue;
|
|
MeshPaintBatchedElementParameters->ShaderParams.AlphaChannelFlag = InParams.bWriteAlpha;
|
|
MeshPaintBatchedElementParameters->ShaderParams.GenerateMaskFlag = false;
|
|
}
|
|
|
|
FBatchedElements* BrushPaintBatchedElements = BrushPaintCanvas.GetBatchedElements(FCanvas::ET_Triangle, MeshPaintBatchedElementParameters, nullptr, SE_BLEND_Opaque);
|
|
BrushPaintBatchedElements->AddReserveVertices(InInfluencedTriangles.Num() * 3);
|
|
BrushPaintBatchedElements->AddReserveTriangles(InInfluencedTriangles.Num(), nullptr, SE_BLEND_Opaque);
|
|
|
|
FHitProxyId BrushPaintHitProxyId = BrushPaintCanvas.GetHitProxyId();
|
|
|
|
TSharedPtr<FCanvas> BrushMaskCanvas;
|
|
TRefCountPtr< FMeshPaintBatchedElementParameters > MeshPaintMaskBatchedElementParameters;
|
|
FBatchedElements* BrushMaskBatchedElements = nullptr;
|
|
FHitProxyId BrushMaskHitProxyId;
|
|
FTextureRenderTargetResource* BrushMaskRenderTargetResource = nullptr;
|
|
|
|
if (bEnableSeamPainting)
|
|
{
|
|
BrushMaskRenderTargetResource = TextureData->BrushMaskRenderTargetTexture->GameThread_GetRenderTargetResource();
|
|
check(BrushMaskRenderTargetResource != nullptr);
|
|
|
|
// Create a canvas for the brush mask rendertarget and clear it to black.
|
|
BrushMaskCanvas = TSharedPtr<FCanvas>(new FCanvas(BrushMaskRenderTargetResource, nullptr, FGameTime(), FeatureLevel));
|
|
BrushMaskCanvas->Clear(FLinearColor::Black);
|
|
|
|
// Parameters for the mask
|
|
MeshPaintMaskBatchedElementParameters = TRefCountPtr< FMeshPaintBatchedElementParameters >(new FMeshPaintBatchedElementParameters());
|
|
{
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.PaintBrushTexture = TextureData->PaintBrushRenderTargetTexture;
|
|
if (LastParams)
|
|
{
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.PaintBrushDirectionVector = InParams.BrushPosition2D - LastParams->BrushPosition2D;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.bRotateBrushTowardsDirection = TextureProperties->bRotateBrushTowardsDirection;
|
|
}
|
|
else
|
|
{
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.PaintBrushDirectionVector = FVector2f(0.0f, 0.0f);
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.bRotateBrushTowardsDirection = false;
|
|
}
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.PaintBrushRotationOffset = TextureProperties->PaintBrushRotationOffset;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.bUseFillBucket = InParams.bUseFillBucket;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.CloneTexture = TextureData->PaintRenderTargetTexture;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.WorldToBrushMatrix = WorldToBrushMatrix;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushRadius = InParams.InnerBrushRadius + InParams.BrushRadialFalloffRange;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushRadialFalloffRange = InParams.BrushRadialFalloffRange;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushDepth = InParams.InnerBrushDepth + InParams.BrushDepthFalloffRange;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushDepthFalloffRange = InParams.BrushDepthFalloffRange;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushStrength = InParams.BrushStrength;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BrushColor = InParams.BrushColor;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.RedChannelFlag = InParams.bWriteRed;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.GreenChannelFlag = InParams.bWriteGreen;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.BlueChannelFlag = InParams.bWriteBlue;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.AlphaChannelFlag = InParams.bWriteAlpha;
|
|
MeshPaintMaskBatchedElementParameters->ShaderParams.GenerateMaskFlag = true;
|
|
}
|
|
|
|
BrushMaskBatchedElements = BrushMaskCanvas->GetBatchedElements(FCanvas::ET_Triangle, MeshPaintMaskBatchedElementParameters, nullptr, SE_BLEND_Opaque);
|
|
BrushMaskBatchedElements->AddReserveVertices(InInfluencedTriangles.Num() * 3);
|
|
BrushMaskBatchedElements->AddReserveTriangles(InInfluencedTriangles.Num(), nullptr, SE_BLEND_Opaque);
|
|
|
|
BrushMaskHitProxyId = BrushMaskCanvas->GetHitProxyId();
|
|
}
|
|
|
|
// Process the influenced triangles - storing off a large list is much slower than processing in a single loop
|
|
for (int32 CurIndex = 0; CurIndex < InInfluencedTriangles.Num(); ++CurIndex)
|
|
{
|
|
FTexturePaintTriangleInfo& CurTriangle = InInfluencedTriangles[CurIndex];
|
|
|
|
FVector2D UVMin(99999.9f, 99999.9f);
|
|
FVector2D UVMax(-99999.9f, -99999.9f);
|
|
|
|
// Transform the triangle and update the UV bounds
|
|
for (int32 TriVertexNum = 0; TriVertexNum < 3; ++TriVertexNum)
|
|
{
|
|
// Update bounds
|
|
float U = CurTriangle.TriUVs[TriVertexNum].X;
|
|
float V = CurTriangle.TriUVs[TriVertexNum].Y;
|
|
|
|
if (U < UVMin.X)
|
|
{
|
|
UVMin.X = U;
|
|
}
|
|
if (U > UVMax.X)
|
|
{
|
|
UVMax.X = U;
|
|
}
|
|
if (V < UVMin.Y)
|
|
{
|
|
UVMin.Y = V;
|
|
}
|
|
if (V > UVMax.Y)
|
|
{
|
|
UVMax.Y = V;
|
|
}
|
|
}
|
|
|
|
// If the triangle lies entirely outside of the 0.0-1.0 range, we'll transpose it back
|
|
FVector2D UVOffset(0.0f, 0.0f);
|
|
if (UVMax.X > 1.0f)
|
|
{
|
|
UVOffset.X = -FMath::FloorToFloat(UVMin.X);
|
|
}
|
|
else if (UVMin.X < 0.0f)
|
|
{
|
|
UVOffset.X = 1.0f + FMath::FloorToFloat(-UVMax.X);
|
|
}
|
|
|
|
if (UVMax.Y > 1.0f)
|
|
{
|
|
UVOffset.Y = -FMath::FloorToFloat(UVMin.Y);
|
|
}
|
|
else if (UVMin.Y < 0.0f)
|
|
{
|
|
UVOffset.Y = 1.0f + FMath::FloorToFloat(-UVMax.Y);
|
|
}
|
|
|
|
// Note that we "wrap" the texture coordinates here to handle the case where the user
|
|
// is painting on a tiling texture, or with the UVs out of bounds. Ideally all of the
|
|
// UVs would be in the 0.0 - 1.0 range but sometimes content isn't setup that way.
|
|
// @todo MeshPaint: Handle triangles that cross the 0.0-1.0 UV boundary?
|
|
for (int32 TriVertexNum = 0; TriVertexNum < 3; ++TriVertexNum)
|
|
{
|
|
CurTriangle.TriUVs[TriVertexNum].X += UVOffset.X;
|
|
CurTriangle.TriUVs[TriVertexNum].Y += UVOffset.Y;
|
|
|
|
// @todo: Need any half-texel offset adjustments here? Some info about offsets and MSAA here: http://drilian.com/2008/11/25/understanding-half-pixel-and-half-texel-offsets/
|
|
// @todo: MeshPaint: Screen-space texture coords: http://diaryofagraphicsprogrammer.blogspot.com/2008/09/calculating-screen-space-texture.html
|
|
CurTriangle.TrianglePoints[TriVertexNum].X = CurTriangle.TriUVs[TriVertexNum].X * TextureData->PaintRenderTargetTexture->GetSurfaceWidth();
|
|
CurTriangle.TrianglePoints[TriVertexNum].Y = CurTriangle.TriUVs[TriVertexNum].Y * TextureData->PaintRenderTargetTexture->GetSurfaceHeight();
|
|
}
|
|
|
|
// Vertex positions
|
|
FVector4 Vert0(CurTriangle.TrianglePoints[0].X, CurTriangle.TrianglePoints[0].Y, 0, 1);
|
|
FVector4 Vert1(CurTriangle.TrianglePoints[1].X, CurTriangle.TrianglePoints[1].Y, 0, 1);
|
|
FVector4 Vert2(CurTriangle.TrianglePoints[2].X, CurTriangle.TrianglePoints[2].Y, 0, 1);
|
|
|
|
// Vertex color
|
|
FLinearColor Col0(CurTriangle.TriVertices[0].X, CurTriangle.TriVertices[0].Y, CurTriangle.TriVertices[0].Z);
|
|
FLinearColor Col1(CurTriangle.TriVertices[1].X, CurTriangle.TriVertices[1].Y, CurTriangle.TriVertices[1].Z);
|
|
FLinearColor Col2(CurTriangle.TriVertices[2].X, CurTriangle.TriVertices[2].Y, CurTriangle.TriVertices[2].Z);
|
|
|
|
// Brush Paint triangle
|
|
{
|
|
int32 V0 = BrushPaintBatchedElements->AddVertex(Vert0, CurTriangle.TriUVs[0], Col0, BrushPaintHitProxyId);
|
|
int32 V1 = BrushPaintBatchedElements->AddVertex(Vert1, CurTriangle.TriUVs[1], Col1, BrushPaintHitProxyId);
|
|
int32 V2 = BrushPaintBatchedElements->AddVertex(Vert2, CurTriangle.TriUVs[2], Col2, BrushPaintHitProxyId);
|
|
|
|
BrushPaintBatchedElements->AddTriangle(V0, V1, V2, MeshPaintBatchedElementParameters, SE_BLEND_Opaque);
|
|
}
|
|
|
|
// Brush Mask triangle
|
|
if (bEnableSeamPainting)
|
|
{
|
|
int32 V0 = BrushMaskBatchedElements->AddVertex(Vert0, CurTriangle.TriUVs[0], Col0, BrushMaskHitProxyId);
|
|
int32 V1 = BrushMaskBatchedElements->AddVertex(Vert1, CurTriangle.TriUVs[1], Col1, BrushMaskHitProxyId);
|
|
int32 V2 = BrushMaskBatchedElements->AddVertex(Vert2, CurTriangle.TriUVs[2], Col2, BrushMaskHitProxyId);
|
|
|
|
BrushMaskBatchedElements->AddTriangle(V0, V1, V2, MeshPaintMaskBatchedElementParameters, SE_BLEND_Opaque);
|
|
}
|
|
}
|
|
|
|
// Tell the rendering thread to draw any remaining batched elements
|
|
{
|
|
BrushPaintCanvas.Flush_GameThread(true);
|
|
|
|
TextureData->bIsPaintingTexture2DModified = true;
|
|
TextureData->PaintedComponents.AddUnique(MeshComponent);
|
|
}
|
|
|
|
ENQUEUE_RENDER_COMMAND(UpdateMeshPaintRTCommand1)(
|
|
[BrushRenderTargetResource](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
TransitionAndCopyTexture(RHICmdList, BrushRenderTargetResource->GetRenderTargetTexture(), BrushRenderTargetResource->TextureRHI, {});
|
|
});
|
|
|
|
if (bEnableSeamPainting)
|
|
{
|
|
BrushMaskCanvas->Flush_GameThread(true);
|
|
|
|
ENQUEUE_RENDER_COMMAND(UpdateMeshPaintRTCommand2)(
|
|
[BrushMaskRenderTargetResource](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
TransitionAndCopyTexture(RHICmdList, BrushMaskRenderTargetResource->GetRenderTargetTexture(), BrushMaskRenderTargetResource->TextureRHI, {});
|
|
});
|
|
}
|
|
|
|
if (!bEnableSeamPainting)
|
|
{
|
|
// Seam painting is not enabled so we just copy our delta paint info to the paint target.
|
|
UTexturePaintToolset::CopyTextureToRenderTargetTexture(TextureData->BrushRenderTargetTexture, TextureData->PaintRenderTargetTexture, FeatureLevel);
|
|
}
|
|
else
|
|
{
|
|
// Constants used for generating quads across entire paint rendertarget
|
|
const float MinU = 0.0f;
|
|
const float MinV = 0.0f;
|
|
const float MaxU = 1.0f;
|
|
const float MaxV = 1.0f;
|
|
const float MinX = 0.0f;
|
|
const float MinY = 0.0f;
|
|
const float MaxX = TextureData->PaintRenderTargetTexture->GetSurfaceWidth();
|
|
const float MaxY = TextureData->PaintRenderTargetTexture->GetSurfaceHeight();
|
|
|
|
if (TextureData->SeamMaskComponent != MeshComponent)
|
|
{
|
|
// Generate the texture seam mask. This is a slow operation when the object has many triangles so we try to only do it once when painting is started.
|
|
// @todo MeshPaint: We only store one seam mask, so when we are painting to a texture asset with multi-select we will end up re-rendering the mask each time the brush crosses component boundaries.
|
|
// Better could be to store a mask per component instead, but still lazily generate them on demand.
|
|
UTexturePaintToolset::GenerateSeamMask(MeshComponent, UVChannel, TextureData->SeamMaskRenderTargetTexture, TextureData->PaintingTexture2D, TextureData->PaintRenderTargetTexture);
|
|
TextureData->SeamMaskComponent = MeshComponent;
|
|
}
|
|
|
|
FTextureRenderTargetResource* RenderTargetResource = TextureData->PaintRenderTargetTexture->GameThread_GetRenderTargetResource();
|
|
check(RenderTargetResource != nullptr);
|
|
// Dilate the paint stroke into the texture seams.
|
|
{
|
|
// Create a canvas for the render target.
|
|
FCanvas Canvas3(RenderTargetResource, nullptr, FGameTime(), FeatureLevel);
|
|
|
|
TRefCountPtr< FMeshPaintDilateBatchedElementParameters > MeshPaintDilateBatchedElementParameters(new FMeshPaintDilateBatchedElementParameters());
|
|
{
|
|
MeshPaintDilateBatchedElementParameters->ShaderParams.Texture0 = TextureData->BrushRenderTargetTexture;
|
|
MeshPaintDilateBatchedElementParameters->ShaderParams.Texture1 = TextureData->SeamMaskRenderTargetTexture;
|
|
MeshPaintDilateBatchedElementParameters->ShaderParams.Texture2 = TextureData->BrushMaskRenderTargetTexture;
|
|
MeshPaintDilateBatchedElementParameters->ShaderParams.WidthPixelOffset = (float)(1.0f / TextureData->PaintRenderTargetTexture->GetSurfaceWidth());
|
|
MeshPaintDilateBatchedElementParameters->ShaderParams.HeightPixelOffset = (float)(1.0f / TextureData->PaintRenderTargetTexture->GetSurfaceHeight());
|
|
}
|
|
|
|
// Draw a quad to copy the texture over to the render target
|
|
TArray< FCanvasUVTri > TriangleList;
|
|
FCanvasUVTri SingleTri;
|
|
SingleTri.V0_Pos = FVector2D(MinX, MinY);
|
|
SingleTri.V0_UV = FVector2D(MinU, MinV);
|
|
SingleTri.V0_Color = FLinearColor::White;
|
|
|
|
SingleTri.V1_Pos = FVector2D(MaxX, MinY);
|
|
SingleTri.V1_UV = FVector2D(MaxU, MinV);
|
|
SingleTri.V1_Color = FLinearColor::White;
|
|
|
|
SingleTri.V2_Pos = FVector2D(MaxX, MaxY);
|
|
SingleTri.V2_UV = FVector2D(MaxU, MaxV);
|
|
SingleTri.V2_Color = FLinearColor::White;
|
|
TriangleList.Add(SingleTri);
|
|
|
|
SingleTri.V0_Pos = FVector2D(MaxX, MaxY);
|
|
SingleTri.V0_UV = FVector2D(MaxU, MaxV);
|
|
SingleTri.V0_Color = FLinearColor::White;
|
|
|
|
SingleTri.V1_Pos = FVector2D(MinX, MaxY);
|
|
SingleTri.V1_UV = FVector2D(MinU, MaxV);
|
|
SingleTri.V1_Color = FLinearColor::White;
|
|
|
|
SingleTri.V2_Pos = FVector2D(MinX, MinY);
|
|
SingleTri.V2_UV = FVector2D(MinU, MinV);
|
|
SingleTri.V2_Color = FLinearColor::White;
|
|
TriangleList.Add(SingleTri);
|
|
|
|
FCanvasTriangleItem TriItemList(TriangleList, nullptr);
|
|
TriItemList.BatchedElementParameters = MeshPaintDilateBatchedElementParameters;
|
|
TriItemList.BlendMode = SE_BLEND_Opaque;
|
|
Canvas3.DrawItem(TriItemList);
|
|
|
|
// Tell the rendering thread to draw any remaining batched elements
|
|
Canvas3.Flush_GameThread(true);
|
|
}
|
|
|
|
ENQUEUE_RENDER_COMMAND(UpdateMeshPaintRTCommand3)(
|
|
[RenderTargetResource](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
TransitionAndCopyTexture(RHICmdList, RenderTargetResource->GetRenderTargetTexture(), RenderTargetResource->TextureRHI, {});
|
|
});
|
|
}
|
|
|
|
// Need to flush the virtual texture adapter since we just updated the painting render target.
|
|
if (TextureData->PaintRenderTargetTextureAdapter)
|
|
{
|
|
TextureData->PaintRenderTargetTextureAdapter->Flush(FBox2f(FVector2f(0, 0), FVector2f(1, 1)));
|
|
}
|
|
|
|
// Need to invalidate runtime virtual textures if paint texture is used in a material that writes to one.
|
|
if (bRequiresRuntimeVirtualTextureUpdates)
|
|
{
|
|
for (TObjectIterator<URuntimeVirtualTextureComponent> It(RF_ClassDefaultObject, true, EInternalObjectFlags::Garbage); It; ++It)
|
|
{
|
|
It->Invalidate(It->Bounds, EVTInvalidatePriority::High);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UMeshTexturePaintingTool::FinishPaintingTexture()
|
|
{
|
|
if (FPaintTexture2DData* TextureData = GetPaintTargetData(PaintingTexture2D))
|
|
{
|
|
// Apply the texture
|
|
if (TextureData->bIsPaintingTexture2DModified == true)
|
|
{
|
|
const int32 TexWidth = TextureData->PaintRenderTargetTexture->SizeX;
|
|
const int32 TexHeight = TextureData->PaintRenderTargetTexture->SizeY;
|
|
TArray< FColor > TexturePixels;
|
|
TexturePixels.AddUninitialized(TexWidth * TexHeight);
|
|
|
|
// Copy the contents of the remote texture to system memory
|
|
|
|
FlushRenderingCommands();
|
|
// NOTE: You are normally not allowed to dereference this pointer on the game thread! Normally you can only pass the pointer around and
|
|
// check for NULLness. We do it in this context, however, and it is only ok because this does not happen every frame and we make sure to flush the
|
|
// rendering thread.
|
|
FTextureRenderTargetResource* RenderTargetResource = TextureData->PaintRenderTargetTexture->GameThread_GetRenderTargetResource();
|
|
check(RenderTargetResource != nullptr);
|
|
|
|
FReadSurfaceDataFlags Flags;
|
|
Flags.SetLinearToGamma(PaintingTexture2D->SRGB);
|
|
RenderTargetResource->ReadPixels(TexturePixels, Flags);
|
|
|
|
// For undo
|
|
TextureData->PaintingTexture2D->SetFlags(RF_Transactional);
|
|
TextureData->PaintingTexture2D->PreEditChange(nullptr);
|
|
|
|
// Store source art
|
|
FImageView ImageView(TexturePixels.GetData(), TexWidth, TexHeight, EGammaSpace::sRGB);
|
|
TextureData->PaintingTexture2D->Source.Init(ImageView);
|
|
|
|
TextureData->PaintingTexture2D->bHasBeenPaintedInEditor = true;
|
|
|
|
// Update the texture (generate mips, compress if needed)
|
|
TextureData->PaintingTexture2D->PostEditChange();
|
|
|
|
TextureData->bIsPaintingTexture2DModified = false;
|
|
|
|
for (UMeshComponent* PaintedComponent : TextureData->PaintedComponents)
|
|
{
|
|
OnPaintingFinishedDelegate.ExecuteIfBound(PaintedComponent);
|
|
}
|
|
TextureData->PaintedComponents.Reset();
|
|
}
|
|
}
|
|
|
|
PaintingTexture2D = nullptr;
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::ClearAllTextureOverrides()
|
|
{
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
FMaterialUpdateContext MaterialUpdateContext;
|
|
|
|
/** Remove all texture overrides which are currently stored and active */
|
|
for (decltype(PaintTargetData)::TIterator It(PaintTargetData); It; ++It)
|
|
{
|
|
FPaintTexture2DData* TextureData = &It.Value();
|
|
|
|
for (UMeshComponent* MeshComponent : TextureData->TextureOverrideComponents)
|
|
{
|
|
if (TSharedPtr<IMeshPaintComponentAdapter> PaintAdapter = MeshPaintingSubsystem->GetAdapterForComponent(MeshComponent))
|
|
{
|
|
PaintAdapter->ApplyOrRemoveTextureOverride(TextureData->PaintingTexture2D, nullptr, MaterialUpdateContext);
|
|
}
|
|
}
|
|
|
|
TextureData->TextureOverrideComponents.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::SetAllTextureOverrides()
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
TArray<UMeshComponent*> SelectedMeshComponents = MeshPaintingSubsystem->GetSelectedMeshComponents();
|
|
|
|
for (FPaintableTexture const& PaintableTexture : PaintableTextures)
|
|
{
|
|
// Apply the overrides only to the components that we are painting with this texture.
|
|
TArray<UMeshComponent*, TInlineAllocator<8>> PaintableMeshComponents;
|
|
for (UMeshComponent* MeshComponent : SelectedMeshComponents)
|
|
{
|
|
if (CanPaintTextureToComponent(PaintableTexture.Texture, MeshComponent))
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(MeshComponent);
|
|
if (IsMeshAdapterSupported(MeshAdapter))
|
|
{
|
|
PaintableMeshComponents.Add(MeshComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PaintableMeshComponents.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UTexture2D* Texture2D = Cast<UTexture2D>(PaintableTexture.Texture);
|
|
if (Texture2D == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Texture2D->BlockOnAnyAsyncBuild();
|
|
|
|
// Create the render target to paint on.
|
|
FPaintTexture2DData* TextureData = AddPaintTargetData(Texture2D);
|
|
|
|
const int32 TextureWidth = Texture2D->Source.GetSizeX();
|
|
const int32 TextureHeight = Texture2D->Source.GetSizeY();
|
|
|
|
if (TextureData->PaintRenderTargetTexture == nullptr ||
|
|
TextureData->PaintRenderTargetTexture->GetSurfaceWidth() != TextureWidth ||
|
|
TextureData->PaintRenderTargetTexture->GetSurfaceHeight() != TextureHeight)
|
|
{
|
|
TextureData->PaintRenderTargetTexture = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
TextureData->PaintRenderTargetTexture->bNeedsTwoCopies = true;
|
|
const bool bForceLinearGamma = true;
|
|
TextureData->PaintRenderTargetTexture->InitCustomFormat(TextureWidth, TextureHeight, PF_A16B16G16R16, bForceLinearGamma);
|
|
TextureData->PaintRenderTargetTexture->UpdateResourceImmediate();
|
|
|
|
TextureData->PaintRenderTargetTextureAdapter = nullptr;
|
|
if (TextureData->PaintingTexture2D->IsCurrentlyVirtualTextured())
|
|
{
|
|
// Virtual textures can't just swap in a render target in their material, so we use a virtual texture adapter.
|
|
FVirtualTextureBuildSettings VirtualTextureBuildSettings;
|
|
TextureData->PaintingTexture2D->GetVirtualTextureBuildSettings(VirtualTextureBuildSettings);
|
|
|
|
TextureData->PaintRenderTargetTextureAdapter = NewObject<UVirtualTextureAdapter>(GetTransientPackage(), NAME_None, RF_Transient);
|
|
TextureData->PaintRenderTargetTextureAdapter->Texture = TextureData->PaintRenderTargetTexture;
|
|
TextureData->PaintRenderTargetTextureAdapter->OverrideWithTextureFormat = Texture2D;
|
|
TextureData->PaintRenderTargetTextureAdapter->bUseDefaultTileSizes = false;
|
|
TextureData->PaintRenderTargetTextureAdapter->TileSize = VirtualTextureBuildSettings.TileSize;
|
|
TextureData->PaintRenderTargetTextureAdapter->TileBorderSize = VirtualTextureBuildSettings.TileBorderSize;
|
|
TextureData->PaintRenderTargetTextureAdapter->UpdateResource();
|
|
}
|
|
}
|
|
|
|
TextureData->PaintRenderTargetTexture->AddressX = Texture2D->AddressX;
|
|
TextureData->PaintRenderTargetTexture->AddressY = Texture2D->AddressY;
|
|
|
|
// Initialize the render target with the texture contents.
|
|
UTexturePaintToolset::SetupInitialRenderTargetData(TextureData->PaintingTexture2D, TextureData->PaintRenderTargetTexture);
|
|
|
|
// Need to flush the virtual texture adapter since we just updated the painting render target.
|
|
if (TextureData->PaintRenderTargetTextureAdapter)
|
|
{
|
|
TextureData->PaintRenderTargetTextureAdapter->Flush(FBox2f(FVector2f(0, 0), FVector2f(1, 1)));
|
|
}
|
|
|
|
// Apply the overrides.
|
|
FMaterialUpdateContext MaterialUpdateContext;
|
|
for (UMeshComponent* MeshComponent : PaintableMeshComponents)
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter = MeshPaintingSubsystem->GetAdapterForComponent(MeshComponent);
|
|
AddTextureOverrideToComponent(*TextureData, MeshComponent, MeshAdapter.Get(), MaterialUpdateContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshTexturePaintingTool::FloodCurrentPaintTexture()
|
|
{
|
|
bRequestPaintBucketFill = true;
|
|
}
|
|
|
|
|
|
UMeshTextureColorPaintingTool::UMeshTextureColorPaintingTool()
|
|
{
|
|
PropertyClass = UMeshTextureColorPaintingToolProperties::StaticClass();
|
|
}
|
|
|
|
void UMeshTextureColorPaintingTool::Setup()
|
|
{
|
|
Super::Setup();
|
|
ColorProperties = Cast<UMeshTextureColorPaintingToolProperties>(BrushProperties);
|
|
|
|
if (UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>())
|
|
{
|
|
// Create a dummy mesh paint virtual texture for the lifetime of the paint tool.
|
|
// This keeps at least one virtual texture alive during painting.
|
|
// Otherwise, if there is only one "real" virtual texture in the scene and we paint on it,
|
|
// it will be deallocted for one or two frames during texture compilation after each paint stroke.
|
|
// For those frames there would be _no_ remainging allocated VTs to use for the FMeshPaintVirtualTextureSceneExtension
|
|
// and which would leave no page table bound for sampling the virtual texture adaptor that wraps the
|
|
// painting render target. That would result in a flicker where the lack of page table means the mesh paint
|
|
// virtual texture gets its fallback color when sampling.
|
|
// Holding this dummy texture prevents that from happening.
|
|
MeshPaintDummyTexture = MeshPaintingSubsystem->CreateMeshPaintTexture(this, 1);
|
|
}
|
|
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartTextureColorPaintTool", "Paint colors to the Mesh Paint Texture object stored on mesh components."),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
bool UMeshTextureColorPaintingTool::IsMeshAdapterSupported(TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter) const
|
|
{
|
|
return MeshAdapter.IsValid() ? MeshAdapter->SupportsTextureColorPaint() : false;
|
|
}
|
|
|
|
UTexture2D* UMeshTextureColorPaintingTool::GetSelectedPaintTexture(UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return Cast<UTexture2D>(InMeshComponent->GetMeshPaintTexture());
|
|
}
|
|
|
|
int32 UMeshTextureColorPaintingTool::GetSelectedUVChannel(UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return InMeshComponent != nullptr ? InMeshComponent->GetMeshPaintTextureCoordinateIndex() : 0;
|
|
}
|
|
|
|
void UMeshTextureColorPaintingTool::GetModifiedTexturesToSave(TArray<UObject*>& OutTexturesToSave) const
|
|
{
|
|
for (FPaintableTexture const& PaintableTexture : PaintableTextures)
|
|
{
|
|
if (PaintableTexture.Texture->GetOutermost()->IsDirty())
|
|
{
|
|
OutTexturesToSave.Add(PaintableTexture.Texture);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMeshTextureColorPaintingTool::CacheTexturePaintData()
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem)
|
|
{
|
|
PaintableTextures.Empty();
|
|
|
|
TArray<UMeshComponent*> PaintableComponents = MeshPaintingSubsystem->GetPaintableMeshComponents();
|
|
for (UMeshComponent const* Component : PaintableComponents)
|
|
{
|
|
int32 DummyDefaultIndex;
|
|
TSharedPtr<IMeshPaintComponentAdapter> Adapter = MeshPaintingSubsystem->GetAdapterForComponent(Component);
|
|
UTexturePaintToolset::RetrieveTexturesForComponent(Component, Adapter.Get(), DummyDefaultIndex, PaintableTextures);
|
|
}
|
|
|
|
PaintableTextures.RemoveAll([](FPaintableTexture const& PaintableTexture) { return !PaintableTexture.bIsMeshTexture; });
|
|
}
|
|
}
|
|
|
|
bool UMeshTextureColorPaintingTool::CanPaintTextureToComponent(UTexture* InTexture, UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return InMeshComponent->GetMeshPaintTexture() == InTexture;
|
|
}
|
|
|
|
|
|
UMeshTextureAssetPaintingTool::UMeshTextureAssetPaintingTool()
|
|
{
|
|
PropertyClass = UMeshTextureAssetPaintingToolProperties::StaticClass();
|
|
}
|
|
|
|
void UMeshTextureAssetPaintingTool::Setup()
|
|
{
|
|
Super::Setup();
|
|
AssetProperties = Cast<UMeshTextureAssetPaintingToolProperties>(BrushProperties);
|
|
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartTexturePaintTool", "The Texture Weight Painting mode enables you to paint on textures and access available properties while doing so ."),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
bool UMeshTextureAssetPaintingTool::IsMeshAdapterSupported(TSharedPtr<IMeshPaintComponentAdapter> MeshAdapter) const
|
|
{
|
|
return MeshAdapter.IsValid() ? MeshAdapter->SupportsTexturePaint() : false;
|
|
}
|
|
|
|
UTexture2D* UMeshTextureAssetPaintingTool::GetSelectedPaintTexture(UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return AssetProperties->PaintTexture;
|
|
}
|
|
|
|
int32 UMeshTextureAssetPaintingTool::GetSelectedUVChannel(UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return AssetProperties->UVChannel;
|
|
}
|
|
|
|
void UMeshTextureAssetPaintingTool::GetModifiedTexturesToSave(TArray<UObject*>& OutTexturesToSave) const
|
|
{
|
|
if (AssetProperties->PaintTexture != nullptr && AssetProperties->PaintTexture->GetOutermost()->IsDirty())
|
|
{
|
|
OutTexturesToSave.Add(AssetProperties->PaintTexture);
|
|
}
|
|
}
|
|
|
|
bool UMeshTextureAssetPaintingTool::ShouldFilterTextureAsset(const FAssetData& AssetData) const
|
|
{
|
|
FSoftObjectPath Path = AssetData.GetSoftObjectPath();
|
|
return !(PaintableTextures.ContainsByPredicate([&Path](const FPaintableTexture& Texture) { return FSoftObjectPath(Texture.Texture) == Path; }));
|
|
}
|
|
|
|
void UMeshTextureAssetPaintingTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
|
|
{
|
|
Super::OnPropertyModified(PropertySet, Property);
|
|
|
|
if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UMeshTextureAssetPaintingToolProperties, PaintTexture)))
|
|
{
|
|
// Find the selected texture and apply it's UV channel.
|
|
for (FPaintableTexture& PaintableTexture : PaintableTextures)
|
|
{
|
|
if (PaintableTexture.Texture == AssetProperties->PaintTexture)
|
|
{
|
|
AssetProperties->UVChannel = PaintableTexture.UVChannelIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Need to recreate the render target overrides with the newly selected texture.
|
|
ClearAllTextureOverrides();
|
|
SetAllTextureOverrides();
|
|
}
|
|
}
|
|
|
|
void UMeshTextureAssetPaintingTool::CacheTexturePaintData()
|
|
{
|
|
UMeshPaintingSubsystem* MeshPaintingSubsystem = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>();
|
|
if (MeshPaintingSubsystem)
|
|
{
|
|
PaintableTextures.Reset();
|
|
|
|
UTexture* DefaultTexture = nullptr;
|
|
int32 DefaultUVChannelIndex = INDEX_NONE;
|
|
|
|
TArray<UMeshComponent*> PaintableComponents = MeshPaintingSubsystem->GetPaintableMeshComponents();
|
|
|
|
// Gather textures on first component.
|
|
if (PaintableComponents.Num() > 0)
|
|
{
|
|
TSharedPtr<IMeshPaintComponentAdapter> Adapter = MeshPaintingSubsystem->GetAdapterForComponent(PaintableComponents[0]);
|
|
int32 DefaultTextureIndex = INDEX_NONE;
|
|
UTexturePaintToolset::RetrieveTexturesForComponent(PaintableComponents[0], Adapter.Get(), DefaultTextureIndex, PaintableTextures);
|
|
if (DefaultTexture == nullptr && PaintableTextures.IsValidIndex(DefaultTextureIndex))
|
|
{
|
|
DefaultTexture = PaintableTextures[DefaultTextureIndex].Texture;
|
|
DefaultUVChannelIndex = PaintableTextures[DefaultTextureIndex].UVChannelIndex;
|
|
}
|
|
}
|
|
|
|
// If there is more than one component we only want textures that are referenced by ALL selected components.
|
|
for (int32 Index = 1; Index < PaintableComponents.Num(); ++Index)
|
|
{
|
|
UMeshComponent* PaintableComponent = PaintableComponents[Index];
|
|
TSharedPtr<IMeshPaintComponentAdapter> Adapter = MeshPaintingSubsystem->GetAdapterForComponent(PaintableComponent);
|
|
TArray<FPaintableTexture> PerComponentPaintableTextures;
|
|
int32 DefaultTextureIndex = INDEX_NONE;
|
|
UTexturePaintToolset::RetrieveTexturesForComponent(PaintableComponent, Adapter.Get(), DefaultTextureIndex, PerComponentPaintableTextures);
|
|
if (DefaultTexture == nullptr && PerComponentPaintableTextures.IsValidIndex(DefaultTextureIndex))
|
|
{
|
|
DefaultTexture = PerComponentPaintableTextures[DefaultTextureIndex].Texture;
|
|
DefaultUVChannelIndex = PerComponentPaintableTextures[DefaultTextureIndex].UVChannelIndex;
|
|
}
|
|
|
|
TArray<FPaintableTexture> CommonPaintableTextures;
|
|
for (FPaintableTexture const& PaintableTexture : PerComponentPaintableTextures)
|
|
{
|
|
if (PaintableTextures.Contains(PaintableTexture))
|
|
{
|
|
CommonPaintableTextures.Add(PaintableTexture);
|
|
}
|
|
}
|
|
PaintableTextures = MoveTemp(CommonPaintableTextures);
|
|
}
|
|
|
|
PaintableTextures.RemoveAll([](FPaintableTexture const& PaintableTexture) { return PaintableTexture.bIsMeshTexture; });
|
|
|
|
// Ensure that the selection remains valid or is invalidated
|
|
int32 SelectedIndex = INDEX_NONE;
|
|
|
|
if (AssetProperties->PaintTexture != nullptr)
|
|
{
|
|
// First try to find fully matching entry, then by texture only (a texture may appear with multiple UV channels).
|
|
SelectedIndex = PaintableTextures.Find(FPaintableTexture(AssetProperties->PaintTexture, AssetProperties->UVChannel));
|
|
if (SelectedIndex == INDEX_NONE)
|
|
{
|
|
SelectedIndex = PaintableTextures.IndexOfByPredicate([&](const FPaintableTexture& Texture) { return Texture.Texture == AssetProperties->PaintTexture; });
|
|
}
|
|
}
|
|
if (SelectedIndex == INDEX_NONE && DefaultTexture != nullptr)
|
|
{
|
|
SelectedIndex = PaintableTextures.Find(FPaintableTexture(DefaultTexture, DefaultUVChannelIndex));
|
|
if (SelectedIndex == INDEX_NONE)
|
|
{
|
|
SelectedIndex = PaintableTextures.IndexOfByPredicate([&](const FPaintableTexture& Texture) { return Texture.Texture == DefaultTexture; });
|
|
}
|
|
}
|
|
if (SelectedIndex == INDEX_NONE && PaintableTextures.Num() > 0)
|
|
{
|
|
SelectedIndex = 0;
|
|
}
|
|
|
|
AssetProperties->PaintTexture = SelectedIndex == INDEX_NONE ? nullptr : Cast<UTexture2D>(PaintableTextures[SelectedIndex].Texture);
|
|
AssetProperties->UVChannel = SelectedIndex == INDEX_NONE ? INDEX_NONE : PaintableTextures[SelectedIndex].UVChannelIndex;
|
|
}
|
|
}
|
|
|
|
bool UMeshTextureAssetPaintingTool::CanPaintTextureToComponent(UTexture* InTexture, UMeshComponent const* InMeshComponent) const
|
|
{
|
|
return InTexture == AssetProperties->PaintTexture;
|
|
}
|
|
|
|
UTexture* UMeshTextureAssetPaintingTool::GetSelectedPaintTextureWithOverride() const
|
|
{
|
|
UTexture* SelectedTexture = AssetProperties->PaintTexture;
|
|
if (AssetProperties->PaintTexture != nullptr)
|
|
{
|
|
FPaintTexture2DData const* TextureData = PaintTargetData.Find(AssetProperties->PaintTexture);
|
|
if (TextureData != nullptr && TextureData->PaintRenderTargetTexture)
|
|
{
|
|
SelectedTexture = TextureData->PaintRenderTargetTexture;
|
|
}
|
|
}
|
|
return SelectedTexture;
|
|
}
|
|
|
|
void UMeshTextureAssetPaintingTool::CycleTextures(int32 Direction)
|
|
{
|
|
if (!PaintableTextures.Num())
|
|
{
|
|
return;
|
|
}
|
|
TObjectPtr<UTexture2D>& SelectedTexture = AssetProperties->PaintTexture;
|
|
const int32 TextureIndex = (SelectedTexture != nullptr) ? PaintableTextures.IndexOfByKey(SelectedTexture) : 0;
|
|
if (TextureIndex != INDEX_NONE)
|
|
{
|
|
int32 NewTextureIndex = TextureIndex + Direction;
|
|
if (NewTextureIndex < 0)
|
|
{
|
|
NewTextureIndex += PaintableTextures.Num();
|
|
}
|
|
NewTextureIndex %= PaintableTextures.Num();
|
|
|
|
if (PaintableTextures.IsValidIndex(NewTextureIndex))
|
|
{
|
|
SelectedTexture = (UTexture2D*)PaintableTextures[NewTextureIndex].Texture;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|