Files
UnrealEngine/Engine/Plugins/Experimental/MeshModelingToolsetExp/Source/MeshModelingToolsExp/Private/BakeMeshAttributeTool.cpp
2025-05-18 13:04:45 +08:00

380 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BakeMeshAttributeTool.h"
#include "InteractiveToolManager.h"
#include "ModelingToolTargetUtil.h"
#include "ToolSetupUtil.h"
#include "AssetUtils/Texture2DUtil.h"
#include "Sampling/MeshCurvatureMapEvaluator.h"
#include "Sampling/MeshHeightMapEvaluator.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(BakeMeshAttributeTool)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UBakeMeshAttributeTool"
void UBakeMeshAttributeTool::Setup()
{
Super::Setup();
// Setup preview materials
WorkingPreviewMaterial = ToolSetupUtil::GetDefaultWorkingMaterialInstance(GetToolManager());
ErrorPreviewMaterial = ToolSetupUtil::GetDefaultErrorMaterial(GetToolManager());
}
bool UBakeMeshAttributeTool::ValidTargetMeshTangents()
{
if (bCheckTargetMeshTangents)
{
bValidTargetMeshTangents = TargetMeshTangents ? FDynamicMeshTangents(TargetMesh.Get()).HasValidTangents(true) : false;
bCheckTargetMeshTangents = false;
}
return bValidTargetMeshTangents;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_TargetMeshTangents(EBakeMapType BakeType)
{
EBakeOpState ResultState = EBakeOpState::Clean;
const bool bNeedTargetMeshTangents = (bool)(BakeType & (EBakeMapType::TangentSpaceNormal | EBakeMapType::BentNormal));
if (bNeedTargetMeshTangents && !ValidTargetMeshTangents())
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidTargetTangentsWarning", "The Target Mesh does not have valid tangents."), EToolMessageLevel::UserWarning);
ResultState = EBakeOpState::Invalid;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_Normal(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
FNormalMapSettings NormalMapSettings;
NormalMapSettings.Dimensions = Dimensions;
if (!(CachedNormalMapSettings == NormalMapSettings))
{
CachedNormalMapSettings = NormalMapSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_Occlusion(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
if (!OcclusionSettings)
{
return EBakeOpState::Invalid;
}
FOcclusionMapSettings OcclusionMapSettings;
OcclusionMapSettings.Dimensions = Dimensions;
OcclusionMapSettings.MaxDistance = (OcclusionSettings->MaxDistance == 0) ? TNumericLimits<float>::Max() : OcclusionSettings->MaxDistance;
OcclusionMapSettings.OcclusionRays = OcclusionSettings->OcclusionRays;
OcclusionMapSettings.SpreadAngle = OcclusionSettings->SpreadAngle;
OcclusionMapSettings.BiasAngle = OcclusionSettings->BiasAngle;
OcclusionMapSettings.NormalSpace = OcclusionSettings->NormalSpace;
if ( !(CachedOcclusionMapSettings == OcclusionMapSettings) )
{
CachedOcclusionMapSettings = OcclusionMapSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_Curvature(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
if (!CurvatureSettings)
{
return EBakeOpState::Invalid;
}
FCurvatureMapSettings CurvatureMapSettings;
CurvatureMapSettings.Dimensions = Dimensions;
CurvatureMapSettings.RangeMultiplier = CurvatureSettings->ColorRangeMultiplier;
CurvatureMapSettings.MinRangeMultiplier = CurvatureSettings->MinRangeMultiplier;
switch (CurvatureSettings->CurvatureType)
{
default:
case EBakeCurvatureTypeMode::MeanAverage:
CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapEvaluator::ECurvatureType::Mean;
break;
case EBakeCurvatureTypeMode::Gaussian:
CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapEvaluator::ECurvatureType::Gaussian;
break;
case EBakeCurvatureTypeMode::Max:
CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapEvaluator::ECurvatureType::MaxPrincipal;
break;
case EBakeCurvatureTypeMode::Min:
CurvatureMapSettings.CurvatureType = (int32)FMeshCurvatureMapEvaluator::ECurvatureType::MinPrincipal;
break;
}
switch (CurvatureSettings->ColorMapping)
{
default:
case EBakeCurvatureColorMode::Grayscale:
CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapEvaluator::EColorMode::BlackGrayWhite;
break;
case EBakeCurvatureColorMode::RedBlue:
CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapEvaluator::EColorMode::RedBlue;
break;
case EBakeCurvatureColorMode::RedGreenBlue:
CurvatureMapSettings.ColorMode = (int32)FMeshCurvatureMapEvaluator::EColorMode::RedGreenBlue;
break;
}
switch (CurvatureSettings->Clamping)
{
default:
case EBakeCurvatureClampMode::None:
CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapEvaluator::EClampMode::FullRange;
break;
case EBakeCurvatureClampMode::OnlyPositive:
CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapEvaluator::EClampMode::Positive;
break;
case EBakeCurvatureClampMode::OnlyNegative:
CurvatureMapSettings.ClampMode = (int32)FMeshCurvatureMapEvaluator::EClampMode::Negative;
break;
}
if (!(CachedCurvatureMapSettings == CurvatureMapSettings))
{
CachedCurvatureMapSettings = CurvatureMapSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_MeshProperty(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
FMeshPropertyMapSettings MeshPropertyMapSettings;
MeshPropertyMapSettings.Dimensions = Dimensions;
if (!(CachedMeshPropertyMapSettings == MeshPropertyMapSettings))
{
CachedMeshPropertyMapSettings = MeshPropertyMapSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_Height(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
FHeightMapSettings HeightMapSettings;
HeightMapSettings.Dimensions = Dimensions;
HeightMapSettings.InnerDistance = HeightSettings->InnerDistance;
HeightMapSettings.OuterDistance = HeightSettings->OuterDistance;
HeightMapSettings.InnerBoundsDistance = HeightSettings->InnerBoundsDistance;
HeightMapSettings.OuterBoundsDistance = HeightSettings->OuterBoundsDistance;
switch (HeightSettings->HeightRangeMode)
{
case EBakeHeightRangeMode::Absolute:
HeightMapSettings.HeightRangeMode = static_cast<int32>(FMeshHeightMapEvaluator::EHeightRangeMode::Absolute);
break;
case EBakeHeightRangeMode::RelativeBounds:
HeightMapSettings.HeightRangeMode = static_cast<int32>(FMeshHeightMapEvaluator::EHeightRangeMode::RelativeBounds);
break;
}
if (!(CachedHeightMapSettings == HeightMapSettings))
{
CachedHeightMapSettings = HeightMapSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_Texture2DImage(const FImageDimensions& Dimensions, const FDynamicMesh3* DetailMesh)
{
EBakeOpState ResultState = EBakeOpState::Clean;
if (!TextureSettings || !DetailMesh)
{
return EBakeOpState::Invalid;
}
FTexture2DSettings NewSettings;
NewSettings.Dimensions = Dimensions;
NewSettings.UVLayer = TextureSettings->UVLayerNamesList.IndexOfByKey(TextureSettings->UVLayer);
const FDynamicMeshUVOverlay* UVOverlay = DetailMesh->Attributes()->GetUVLayer(NewSettings.UVLayer);
if (UVOverlay == nullptr)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidUVWarning", "The Source Mesh does not have the selected UV layer"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
if (TextureSettings->SourceTexture == nullptr)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidTextureWarning", "The Source Texture is not valid"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
{
CachedTextureImage = MakeShared<UE::Geometry::TImageBuilder<FVector4f>, ESPMode::ThreadSafe>();
if (!UE::AssetUtils::ReadTexture(TextureSettings->SourceTexture, *CachedTextureImage, bPreferPlatformData))
{
GetToolManager()->DisplayMessage(LOCTEXT("CannotReadTextureWarning", "Cannot read from the source texture"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
}
if (!(CachedTexture2DSettings == NewSettings))
{
CachedTexture2DSettings = NewSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeTool::UpdateResult_MultiTexture(const FImageDimensions& Dimensions, const FDynamicMesh3* DetailMesh)
{
EBakeOpState ResultState = EBakeOpState::Clean;
if (!MultiTextureSettings || !DetailMesh)
{
return EBakeOpState::Invalid;
}
FTexture2DSettings NewSettings;
NewSettings.Dimensions = Dimensions;
NewSettings.UVLayer = MultiTextureSettings->UVLayerNamesList.IndexOfByKey(MultiTextureSettings->UVLayer);
const FDynamicMeshUVOverlay* UVOverlay = DetailMesh->Attributes()->GetUVLayer(NewSettings.UVLayer);
if (UVOverlay == nullptr)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidUVWarning", "The Source Mesh does not have the selected UV layer"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
const int NumMaterialIDs = MultiTextureSettings->MaterialIDSourceTextures.Num();
CachedMultiTextures.Reset();
CachedMultiTextures.SetNum(NumMaterialIDs);
int NumValidTextures = 0;
for ( int MaterialID = 0; MaterialID < NumMaterialIDs; ++MaterialID)
{
if (UTexture2D* Texture = MultiTextureSettings->MaterialIDSourceTextures[MaterialID])
{
CachedMultiTextures[MaterialID] = MakeShared<TImageBuilder<FVector4f>, ESPMode::ThreadSafe>();
if (!UE::AssetUtils::ReadTexture(Texture, *CachedMultiTextures[MaterialID], bPreferPlatformData))
{
GetToolManager()->DisplayMessage(LOCTEXT("CannotReadTextureWarning", "Cannot read from the source texture"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
++NumValidTextures;
}
}
if (NumValidTextures == 0)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidTextureWarning", "The Source Texture is not valid"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
if (!(CachedMultiTexture2DSettings == NewSettings))
{
CachedMultiTexture2DSettings = NewSettings;
ResultState = EBakeOpState::Evaluate;
}
return ResultState;
}
int UBakeMeshAttributeTool::SelectColorTextureToBake(const TArray<UTexture*>& Textures)
{
TArray<int> TextureVotes;
TextureVotes.Init(0, Textures.Num());
for (int TextureIndex = 0; TextureIndex < Textures.Num(); ++TextureIndex)
{
UTexture* Tex = Textures[TextureIndex];
UTexture2D* Tex2D = Cast<UTexture2D>(Tex);
if (Tex2D)
{
// Texture uses SRGB
if (Tex->SRGB != 0)
{
++TextureVotes[TextureIndex];
}
#if WITH_EDITORONLY_DATA
// Texture has multiple channels
ETextureSourceFormat Format = Tex->Source.GetFormat();
if (Format == TSF_BGRA8 || Format == TSF_BGRE8 || Format == TSF_RGBA16 || Format == TSF_RGBA16F || Format == TSF_RGBA32F)
{
++TextureVotes[TextureIndex];
}
#endif
// What else? Largest texture? Most layers? Most mipmaps?
}
}
int MaxIndex = -1;
int MaxVotes = -1;
for (int TextureIndex = 0; TextureIndex < Textures.Num(); ++TextureIndex)
{
if (TextureVotes[TextureIndex] > MaxVotes)
{
MaxIndex = TextureIndex;
MaxVotes = TextureVotes[TextureIndex];
}
}
return MaxIndex;
}
void UBakeMeshAttributeTool::UpdateMultiTextureMaterialIDs(
UToolTarget* Target,
TArray<TObjectPtr<UTexture2D>>& AllSourceTextures,
TArray<TObjectPtr<UTexture2D>>& MaterialIDTextures)
{
ProcessComponentTextures(UE::ToolTarget::GetTargetComponent(Target),
[&AllSourceTextures, &MaterialIDTextures](const int NumMaterials, const int MaterialID, const TArray<UTexture*>& Textures)
{
MaterialIDTextures.SetNumZeroed(NumMaterials);
for (UTexture* Tex : Textures)
{
UTexture2D* Tex2D = Cast<UTexture2D>(Tex);
if (Tex2D)
{
AllSourceTextures.Add(Tex2D);
}
}
UTexture2D* Tex2D = nullptr;
constexpr bool bGuessAtTextures = true;
if constexpr (bGuessAtTextures)
{
const int SelectedTextureIndex = SelectColorTextureToBake(Textures);
if (SelectedTextureIndex >= 0)
{
Tex2D = Cast<UTexture2D>(Textures[SelectedTextureIndex]);
}
}
MaterialIDTextures[MaterialID] = Tex2D;
});
}
#undef LOCTEXT_NAMESPACE