Files
UnrealEngine/Engine/Plugins/MeshPainting/Source/MeshPaintEditorMode/Private/MeshPaintModeHelpers.cpp
2025-05-18 13:04:45 +08:00

974 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshPaintModeHelpers.h"
#include "ComponentReregisterContext.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/Texture2D.h"
#include "IDesktopPlatform.h"
#include "SceneView.h"
#include "InteractiveToolManager.h"
#include "StaticMeshComponentLODInfo.h"
#include "InterchangeGenericAssetsPipelineSharedSettings.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "Rendering/SkeletalMeshLODModel.h"
#include "Utils.h"
#include "Framework/Application/SlateApplication.h"
#include "ImportVertexColorOptions.h"
#include "EditorViewportClient.h"
#include "Interfaces/IMainFrameModule.h"
#include "DesktopPlatformModule.h"
#include "PackageTools.h"
#include "LevelEditor.h"
#include "IAssetViewport.h"
#include "EditorViewportClient.h"
#include "Factories/FbxSkeletalMeshImportData.h"
#include "Rendering/SkeletalMeshModel.h"
#include "MeshPaintHelpers.h"
#include "MeshPaintMode.h"
#include "MeshPaintVisualize.h"
#include "MeshTexturePaintingTool.h"
#include "MeshVertexPaintingTool.h"
#include "InterchangeAssetImportData.h"
#include "InterchangeGenericAssetsPipeline.h"
#include "InterchangePythonPipelineBase.h"
#include "Math/Color.h"
#include "StaticMeshLODResourcesToDynamicMesh.h"
#include "Sampling/MeshMapBaker.h"
#include "Sampling/MeshPropertyMapEvaluator.h"
#include "VT/MeshPaintVirtualTexture.h"
void UMeshPaintModeSubsystem::SetViewportColorMode(EMeshPaintActiveMode ActiveMode, EMeshPaintDataColorViewMode ColorViewMode, FEditorViewportClient* ViewportClient, UInteractiveTool const* ActiveTool)
{
if (ViewportClient->IsPerspective())
{
// Update viewport show flags
{
// show flags forced on during vertex color modes
if (ColorViewMode == EMeshPaintDataColorViewMode::Normal)
{
if (ViewportClient->EngineShowFlags.VertexColors)
{
// Clear the flags relevant to vertex color modes
ViewportClient->EngineShowFlags.SetVertexColors(false);
ViewportClient->CurrentNaniteVisualizationMode = NAME_None;
// Restore the vertex color mode flags that were set when we last entered vertex color mode
ApplyViewMode(ViewportClient->GetViewMode(), ViewportClient->IsPerspective(), ViewportClient->EngineShowFlags);
MeshPaintVisualize::SetPaintMode(EMeshPaintVisualizePaintMode::VertexColor);
MeshPaintVisualize::SetShowMode(EMeshPaintVisualizeShowMode::ShowAll);
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Color);
MeshPaintVisualize::SetTextureAsset(nullptr);
}
}
else
{
ViewportClient->EngineShowFlags.SetVertexColors(true);
MeshPaintVisualize::SetShowMode(EMeshPaintVisualizeShowMode::ShowSelected);
switch (ColorViewMode)
{
case EMeshPaintDataColorViewMode::RGB:
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Color);
break;
case EMeshPaintDataColorViewMode::Alpha:
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Alpha);
break;
case EMeshPaintDataColorViewMode::Red:
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Red);
break;
case EMeshPaintDataColorViewMode::Green:
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Green);
break;
case EMeshPaintDataColorViewMode::Blue:
MeshPaintVisualize::SetChannelMode(EVertexColorViewMode::Blue);
break;
}
UTexture* SelectedTexture = nullptr;
int32 UVChannel = 0;
if (ActiveMode == EMeshPaintActiveMode::Texture)
{
UMeshTextureAssetPaintingTool const* TextureTool = Cast<UMeshTextureAssetPaintingTool>(ActiveTool);
if (TextureTool != nullptr)
{
SelectedTexture = TextureTool->GetSelectedPaintTextureWithOverride();
UVChannel = TextureTool->GetSelectedUVChannel(nullptr);
}
}
static FName NAME_VertexColor("VertexColor");
static FName NAME_MeshPaintTexture("MeshPaintTexture");
switch (ActiveMode)
{
case EMeshPaintActiveMode::VertexColor:
case EMeshPaintActiveMode::VertexWeights:
MeshPaintVisualize::SetPaintMode(EMeshPaintVisualizePaintMode::VertexColor);
ViewportClient->EngineShowFlags.SetVisualizeNanite(true);
ViewportClient->CurrentNaniteVisualizationMode = NAME_VertexColor;
break;
case EMeshPaintActiveMode::TextureColor:
MeshPaintVisualize::SetPaintMode(EMeshPaintVisualizePaintMode::TextureColor);
ViewportClient->EngineShowFlags.SetVisualizeNanite(true);
ViewportClient->CurrentNaniteVisualizationMode = NAME_MeshPaintTexture;
break;
case EMeshPaintActiveMode::Texture:
MeshPaintVisualize::SetPaintMode(EMeshPaintVisualizePaintMode::TextureAsset);
ViewportClient->EngineShowFlags.SetVisualizeNanite(SelectedTexture != nullptr);
ViewportClient->CurrentNaniteVisualizationMode = SelectedTexture != nullptr ? NAME_MeshPaintTexture : NAME_None;
break;
}
MeshPaintVisualize::SetTextureAsset(SelectedTexture);
MeshPaintVisualize::SetTextureCoordinateIndex(UVChannel);
}
}
}
}
void UMeshPaintModeSubsystem::SetRealtimeViewport(FEditorViewportClient* ViewportClient, bool bRealtime)
{
if (ViewportClient != nullptr)
{
if (ViewportClient->IsPerspective())
{
static const FText SystemDisplayName = NSLOCTEXT("MeshPaint", "RealtimeOverrideMessage_MeshPaint", "Mesh Paint");
if (bRealtime)
{
if (!ViewportClient->HasRealtimeOverride(SystemDisplayName))
{
ViewportClient->AddRealtimeOverride(bRealtime, SystemDisplayName);
}
}
else
{
if (ViewportClient->HasRealtimeOverride(SystemDisplayName))
{
ViewportClient->RemoveRealtimeOverride(SystemDisplayName);
}
}
}
}
}
void UMeshPaintModeSubsystem::ImportVertexColorsFromTexture(UMeshComponent* MeshComponent)
{
checkf(MeshComponent != nullptr, TEXT("Invalid mesh component ptr"));
// Get TGA texture filepath
FString ChosenFilename("");
FString ExtensionStr;
ExtensionStr += TEXT("TGA Files|*.tga|");
FString PromptTitle("Pick TGA Texture File");
// First, display the file open dialog for selecting the file.
TArray<FString> Filenames;
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
bool bOpen = false;
if (DesktopPlatform)
{
bOpen = DesktopPlatform->OpenFileDialog(
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
PromptTitle,
TEXT(""),
TEXT(""),
*ExtensionStr,
EFileDialogFlags::None,
Filenames
);
}
if (bOpen && Filenames.Num() == 1)
{
// Valid file name picked
const FString FileName = Filenames[0];
UTexture2D* ColorTexture = ImportObject<UTexture2D>(GEngine, NAME_None, RF_Public, *FileName, nullptr, nullptr, TEXT("NOMIPMAPS=1 NOCOMPRESSION=1"));
if (ColorTexture && ColorTexture->Source.GetFormat() == TSF_BGRA8)
{
// Have a valid texture, now need user to specify options for importing
TSharedRef<SWindow> Window = SNew(SWindow)
.Title(FText::FromString(TEXT("Vertex Color Import Options")))
.SizingRule(ESizingRule::Autosized);
TSharedPtr<SImportVertexColorOptionsWindow> OptionsWindow = SNew(SImportVertexColorOptionsWindow).WidgetWindow(Window)
.WidgetWindow(Window)
.Component(MeshComponent)
.FullPath(FText::FromString(ChosenFilename));
Window->SetContent
(
OptionsWindow->AsShared()
);
TSharedPtr<SWindow> ParentWindow;
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
{
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
ParentWindow = MainFrame.GetParentWindow();
}
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
if (OptionsWindow->ShouldImport())
{
// Options specified and start importing
UImportVertexColorOptions* Options = OptionsWindow->GetOptions();
if (MeshComponent->IsA<UStaticMeshComponent>())
{
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
if (StaticMeshComponent)
{
if (Options->bImportToInstance)
{
// Import colors to static mesh / component
ImportVertexColorsToStaticMeshComponent(StaticMeshComponent, Options, ColorTexture);
}
else
{
if (StaticMeshComponent->GetStaticMesh())
{
ImportVertexColorsToStaticMesh(StaticMeshComponent->GetStaticMesh(), Options, ColorTexture);
}
}
}
}
else if (MeshComponent->IsA<USkeletalMeshComponent>())
{
USkeletalMeshComponent* SkeletalMeshComponent = Cast<USkeletalMeshComponent>(MeshComponent);
if (SkeletalMeshComponent->GetSkeletalMeshAsset())
{
// Import colors to skeletal mesh
ImportVertexColorsToSkeletalMesh(SkeletalMeshComponent->GetSkeletalMeshAsset(), Options, ColorTexture);
}
}
}
}
else if (!ColorTexture)
{
// Unable to import file
}
else if (ColorTexture && ColorTexture->Source.GetFormat() != TSF_BGRA8)
{
// Able to import file but incorrect format
}
}
}
void UMeshPaintModeSubsystem::ImportVertexColorsFromMeshPaintTexture(UMeshComponent* MeshComponent)
{
if (UTexture2D* Texture = Cast<UTexture2D>(MeshComponent->GetMeshPaintTexture()))
{
#if WITH_EDITOR
// We may need to wait for the texture to compile before importing.
// This is most likely to happen when we are immediately propagating texture color painting to vertex colors.
Texture->BlockOnAnyAsyncBuild();
#endif
UImportVertexColorOptions* Options = NewObject<UImportVertexColorOptions>();
Options->UVIndex = MeshComponent->GetMeshPaintTextureCoordinateIndex();
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent))
{
ImportVertexColorsToStaticMeshComponent(StaticMeshComponent, Options, Texture);
}
}
}
void UMeshPaintModeSubsystem::ImportMeshPaintTextureFromVertexColors(UMeshComponent* MeshComponent)
{
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(MeshComponent);
if (StaticMeshComponent == nullptr)
{
return;
}
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh == nullptr)
{
return;
}
const int32 LodIndex = 0;
FStaticMeshComponentLODInfo* InstanceMeshLODInfo = StaticMeshComponent->LODData.IsValidIndex(LodIndex) ? &StaticMeshComponent->LODData[LodIndex] : nullptr;
const bool bHasPerInstanceVertexColors = InstanceMeshLODInfo != nullptr && InstanceMeshLODInfo->OverrideVertexColors != nullptr;
UE::Geometry::FStaticMeshLODResourcesToDynamicMesh::ConversionOptions ConversionOptions;
ConversionOptions.bWantTangents = false;
ConversionOptions.bWantMaterialIDs = false;
UE::Geometry::FDynamicMesh3 DynamicMesh;
UE::Geometry::FStaticMeshLODResourcesToDynamicMesh Converter;
Converter.Convert(
&StaticMesh->GetRenderData()->LODResources[LodIndex],
ConversionOptions,
DynamicMesh,
bHasPerInstanceVertexColors,
[InstanceMeshLODInfo](int32 Index)
{
return InstanceMeshLODInfo->OverrideVertexColors->VertexColor(Index);
});
const int32 TextureSize = StaticMeshComponent->GetMeshPaintTextureResolution();
const UE::Geometry::FDynamicMeshAABBTree3 DetailSpatial(&DynamicMesh);
UE::Geometry::FMeshBakerDynamicMeshSampler DetailSampler(&DynamicMesh, &DetailSpatial);
TSharedPtr<UE::Geometry::FMeshPropertyMapEvaluator, ESPMode::ThreadSafe> PropertyEval = MakeShared<UE::Geometry::FMeshPropertyMapEvaluator, ESPMode::ThreadSafe>();
PropertyEval->Property = UE::Geometry::EMeshPropertyMapType::VertexColor;
UE::Geometry::FMeshMapBaker Baker;
Baker.SetTargetMesh(&DynamicMesh);
Baker.SetDetailSampler(&DetailSampler);
Baker.AddEvaluator(PropertyEval);
Baker.SetTargetMeshUVLayer(StaticMeshComponent->GetMeshPaintTextureCoordinateIndex());
Baker.SetDimensions(UE::Geometry::FImageDimensions(TextureSize, TextureSize));
Baker.SetProjectionDistance(3.0f);
Baker.SetSamplesPerPixel(1);
Baker.SetFilter(UE::Geometry::FMeshMapBaker::EBakeFilterType::BSpline);
Baker.SetGutterEnabled(true);
Baker.SetGutterSize(4);
Baker.Bake();
FImageView ResultImage((FLinearColor*)Baker.GetBakeResults(0)[0]->GetImageBuffer().GetData(), TextureSize, TextureSize);
FImage ConvertedImage;
ResultImage.CopyTo(ConvertedImage, ERawImageFormat::BGRA8, EGammaSpace::sRGB);
UMeshPaintVirtualTexture* NewTexture = NewObject<UMeshPaintVirtualTexture>(StaticMeshComponent->GetOutermost());
NewTexture->Source.Init(ConvertedImage);
NewTexture->OwningComponent = MakeWeakObjectPtr(StaticMeshComponent);
NewTexture->UpdateResource();
StaticMeshComponent->Modify();
StaticMeshComponent->SetMeshPaintTexture(NewTexture);
}
void UMeshPaintModeSubsystem::ImportVertexColorsToSkeletalMesh(USkeletalMesh* SkeletalMesh, const UImportVertexColorOptions* Options, UTexture2D* Texture)
{
checkf(SkeletalMesh && Options && Texture, TEXT("Invalid ptr"));
// Extract color data from texture
// todo: better to use GetMipImage rather than GetMipData
TArray64<uint8> SrcMipData;
verify( Texture->Source.GetMipData(SrcMipData, 0) );
const uint8* MipData = SrcMipData.GetData();
TUniquePtr< FSkinnedMeshComponentRecreateRenderStateContext > RecreateRenderStateContext;
FSkeletalMeshRenderData* Resource = SkeletalMesh->GetResourceForRendering();
const int32 ImportLOD = Options->LODIndex;
const int32 UVIndex = Options->UVIndex;
const FColor ColorMask = Options->CreateColorMask();
if (Resource && Resource->LODRenderData.IsValidIndex(ImportLOD))
{
RecreateRenderStateContext = MakeUnique<FSkinnedMeshComponentRecreateRenderStateContext>(SkeletalMesh);
SkeletalMesh->Modify();
SkeletalMesh->ReleaseResources();
SkeletalMesh->ReleaseResourcesFence.Wait();
FSkeletalMeshLODRenderData& LODData = Resource->LODRenderData[ImportLOD];
if (LODData.StaticVertexBuffers.ColorVertexBuffer.GetNumVertices() == 0)
{
LODData.StaticVertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor::White, LODData.GetNumVertices());
BeginInitResource(&LODData.StaticVertexBuffers.ColorVertexBuffer);
}
for (uint32 VertexIndex = 0; VertexIndex < LODData.GetNumVertices(); ++VertexIndex)
{
const FVector2D UV = FVector2D(LODData.StaticVertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, UVIndex));
LODData.StaticVertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex) = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->PickVertexColorFromTextureData(MipData, UV, Texture, ColorMask);
}
SkeletalMesh->InitResources();
}
checkf(SkeletalMesh->GetImportedModel()->LODModels.IsValidIndex(ImportLOD), TEXT("Invalid Imported Model index for vertex painting"));
FSkeletalMeshLODModel& LODModel = SkeletalMesh->GetImportedModel()->LODModels[ImportLOD];
const uint32 NumVertices = LODModel.NumVertices;
for (uint32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
{
int32 SectionIndex = INDEX_NONE;
int32 SectionVertexIndex = INDEX_NONE;
LODModel.GetSectionFromVertexIndex(VertexIndex, SectionIndex, SectionVertexIndex);
const FVector2D UV = FVector2D(LODModel.Sections[SectionIndex].SoftVertices[SectionVertexIndex].UVs[UVIndex]);
LODModel.Sections[SectionIndex].SoftVertices[SectionVertexIndex].Color = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->PickVertexColorFromTextureData(MipData, UV, Texture, ColorMask);
}
//Make sure we change the import data so the re-import do not replace the new data
if (SkeletalMesh->GetAssetImportData())
{
UFbxSkeletalMeshImportData* ImportData = Cast<UFbxSkeletalMeshImportData>(SkeletalMesh->GetAssetImportData());
if (ImportData && ImportData->VertexColorImportOption != EVertexColorImportOption::Ignore)
{
ImportData->SetFlags(RF_Transactional);
ImportData->Modify();
ImportData->VertexColorImportOption = EVertexColorImportOption::Ignore;
}
UInterchangeAssetImportData* InterchangeAssetImportData = Cast<UInterchangeAssetImportData>(SkeletalMesh->GetAssetImportData());
if (InterchangeAssetImportData)
{
TArray<UObject*> Pipelines = InterchangeAssetImportData->GetPipelines();
for (UObject* PipelineBase : Pipelines)
{
UInterchangeGenericAssetsPipeline* GenericAssetPipeline = Cast<UInterchangeGenericAssetsPipeline>(PipelineBase);
if (GenericAssetPipeline)
{
if (GenericAssetPipeline->CommonMeshesProperties && GenericAssetPipeline->CommonMeshesProperties->VertexColorImportOption != EInterchangeVertexColorImportOption::IVCIO_Ignore)
{
GenericAssetPipeline->SetFlags(RF_Transactional);
GenericAssetPipeline->Modify();
GenericAssetPipeline->CommonMeshesProperties->VertexColorImportOption = EInterchangeVertexColorImportOption::IVCIO_Ignore;
}
}
}
}
}
}
bool UMeshPaintModeSubsystem::RetrieveViewportPaintRays(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI, TArray<FPaintRay>& OutPaintRays)
{
checkf(View && Viewport && PDI, TEXT("Invalid Viewport data"));
FEditorViewportClient* ViewportClient = (FEditorViewportClient*)Viewport->GetClient();
checkf(ViewportClient != nullptr, TEXT("Unable to retrieve viewport client"));
if (ViewportClient->IsPerspective())
{
{
// Else we're painting with mouse
// Make sure the cursor is visible OR we're flood filling. No point drawing a paint cue when there's no cursor.
if (Viewport->IsCursorVisible())
{
if (!PDI->IsHitTesting())
{
// Grab the mouse cursor position
FIntPoint MousePosition;
Viewport->GetMousePos(MousePosition);
// Is the mouse currently over the viewport? or flood filling
if ((MousePosition.X >= 0 && MousePosition.Y >= 0 && MousePosition.X < (int32)Viewport->GetSizeXY().X && MousePosition.Y < (int32)Viewport->GetSizeXY().Y))
{
// Compute a world space ray from the screen space mouse coordinates
FViewportCursorLocation MouseViewportRay(View, ViewportClient, MousePosition.X, MousePosition.Y);
FPaintRay& NewPaintRay = *new(OutPaintRays) FPaintRay();
NewPaintRay.CameraLocation = View->ViewMatrices.GetViewOrigin();
NewPaintRay.RayStart = MouseViewportRay.GetOrigin();
NewPaintRay.RayDirection = MouseViewportRay.GetDirection();
NewPaintRay.ViewportInteractor = nullptr;
}
}
}
}
}
return false;
}
void UMeshPaintModeSubsystem::ImportVertexColorsToStaticMesh(UStaticMesh* StaticMesh, const UImportVertexColorOptions* Options, UTexture2D* Texture)
{
checkf(StaticMesh && Options && Texture, TEXT("Invalid ptr"));
// Extract color data from texture
// todo: better to use GetMipImage rather than GetMipData
TArray64<uint8> SrcMipData;
verify( Texture->Source.GetMipData(SrcMipData, 0) );
const uint8* MipData = SrcMipData.GetData();
TUniquePtr< FStaticMeshComponentRecreateRenderStateContext > RecreateRenderStateContext = MakeUnique<FStaticMeshComponentRecreateRenderStateContext>(StaticMesh);
const int32 ImportLOD = Options->LODIndex;
FStaticMeshLODResources& LODModel = StaticMesh->GetRenderData()->LODResources[ImportLOD];
// Dirty the mesh
StaticMesh->Modify();
// Release the static mesh's resources.
StaticMesh->ReleaseResources();
// Flush the resource release commands to the rendering thread to ensure that the build doesn't occur while a resource is still
// allocated, and potentially accessing the UStaticMesh.
StaticMesh->ReleaseResourcesFence.Wait();
if (LODModel.VertexBuffers.ColorVertexBuffer.GetNumVertices() == 0)
{
// Mesh doesn't have a color vertex buffer yet! We'll create one now.
LODModel.VertexBuffers.ColorVertexBuffer.InitFromSingleColor(FColor::White, LODModel.GetNumVertices());
// @todo MeshPaint: Make sure this is the best place to do this
BeginInitResource(&LODModel.VertexBuffers.ColorVertexBuffer);
}
const int32 UVIndex = Options->UVIndex;
const FColor ColorMask = Options->CreateColorMask();
for (uint32 VertexIndex = 0; VertexIndex < LODModel.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); ++VertexIndex)
{
const FVector2D UV = FVector2D(LODModel.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, UVIndex));
LODModel.VertexBuffers.ColorVertexBuffer.VertexColor(VertexIndex) = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->PickVertexColorFromTextureData(MipData, UV, Texture, ColorMask);
}
// Make sure colors are saved into raw mesh
StaticMesh->InitResources();
}
void UMeshPaintModeSubsystem::ImportVertexColorsToStaticMeshComponent(UStaticMeshComponent* StaticMeshComponent, const UImportVertexColorOptions* Options, UTexture2D* Texture)
{
checkf(StaticMeshComponent && Options && Texture, TEXT("Invalid ptr"));
// Extract color data from texture
// todo: better to use GetMipImage rather than GetMipData
TArray64<uint8> SrcMipData;
verify( Texture->Source.GetMipData(SrcMipData, 0) );
const uint8* MipData = SrcMipData.GetData();
TUniquePtr< FComponentReregisterContext > ComponentReregisterContext;
const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
if (Mesh)
{
ComponentReregisterContext = MakeUnique<FComponentReregisterContext>(StaticMeshComponent);
StaticMeshComponent->Modify();
const int32 ImportLOD = Options->LODIndex;
const FStaticMeshLODResources& LODModel = Mesh->GetRenderData()->LODResources[ImportLOD];
if (!StaticMeshComponent->LODData.IsValidIndex(ImportLOD))
{
StaticMeshComponent->SetLODDataCount(ImportLOD + 1, StaticMeshComponent->LODData.Num());
}
FStaticMeshComponentLODInfo& InstanceMeshLODInfo = StaticMeshComponent->LODData[ImportLOD];
if (InstanceMeshLODInfo.OverrideVertexColors)
{
InstanceMeshLODInfo.ReleaseOverrideVertexColorsAndBlock();
}
// Setup the instance vertex color array
InstanceMeshLODInfo.OverrideVertexColors = new FColorVertexBuffer;
if ((int32)LODModel.VertexBuffers.ColorVertexBuffer.GetNumVertices() == LODModel.GetNumVertices())
{
// copy mesh vertex colors to the instance ones
InstanceMeshLODInfo.OverrideVertexColors->InitFromColorArray(&LODModel.VertexBuffers.ColorVertexBuffer.VertexColor(0), LODModel.GetNumVertices());
}
else
{
// Original mesh didn't have any colors, so just use a default color
InstanceMeshLODInfo.OverrideVertexColors->InitFromSingleColor(FColor::White, LODModel.GetNumVertices());
}
if (ImportLOD > 0)
{
StaticMeshComponent->bCustomOverrideVertexColorPerLOD = true;
}
const int32 UVIndex = Options->UVIndex;
const FColor ColorMask = Options->CreateColorMask();
for (uint32 VertexIndex = 0; VertexIndex < LODModel.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); ++VertexIndex)
{
const FVector2D UV = FVector2D(LODModel.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(VertexIndex, UVIndex));
InstanceMeshLODInfo.OverrideVertexColors->VertexColor(VertexIndex) = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->PickVertexColorFromTextureData(MipData, UV, Texture, ColorMask);
}
//Update the cache painted vertices
InstanceMeshLODInfo.PaintedVertices.Empty();
StaticMeshComponent->CachePaintedDataIfNecessary();
BeginInitResource(InstanceMeshLODInfo.OverrideVertexColors);
}
else
{
// Error
}
}
void UMeshPaintModeSubsystem::PropagateVertexColors(const TArray<UStaticMeshComponent *> StaticMeshComponents)
{
bool SomePaintWasPropagated = false;
TUniquePtr< FComponentReregisterContext > ComponentReregisterContext;
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
UStaticMesh* Mesh = Component->GetStaticMesh();
for (int32 LODIndex = 0; LODIndex < Mesh->GetRenderData()->LODResources.Num(); LODIndex++)
{
// Will not be guaranteed to match render data as user can paint to a specific LOD index
if (Component->LODData.IsValidIndex(LODIndex))
{
FStaticMeshComponentLODInfo& InstanceMeshLODInfo = Component->LODData[LODIndex];
if (InstanceMeshLODInfo.OverrideVertexColors)
{
Mesh->Modify();
// Try using the mapping generated when building the mesh.
if (GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->PropagateColorsToRawMesh(Mesh, LODIndex, InstanceMeshLODInfo))
{
SomePaintWasPropagated = true;
}
}
}
}
if (SomePaintWasPropagated)
{
ComponentReregisterContext = MakeUnique<FComponentReregisterContext>(Component);
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->RemoveComponentInstanceVertexColors(Component);
Mesh->Build();
}
}
}
bool UMeshPaintModeSubsystem::CanPropagateVertexColors(TArray<UStaticMeshComponent*>& StaticMeshComponents, TArray<UStaticMesh*>& StaticMeshes, int32 NumInstanceVertexColorBytes)
{
bool bValid = StaticMeshComponents.Num() > 0;
for (const UStaticMeshComponent* Component : StaticMeshComponents)
{
UStaticMesh* StaticMesh = Component->GetStaticMesh();
// Check for components painting to the same static mesh
const bool bDuplicateSelection = StaticMesh != nullptr && StaticMeshes.Contains(StaticMesh);
if (bDuplicateSelection)
{
bValid = false;
break;
}
if (StaticMesh != nullptr)
{
// Disallow propagation of vertex colors to cooked static mesh assets.
if (StaticMesh->GetOutermost()->bIsCookedForEditor)
{
bValid = false;
break;
}
StaticMeshes.AddUnique(StaticMesh);
}
int32 CachedLODIndex = 0;
if (UMeshVertexColorPaintingTool* ColorPaintingTool = Cast<UMeshVertexColorPaintingTool>(UMeshPaintMode::GetMeshPaintMode()->GetToolManager()->GetActiveTool(EToolSide::Left)))
{
CachedLODIndex = ColorPaintingTool->GetCachedLODIndex();
}
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetInstanceColorDataInfo(Component, CachedLODIndex, NumInstanceVertexColorBytes);
}
return bValid && (NumInstanceVertexColorBytes > 0);
}
void UMeshPaintModeSubsystem::CopyVertexColors(const TArray<UStaticMeshComponent *> StaticMeshComponents, TArray<FPerComponentVertexColorData>& CopiedVertexColors)
{
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
/** Make sure we have valid data to copy from */
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
const UStaticMesh* StaticMesh = Component->GetStaticMesh();
ensure(StaticMesh != nullptr);
if (StaticMesh)
{
// Create copy structure instance for this mesh
FPerComponentVertexColorData ComponentData(StaticMesh, Component->GetBlueprintCreatedComponentIndex());
const int32 NumLODs = StaticMesh->GetNumLODs();
ComponentData.PerLODVertexColorData.AddDefaulted(NumLODs);
// Retrieve and store vertex colors for each LOD in the mesh
for (int32 LODIndex = 0; LODIndex < NumLODs; ++LODIndex)
{
FPerLODVertexColorData& LODData = ComponentData.PerLODVertexColorData[LODIndex];
TArray<FColor> ColorData;
TArray<FVector> VertexData;
if (Component->LODData.IsValidIndex(LODIndex) && (Component->LODData[LODIndex].OverrideVertexColors != nullptr))
{
ColorData = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetInstanceColorDataForLOD(Component, LODIndex);
}
else
{
ColorData = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetColorDataForLOD(StaticMesh, LODIndex);
}
VertexData = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetVerticesForLOD(StaticMesh, LODIndex);
const bool bValidColorData = VertexData.Num() == ColorData.Num();
for (int32 VertexIndex = 0; VertexIndex < VertexData.Num(); ++VertexIndex)
{
const FColor& Color = bValidColorData ? ColorData[VertexIndex] : FColor::White;
LODData.ColorsByIndex.Add(Color);
LODData.ColorsByPosition.Add(VertexData[VertexIndex], Color);
}
}
CopiedVertexColors.Add(ComponentData);
}
}
}
bool UMeshPaintModeSubsystem::CanCopyInstanceVertexColors(const TArray<UStaticMeshComponent*>& StaticMeshComponents, int32 PaintingMeshLODIndex)
{
// Ensure that the selection does not contain two components which point to identical meshes
TArray<const UStaticMesh*> ContainedMeshes;
bool bValidSelection = true;
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
if (Component->GetStaticMesh() != nullptr)
{
const UStaticMesh* StaticMesh = Component->GetStaticMesh();
if (!ContainedMeshes.Contains(StaticMesh))
{
ContainedMeshes.Add(StaticMesh);
}
else
{
bValidSelection = false;
break;
}
}
}
int32 NumValidMeshes = 0;
// Retrieve per instance vertex color information (only valid if the component contains actual instance vertex colors)
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
if (Component->GetStaticMesh() != nullptr && Component->GetStaticMesh()->GetNumLODs() > (int32)PaintingMeshLODIndex)
{
uint32 BufferSize = GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->GetVertexColorBufferSize(Component, PaintingMeshLODIndex, true);
if (BufferSize > 0)
{
++NumValidMeshes;
}
}
}
return bValidSelection && (NumValidMeshes != 0);
}
void UMeshPaintModeSubsystem::PasteVertexColors(const TArray<UStaticMeshComponent*>& StaticMeshComponents, TArray<FPerComponentVertexColorData>& CopiedColorsByComponent)
{
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
TUniquePtr< FComponentReregisterContext > ComponentReregisterContext;
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
UStaticMesh* Mesh = Component->GetStaticMesh();
if (Mesh && Mesh->GetNumLODs() > 0)
{
// See if there is a valid instance of copied vertex colors for this mesh
const int32 BlueprintCreatedComponentIndex = Component->GetBlueprintCreatedComponentIndex();
FPerComponentVertexColorData* PasteColors = CopiedColorsByComponent.FindByPredicate([=](const FPerComponentVertexColorData& ComponentData)
{
return (ComponentData.OriginalMesh.Get() == Mesh && ComponentData.ComponentIndex == BlueprintCreatedComponentIndex);
});
if (PasteColors)
{
ComponentReregisterContext = MakeUnique<FComponentReregisterContext>(Component);
const int32 NumLods = Mesh->GetNumLODs();
Component->SetFlags(RF_Transactional);
Component->Modify();
Component->SetLODDataCount(NumLods, NumLods);
/** Remove all vertex colors before we paste in new ones */
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->RemoveComponentInstanceVertexColors(Component);
/** Try and apply copied vertex colors for each LOD in the mesh */
for (int32 LODIndex = 0; LODIndex < NumLods; ++LODIndex)
{
FStaticMeshLODResources& LodRenderData = Mesh->GetRenderData()->LODResources[LODIndex];
FStaticMeshComponentLODInfo& ComponentLodInfo = Component->LODData[LODIndex];
const int32 NumLodsInCopyBuffer = PasteColors->PerLODVertexColorData.Num();
if (LODIndex >= NumLodsInCopyBuffer)
{
// no corresponding LOD in color paste buffer CopiedColorsByLOD
// create array of all white verts
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->SetInstanceColorDataForLOD(Component, LODIndex, FColor::White, FColor::White);
}
else
{
FPerLODVertexColorData& LODData = PasteColors->PerLODVertexColorData[LODIndex];
const int32 NumLODVertices = LodRenderData.GetNumVertices();
if (NumLODVertices == LODData.ColorsByIndex.Num())
{
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->SetInstanceColorDataForLOD(Component, LODIndex, LODData.ColorsByIndex);
}
else
{
// verts counts mismatch - build translation/fixup list of colors in ReOrderedColors
TArray<FColor> PositionMatchedColors;
PositionMatchedColors.Empty(NumLODVertices);
for (int32 VertexIndex = 0; VertexIndex < NumLODVertices; ++VertexIndex)
{
// Search for color matching this vertex position otherwise fill it with white
const FVector& Vertex = (FVector)LodRenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex);
const FColor* FoundColor = LODData.ColorsByPosition.Find(Vertex);
PositionMatchedColors.Add(FoundColor ? *FoundColor : FColor::White);
}
GEngine->GetEngineSubsystem<UMeshPaintingSubsystem>()->SetInstanceColorDataForLOD(Component, LODIndex, PositionMatchedColors);
}
}
}
/** Update cached paint data on static mesh component and update DDC key */
Component->CachePaintedDataIfNecessary();
Component->StaticMeshDerivedDataKey = Mesh->GetRenderData()->DerivedDataKey;
}
}
}
}
bool UMeshPaintModeSubsystem::CanPasteInstanceVertexColors(const TArray<UStaticMeshComponent*>& StaticMeshComponents, const TArray<FPerComponentVertexColorData>& CopiedColorsByComponent)
{
bool bValidForPasting = false;
/** Make sure we have copied vertex color data which matches at least mesh component in the current selection */
for (UStaticMeshComponent* Component : StaticMeshComponents)
{
checkf(Component != nullptr, TEXT("Invalid Static Mesh Component"));
UStaticMesh* Mesh = Component->GetStaticMesh();
if (Mesh && Mesh->GetNumLODs() > 0)
{
// See if there is a valid instance of copied vertex colors for this mesh
const int32 BlueprintCreatedComponentIndex = Component->GetBlueprintCreatedComponentIndex();
const FPerComponentVertexColorData* PasteColors = CopiedColorsByComponent.FindByPredicate([=](const FPerComponentVertexColorData& ComponentData)
{
return (ComponentData.OriginalMesh.Get() == Mesh && ComponentData.ComponentIndex == BlueprintCreatedComponentIndex);
});
if (PasteColors)
{
bValidForPasting = true;
break;
}
}
}
return bValidForPasting;
}
void UMeshPaintModeSubsystem::RemovePerLODColors(const TArray<UMeshComponent*>& PaintableComponents)
{
//Remove painting on all lowers LODs before doing the propagation
for (UMeshComponent* SelectedComponent : PaintableComponents)
{
UStaticMeshComponent *StaticMeshComponent = Cast<UStaticMeshComponent>(SelectedComponent);
if (StaticMeshComponent && StaticMeshComponent->GetStaticMesh())
{
// Mark the mesh component as modified
StaticMeshComponent->Modify();
// If this is called from the Remove button being clicked the SMC wont be in a Reregister context,
// but when it gets called from a Paste or Copy to Source operation it's already inside a more specific
// SMCRecreateScene context so we shouldn't put it inside another one.
if (StaticMeshComponent->IsRenderStateCreated())
{
// Detach all instances of this static mesh from the scene.
FComponentReregisterContext ComponentReregisterContext(StaticMeshComponent);
for (int32 LODIndex = 1; LODIndex < StaticMeshComponent->LODData.Num(); ++LODIndex)
{
StaticMeshComponent->RemoveInstanceVertexColorsFromLOD(LODIndex);
}
}
else
{
for (int32 LODIndex = 1; LODIndex < StaticMeshComponent->LODData.Num(); ++LODIndex)
{
StaticMeshComponent->RemoveInstanceVertexColorsFromLOD(LODIndex);
}
}
}
}
}
bool UMeshPaintModeSubsystem::CanFixTextureColors(const TArray<UMeshComponent*>& Components)
{
for (UMeshComponent* Component : Components)
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
{
if (UTexture* Texture = StaticMeshComponent->GetMeshPaintTexture())
{
if (StaticMeshComponent->CanMeshPaintTextureColors())
{
if (StaticMeshComponent->GetMeshPaintTextureResolution() != Texture->Source.GetSizeX())
{
return true;
}
}
}
}
}
return false;
}
void UMeshPaintModeSubsystem::FixTextureColors(const TArray<UMeshComponent*>& Components)
{
for (UMeshComponent* Component : Components)
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(Component))
{
if (UTexture* Texture = StaticMeshComponent->GetMeshPaintTexture())
{
int32 TextureResolution = StaticMeshComponent->GetMeshPaintTextureResolution();
if (TextureResolution != Texture->Source.GetSizeX())
{
FImage Image;
if (Texture->Source.GetMipImage(Image, 0))
{
FImage ResizedImage(TextureResolution, TextureResolution, Image.NumSlices, Image.Format, Image.GammaSpace);
FImageCore::ResizeImage(Image, ResizedImage);
Texture->Modify();
Texture->Source.Init(ResizedImage);
Texture->PostEditChange();
}
}
}
}
}
}
void UMeshPaintModeSubsystem::SwapColors()
{
if (UMeshPaintingToolProperties* Settings = UMeshPaintMode::GetToolProperties())
{
Settings->Modify();
FLinearColor TempPaintColor = Settings->PaintColor;
Settings->PaintColor = Settings->EraseColor;
Settings->EraseColor = TempPaintColor;
}
}