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

470 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "IMeshPaintComponentAdapter.h"
#include "Components/MeshComponent.h"
#include "Containers/ContainerAllocationPolicies.h"
#include "Engine/Texture.h"
#include "Engine/World.h"
#include "MaterialShared.h"
#include "Materials/Material.h"
#include "Materials/MaterialExpressionTextureCoordinate.h"
#include "Materials/MaterialExpressionTextureSampleParameter.h"
#include "Materials/MaterialInstance.h"
#include "MeshPaintingToolsetTypes.h"
#include "TexturePaintToolset.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtrTemplates.h"
//////////////////////////////////////////////////////////////////////////
// IMeshPaintGeometryAdapter
void IMeshPaintComponentAdapter::DefaultApplyOrRemoveTextureOverride(UMeshComponent* InMeshComponent, UTexture* SourceTexture, UTexture* OverrideTexture)
{
// Legacy implementation for the deprecation duration. For a valid implementation look at deprecation comment
const ERHIFeatureLevel::Type FeatureLevel = InMeshComponent->GetWorld()->GetFeatureLevel();
// Check all the materials on the mesh to see if the user texture is there
int32 MaterialIndex = 0;
UMaterialInterface* MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
while (MaterialToCheck != nullptr)
{
if (!OverrideTexture || DoesMaterialUseTexture(MaterialToCheck, SourceTexture))
{
MaterialToCheck->OverrideTexture(SourceTexture, OverrideTexture, FeatureLevel);
}
++MaterialIndex;
MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
}
}
static bool IsTextureSuitableForTexturePainting(const TWeakObjectPtr<UTexture> TexturePtr)
{
return (TexturePtr.Get() != nullptr &&
!TexturePtr->IsNormalMap() &&
!TexturePtr->HasHDRSource() && // Currently HDR textures are not supported to paint on.
TexturePtr->Source.IsValid() &&
TexturePtr->Source.GetFormat() != TSF_G16 && // Currently 16 bit textures are not supported to paint on.
TexturePtr->Source.GetBytesPerPixel() > 0 && // Textures' sources must have a known count of bytes per pixel
(TexturePtr->Source.GetBytesPerPixel() <= UTexturePaintToolset::GetMaxSupportedBytesPerPixelForPainting())); // Textures' sources must fit in FColor struct to be supported.
}
static void AddPaintableTextureFromMaterialExpression(UMaterialInterface const* InMaterial, UMaterialExpressionTextureBase const* InExpression, int32& OutDefaultIndex, TArray<struct FPaintableTexture>& InOutTextureList)
{
if (InExpression == nullptr || !IsTextureSuitableForTexturePainting(InExpression->Texture))
{
return;
}
// Default UV channel to index 0.
FPaintableTexture PaintableTexture(InExpression->Texture, 0);
// Texture Samples can have UV's specified, check the first node for whether it has a custom UV channel set.
// We only check the first as the Mesh paint mode does not support painting with UV's modified in the shader.
if (UMaterialExpressionTextureSample const* TextureSampleExpression = Cast<UMaterialExpressionTextureSample>(InExpression))
{
if (UMaterialExpressionTextureCoordinate const* TextureCoordsExpression = Cast<UMaterialExpressionTextureCoordinate>(TextureSampleExpression->Coordinates.Expression))
{
// Store the uv channel, this is set when the texture is selected.
PaintableTexture.UVChannelIndex = TextureCoordsExpression->CoordinateIndex;
}
else
{
PaintableTexture.UVChannelIndex = TextureSampleExpression->ConstCoordinate;
}
// Handle texture parameter expressions.
if (UMaterialExpressionTextureSampleParameter const* TextureSampleParameterExpression = Cast<UMaterialExpressionTextureSampleParameter>(TextureSampleExpression))
{
// Grab the overridden texture if it exists.
InMaterial->GetTextureParameterValue(TextureSampleParameterExpression->ParameterName, PaintableTexture.Texture);
}
}
// Note that the same texture will be added again if its UV channel differs.
const int32 TextureIndex = InOutTextureList.AddUnique(PaintableTexture);
// Cache the first default index, if there is no previous info this will be used as the selected texture.
if ((OutDefaultIndex == INDEX_NONE) && InExpression->IsDefaultMeshpaintTexture)
{
OutDefaultIndex = TextureIndex;
}
}
void IMeshPaintComponentAdapter::DefaultQueryPaintableTextures(int32 MaterialIndex, const UMeshComponent* MeshComponent, int32& OutDefaultIndex, TArray<struct FPaintableTexture>& InOutTextureList)
{
OutDefaultIndex = INDEX_NONE;
// We already know the material we are painting on, take it off the static mesh component
UMaterialInterface const* Material = MeshComponent->GetMaterial(MaterialIndex);
while (Material != nullptr)
{
if (Material != NULL)
{
// First iterate top level material expressions.
for (UMaterialExpression* MaterialExpression : Material->GetMaterial()->GetExpressions())
{
if (UMaterialExpressionTextureBase* TextureExpression = Cast<UMaterialExpressionTextureBase>(MaterialExpression))
{
AddPaintableTextureFromMaterialExpression(Material, TextureExpression, OutDefaultIndex, InOutTextureList);
}
}
// Now iterate material expressions from material functions.
TArray<UMaterialFunctionInterface*> MaterialFunctions;
Material->GetDependentFunctions(MaterialFunctions);
for (UMaterialFunctionInterface* MaterialFunction : MaterialFunctions)
{
for (UMaterialExpression* MaterialExpression : MaterialFunction->GetExpressions())
{
if (UMaterialExpressionTextureBase* TextureExpression = Cast<UMaterialExpressionTextureBase>(MaterialExpression))
{
AddPaintableTextureFromMaterialExpression(Material, TextureExpression, OutDefaultIndex, InOutTextureList);
}
}
}
}
// Make sure to include all texture parameters.
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> ParameterValues;
Material->GetAllParametersOfType(EMaterialParameterType::Texture, ParameterValues);
for (auto& ParameterElem : ParameterValues)
{
const TWeakObjectPtr<UTexture> TexturePtr = ParameterElem.Value.Value.Texture;
if (IsTextureSuitableForTexturePainting(TexturePtr))
{
FPaintableTexture PaintableTexture;
// Default UV channel to index 0.
PaintableTexture = FPaintableTexture(TexturePtr.Get(), 0);
InOutTextureList.AddUnique(PaintableTexture);
}
}
if (UMaterialInstance const* MaterialInstance = Cast<UMaterialInstance>(Material))
{
Material = MaterialInstance->Parent ? Cast<UMaterialInstance>(MaterialInstance->Parent) : nullptr;
}
else
{
// This prevents an infinite loop when `Material` isn't a material instance.
break;
}
}
// If the component has a mesh paint texture, then add it here.
if (UTexture* MeshPaintTexture = MeshComponent->GetMeshPaintTexture())
{
const int32 CoordinateIndex = MeshComponent->GetMeshPaintTextureCoordinateIndex();
InOutTextureList.AddUnique(FPaintableTexture(MeshPaintTexture, CoordinateIndex, true));
}
}
namespace UE::MeshPaintingToolset
{
namespace Private
{
struct FOverrideData
{
TWeakObjectPtr<UTexture> OverrideTexture;
TSet<ERHIFeatureLevel::Type, DefaultKeyFuncs<ERHIFeatureLevel::Type>, TInlineSetAllocator<1>> OverridenFeatureLevels;
uint32 Count = 0;
};
struct FGlobalTextureOverrideState
{
using FOverrideKey = TPair<TWeakObjectPtr<UMaterialInterface>, TWeakObjectPtr<const UTexture>>;
static void DuplicateOverrideOwnership(const FDefaultTextureOverride* Current, const FDefaultTextureOverride* To)
{
TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>>* Overrides = DefaultTextureOverrideToOverrides.Find(Current);
if (Overrides)
{
for (const FOverrideKey& Pair : *Overrides)
{
OverridesData.Find(Pair)->Count += 1;
}
// Duplicate the override in case an allocation change the addresses
DefaultTextureOverrideToOverrides.Add(To, TSet<FOverrideKey>(*Overrides));
}
}
static void TransferOverrideOwnership(const FDefaultTextureOverride* Current, const FDefaultTextureOverride* To)
{
TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>> Overrides;
if (DefaultTextureOverrideToOverrides.RemoveAndCopyValue(Current, Overrides))
{
DefaultTextureOverrideToOverrides.Add(To, MoveTemp(Overrides));
}
}
static void RegisterMaterialOverride(const FDefaultTextureOverride* Requester, UMaterialInterface* Material, const UTexture* SourceTexture, UTexture* OverrrideTexture, const ERHIFeatureLevel::Type FeatureLevel)
{
TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>>& Overrides = DefaultTextureOverrideToOverrides.FindOrAdd(Requester);
FOverrideKey Pair = FOverrideKey(TWeakObjectPtr<UMaterialInterface>(Material), TWeakObjectPtr<const UTexture>(SourceTexture));
uint32 PairHash = GetTypeHash(Pair);
Overrides.AddByHash(PairHash, Pair);
FOverrideData& OverrideData = OverridesData.FindOrAddByHash(PairHash, Pair);
bool bFeatureLevelAlreadyOverriden = false;
uint32 FeatureLevelHash = GetTypeHash(FeatureLevel);
OverrideData.OverridenFeatureLevels.AddByHash(FeatureLevelHash, FeatureLevel, &bFeatureLevelAlreadyOverriden);
if (OverrideData.Count == 0 || OverrideData.OverrideTexture != OverrrideTexture)
{
OverrideData.OverrideTexture = OverrrideTexture;
for (const ERHIFeatureLevel::Type LevelToUpdate : OverrideData.OverridenFeatureLevels)
{
Material->OverrideTexture(SourceTexture, OverrrideTexture, LevelToUpdate);
}
AddMaterialTracking(Pair);
}
else if (!bFeatureLevelAlreadyOverriden)
{
OverrideData.OverridenFeatureLevels.AddByHash(FeatureLevelHash, FeatureLevel);
Material->OverrideTexture(SourceTexture, OverrrideTexture, FeatureLevel);
}
OverrideData.Count += 1;
}
static void RemoveMaterialOverride(const FDefaultTextureOverride* Requester, UMaterialInterface* Material, const UTexture* SourceTexture)
{
if (TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>>* Override = DefaultTextureOverrideToOverrides.Find(Requester))
{
FOverrideKey Pair = FOverrideKey(TWeakObjectPtr<UMaterialInterface>(Material), TWeakObjectPtr<const UTexture>(SourceTexture));
uint32 PairHash = GetTypeHash(Pair);
if (Override->RemoveByHash(PairHash, Pair) > 0)
{
if (FOverrideData* OverrideData = OverridesData.FindByHash(PairHash, Pair))
{
OverrideData->Count -= 1;
if (OverrideData->Count == 0)
{
for (const ERHIFeatureLevel::Type FeatureLevel : OverrideData->OverridenFeatureLevels)
{
Material->OverrideTexture(SourceTexture, nullptr, FeatureLevel);
}
OverridesData.RemoveByHash(PairHash, Pair);
RemoveMaterialTracking(Pair);
}
}
}
}
}
static void FreeOverrideOwnership(const FDefaultTextureOverride* Requester)
{
TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>> Overrides;
if (DefaultTextureOverrideToOverrides.RemoveAndCopyValue(Requester, Overrides))
{
for (const FOverrideKey& Override : Overrides)
{
uint32 OverrideHash = GetTypeHash(Override);
if (FOverrideData* OverrideData = OverridesData.FindByHash(OverrideHash, Override))
{
OverrideData->Count -= 1;
if (OverrideData->Count == 0)
{
UMaterialInterface* Material = Override.Key.Get();
const UTexture* SourceTexture = Override.Value.Get();
for (const ERHIFeatureLevel::Type LevelToUpdate : OverrideData->OverridenFeatureLevels)
{
Material->OverrideTexture(SourceTexture, nullptr, LevelToUpdate);
}
OverridesData.RemoveByHash(OverrideHash, Override);
RemoveMaterialTracking(Override);
}
}
}
}
}
static void AddMaterialTracking(const FOverrideKey& Override)
{
if (MaterialsAndTexturesOverriden.IsEmpty())
{
OnObjectModifiedDelegateHandle = FCoreUObjectDelegates::OnObjectModified.AddStatic(&FGlobalTextureOverrideState::OnObjectModified);
PostEditDelegateHandle = FCoreUObjectDelegates::OnObjectPropertyChanged.AddStatic(&FGlobalTextureOverrideState::OnObjectPropertyChanged);
}
TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>& Textures = MaterialsAndTexturesOverriden.FindOrAdd(Override.Key);
Textures.Add(Override.Value);
}
static void RemoveMaterialTracking(const FOverrideKey& Override)
{
uint32 MaterialHash = GetTypeHash(Override.Key);
if (TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>* Textures = MaterialsAndTexturesOverriden.FindByHash(MaterialHash, Override.Key))
{
Textures->Remove(Override.Value);
if (Textures->IsEmpty())
{
MaterialsAndTexturesOverriden.RemoveByHash(MaterialHash, Override.Key);
if (MaterialsAndTexturesOverriden.IsEmpty())
{
FCoreUObjectDelegates::OnObjectModified.Remove(OnObjectModifiedDelegateHandle);
FCoreUObjectDelegates::OnObjectPropertyChanged.Remove(PostEditDelegateHandle);
}
}
}
}
static void OnObjectModified(UObject* Object)
{
if (UMaterialInterface* Material = Cast<UMaterialInterface>(Object))
{
FOverrideKey Override;
Override.Key = Material;
if (const TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>* Textures = MaterialsAndTexturesOverriden.Find(Override.Key))
{
for (const TWeakObjectPtr<const UTexture>& Texture : *Textures)
{
if (const UTexture* RawTexturePtr = Texture.Get())
{
Override.Value = Texture;
if (FOverrideData* OverrideData = OverridesData.Find(Override))
{
for (const ERHIFeatureLevel::Type FeatureLevel : OverrideData->OverridenFeatureLevels)
{
// The material resource might change because of the modifications. To avoid leaking some temp texture overrides, this just remove the temporary overrides during the modification.
Material->OverrideTexture(RawTexturePtr, nullptr, FeatureLevel);
}
}
}
}
}
}
}
static void OnObjectPropertyChanged(UObject* Object, FPropertyChangedEvent& PropertyChangedEvent)
{
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
if (UMaterialInterface* Material = Cast<UMaterialInterface>(Object))
{
FOverrideKey Override;
Override.Key = Material;
if (const TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>* Textures = MaterialsAndTexturesOverriden.Find(Override.Key))
{
for (const TWeakObjectPtr<const UTexture>& Texture : *Textures)
{
if (const UTexture* RawTexturePtr = Texture.Get())
{
Override.Value = Texture;
if (FOverrideData* OverrideData = OverridesData.Find(Override))
{
if (UTexture* OverrideTexture = OverrideData->OverrideTexture.Get())
{
for (const ERHIFeatureLevel::Type FeatureLevel : OverrideData->OverridenFeatureLevels)
{
// Reapply the temporary overrides after the modification.
Material->OverrideTexture(RawTexturePtr, OverrideTexture, FeatureLevel);
}
}
}
}
}
}
}
}
}
private:
static TMap<FOverrideKey, FOverrideData> OverridesData;
static TMap<const FDefaultTextureOverride*, TSet<FOverrideKey, DefaultKeyFuncs<FOverrideKey>, TInlineSetAllocator<2>>> DefaultTextureOverrideToOverrides;
static TMap<TWeakObjectPtr<UMaterialInterface>, TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>> MaterialsAndTexturesOverriden;
static FDelegateHandle OnObjectModifiedDelegateHandle;
static FDelegateHandle PostEditDelegateHandle;
};
TMap<FGlobalTextureOverrideState::FOverrideKey, FOverrideData> FGlobalTextureOverrideState::OverridesData;
TMap<const FDefaultTextureOverride*, TSet<FGlobalTextureOverrideState::FOverrideKey, DefaultKeyFuncs<FGlobalTextureOverrideState::FOverrideKey>, TInlineSetAllocator<2>>> FGlobalTextureOverrideState::DefaultTextureOverrideToOverrides;
TMap<TWeakObjectPtr<UMaterialInterface>, TSet<TWeakObjectPtr<const UTexture>, DefaultKeyFuncs<TWeakObjectPtr<const UTexture>>, TInlineSetAllocator<2>>> FGlobalTextureOverrideState::MaterialsAndTexturesOverriden;
FDelegateHandle FGlobalTextureOverrideState::OnObjectModifiedDelegateHandle;
FDelegateHandle FGlobalTextureOverrideState::PostEditDelegateHandle;
}
FDefaultTextureOverride::FDefaultTextureOverride(const FDefaultTextureOverride& InOther)
{
operator=(InOther);
}
FDefaultTextureOverride::FDefaultTextureOverride(FDefaultTextureOverride&& InOther)
{
operator=(MoveTemp(InOther));
}
FDefaultTextureOverride& FDefaultTextureOverride::operator=(const FDefaultTextureOverride& InOther)
{
Private::FGlobalTextureOverrideState::DuplicateOverrideOwnership(this, &InOther);
return *this;
}
FDefaultTextureOverride& FDefaultTextureOverride::operator=(FDefaultTextureOverride&& InOther)
{
Private::FGlobalTextureOverrideState::TransferOverrideOwnership(this, &InOther);
return *this;
}
void FDefaultTextureOverride::ApplyOrRemoveTextureOverride(UMeshComponent* InMeshComponent, const UTexture* SourceTexture, UTexture* OverrideTexture) const
{
check(IsInGameThread());
const ERHIFeatureLevel::Type FeatureLevel = InMeshComponent->GetWorld()->GetFeatureLevel();
// Check all the materials on the mesh to see if the user texture is there
int32 MaterialIndex = 0;
UMaterialInterface* MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
while (MaterialToCheck != nullptr)
{
if (!OverrideTexture)
{
// Unregister for all materials. This will no affect the material that weren't overridden by this instance
Private::FGlobalTextureOverrideState::RemoveMaterialOverride(this, MaterialToCheck, SourceTexture);
}
else
{
// Keep track of the material overridden
Private::FGlobalTextureOverrideState::RegisterMaterialOverride(this, MaterialToCheck, SourceTexture, OverrideTexture, FeatureLevel);
}
++MaterialIndex;
MaterialToCheck = InMeshComponent->GetMaterial(MaterialIndex);
}
// Check to see if the source texture is the special mesh paint texture on the component.
// But always apply setting override to nullptr which can happen after the SourceTexture is cleared from the component.
if (InMeshComponent->GetMeshPaintTexture() == SourceTexture || !OverrideTexture)
{
InMeshComponent->SetMeshPaintTextureOverride(OverrideTexture);
}
}
FDefaultTextureOverride::~FDefaultTextureOverride()
{
check(IsInGameThread());
Private::FGlobalTextureOverrideState::FreeOverrideOwnership(this);
}
}