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

948 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BakeMeshAttributeMapsTool.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "ModelingToolTargetUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/MeshTransforms.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "Sampling/MeshNormalMapEvaluator.h"
#include "Sampling/MeshOcclusionMapEvaluator.h"
#include "Sampling/MeshCurvatureMapEvaluator.h"
#include "Sampling/MeshPropertyMapEvaluator.h"
#include "Sampling/MeshUVShellMapEvaluator.h"
#include "Sampling/MeshResampleImageEvaluator.h"
#include "Sampling/MeshHeightMapEvaluator.h"
#include "ImageUtils.h"
#include "AssetUtils/Texture2DUtil.h"
#include "EngineAnalytics.h"
#include "TargetInterfaces/MaterialProvider.h"
#include "TargetInterfaces/MeshDescriptionProvider.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "TargetInterfaces/StaticMeshBackedTarget.h"
#include "TargetInterfaces/SkeletalMeshBackedTarget.h"
#include "ToolTargetManager.h"
// required to pass UStaticMesh asset so we can save at same location
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/DynamicMeshComponent.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(BakeMeshAttributeMapsTool)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UBakeMeshAttributeMapsTool"
/*
* ToolBuilder
*/
const FToolTargetTypeRequirements& UBakeMeshAttributeMapsToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UMeshDescriptionProvider::StaticClass(),
UPrimitiveComponentBackedTarget::StaticClass(),
UMaterialProvider::StaticClass()
});
return TypeRequirements;
}
bool UBakeMeshAttributeMapsToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
const int32 NumTargets = SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements());
if (NumTargets == 1 || NumTargets == 2)
{
bool bValidTargets = true;
SceneState.TargetManager->EnumerateSelectedAndTargetableComponents(SceneState, GetTargetRequirements(),
[&bValidTargets](UActorComponent* Component)
{
UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>(Component);
USkeletalMeshComponent* SkeletalMesh = Cast<USkeletalMeshComponent>(Component);
UDynamicMeshComponent* DynMesh = Cast<UDynamicMeshComponent>(Component);
bValidTargets = bValidTargets && (StaticMesh || SkeletalMesh || DynMesh);
});
return bValidTargets;
}
return false;
}
UMultiSelectionMeshEditingTool* UBakeMeshAttributeMapsToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
return NewObject<UBakeMeshAttributeMapsTool>(SceneState.ToolManager);
}
const TArray<FString>& UBakeMeshAttributeMapsToolProperties::GetMapPreviewNamesFunc()
{
return MapPreviewNamesList;
}
/*
* Operators
*/
class FMeshMapBakerOp : public TGenericDataOperator<FMeshMapBaker>
{
public:
using ImagePtr = TSharedPtr<UE::Geometry::TImageBuilder<FVector4f>, ESPMode::ThreadSafe>;
// General bake settings
TSharedPtr<UE::Geometry::FDynamicMesh3, ESPMode::ThreadSafe> DetailMesh;
TSharedPtr<UE::Geometry::FDynamicMeshAABBTree3, ESPMode::ThreadSafe> DetailSpatial;
TSharedPtr<UE::Geometry::TMeshTangents<double>, ESPMode::ThreadSafe> DetailMeshTangents;
TSharedPtr<UE::Geometry::FDynamicMesh3, ESPMode::ThreadSafe> BaseMesh;
TUniquePtr<UE::Geometry::FMeshMapBaker> Baker;
UBakeMeshAttributeMapsTool::FBakeSettings BakeSettings;
TSharedPtr<UE::Geometry::TMeshTangents<double>, ESPMode::ThreadSafe> BaseMeshTangents;
TSharedPtr<TArray<int32>, ESPMode::ThreadSafe> BaseMeshUVCharts;
bool bIsBakeToSelf = false;
ImagePtr SampleFilterMask;
// Map Type settings
EBakeMapType Maps;
FNormalMapSettings NormalSettings;
FOcclusionMapSettings OcclusionSettings;
FCurvatureMapSettings CurvatureSettings;
FMeshPropertyMapSettings PropertySettings;
FHeightMapSettings HeightSettings;
FUVShellMapSettings UVShellSettings;
FTexture2DSettings TextureSettings;
FTexture2DSettings MultiTextureSettings;
// NormalMap settings
ImagePtr DetailMeshNormalMap;
int32 DetailMeshNormalUVLayer = 0;
IMeshBakerDetailSampler::EBakeDetailNormalSpace DetailMeshNormalSpace = IMeshBakerDetailSampler::EBakeDetailNormalSpace::Tangent;
// Texture2DImage & MultiTexture settings
ImagePtr TextureImage;
TArray<ImagePtr> MaterialIDTextures;
// Begin TGenericDataOperator interface
virtual void CalculateResult(FProgressCancel* Progress) override
{
Baker = MakeUnique<FMeshMapBaker>();
Baker->CancelF = [Progress]() {
return Progress && Progress->Cancelled();
};
Baker->SetTargetMesh(BaseMesh.Get());
Baker->SetTargetMeshUVLayer(BakeSettings.TargetUVLayer);
Baker->SetDimensions(BakeSettings.Dimensions);
Baker->SetProjectionDistance(BakeSettings.ProjectionDistance);
Baker->SetSamplesPerPixel(BakeSettings.SamplesPerPixel);
Baker->SetTargetMeshUVCharts(BaseMeshUVCharts.Get());
Baker->SetTargetMeshTangents(BaseMeshTangents);
if (bIsBakeToSelf)
{
Baker->SetCorrespondenceStrategy(FMeshBaseBaker::ECorrespondenceStrategy::Identity);
}
if (SampleFilterMask)
{
Baker->SampleFilterF = [this](const FVector2i& ImageCoords, const FVector2d& UV, int32 TriID)
{
const FVector4f Mask = SampleFilterMask->BilinearSampleUV<float>(UV, FVector4f::One());
return (Mask.X + Mask.Y + Mask.Z) / 3;
};
}
FMeshBakerDynamicMeshSampler DetailSampler(DetailMesh.Get(), DetailSpatial.Get(), DetailMeshTangents.Get());
Baker->SetDetailSampler(&DetailSampler);
// Occlusion evaluator is shared by both AmbientOcclusion and BentNormal.
// Only initialize it once. OcclusionType is initialized to None. Callers
// must manually update the OcclusionType.
auto InitOcclusionEval = [this](TSharedPtr<FMeshOcclusionMapEvaluator>& Eval)
{
if (!Eval)
{
Eval = MakeShared<FMeshOcclusionMapEvaluator>();
Eval->OcclusionType = EMeshOcclusionMapType::None;
Eval->NumOcclusionRays = OcclusionSettings.OcclusionRays;
Eval->MaxDistance = OcclusionSettings.MaxDistance;
Eval->SpreadAngle = OcclusionSettings.SpreadAngle;
Eval->BiasAngleDeg = OcclusionSettings.BiasAngle;
switch (OcclusionSettings.NormalSpace)
{
case EBakeNormalSpace::Tangent:
Eval->NormalSpace = FMeshOcclusionMapEvaluator::ESpace::Tangent;
break;
case EBakeNormalSpace::Object:
Eval->NormalSpace = FMeshOcclusionMapEvaluator::ESpace::Object;
break;
}
Baker->AddEvaluator(Eval);
}
};
TSharedPtr<FMeshOcclusionMapEvaluator> OcclusionEval;
for (const EBakeMapType MapType : ENUM_EBAKEMAPTYPE_ALL)
{
switch (BakeSettings.BakeMapTypes & MapType)
{
case EBakeMapType::TangentSpaceNormal:
{
TSharedPtr<FMeshNormalMapEvaluator> NormalEval = MakeShared<FMeshNormalMapEvaluator>();
DetailSampler.SetNormalTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailNormalTexture(DetailMeshNormalMap.Get(), DetailMeshNormalUVLayer, DetailMeshNormalSpace));
Baker->AddEvaluator(NormalEval);
break;
}
case EBakeMapType::AmbientOcclusion:
{
InitOcclusionEval(OcclusionEval);
OcclusionEval->OcclusionType |= EMeshOcclusionMapType::AmbientOcclusion;
break;
}
case EBakeMapType::BentNormal:
{
InitOcclusionEval(OcclusionEval);
OcclusionEval->OcclusionType |= EMeshOcclusionMapType::BentNormal;
break;
}
case EBakeMapType::Curvature:
{
TSharedPtr<FMeshCurvatureMapEvaluator> CurvatureEval = MakeShared<FMeshCurvatureMapEvaluator>();
CurvatureEval->RangeScale = FMathd::Clamp(CurvatureSettings.RangeMultiplier, 0.0001, 1000.0);
CurvatureEval->MinRangeScale = FMathd::Clamp(CurvatureSettings.MinRangeMultiplier, 0.0, 1.0);
CurvatureEval->UseCurvatureType = (FMeshCurvatureMapEvaluator::ECurvatureType)CurvatureSettings.CurvatureType;
CurvatureEval->UseColorMode = (FMeshCurvatureMapEvaluator::EColorMode)CurvatureSettings.ColorMode;
CurvatureEval->UseClampMode = (FMeshCurvatureMapEvaluator::EClampMode)CurvatureSettings.ClampMode;
Baker->AddEvaluator(CurvatureEval);
break;
}
case EBakeMapType::ObjectSpaceNormal:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::Normal;
DetailSampler.SetNormalTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailNormalTexture(DetailMeshNormalMap.Get(), DetailMeshNormalUVLayer, DetailMeshNormalSpace));
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::FaceNormal:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::FacetNormal;
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::Position:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::Position;
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::MaterialID:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::MaterialID;
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::PolyGroupID:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::PolyGroupID;
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::Height:
{
TSharedPtr<FMeshHeightMapEvaluator> HeightEval = MakeShared<FMeshHeightMapEvaluator>();
HeightEval->RangeMode = (FMeshHeightMapEvaluator::EHeightRangeMode) HeightSettings.HeightRangeMode;
switch(HeightEval->RangeMode)
{
case FMeshHeightMapEvaluator::EHeightRangeMode::Absolute:
HeightEval->Range = FInterval1f::MakeFromUnordered(HeightSettings.InnerDistance, HeightSettings.OuterDistance);
break;
case FMeshHeightMapEvaluator::EHeightRangeMode::RelativeBounds:
HeightEval->Range = FInterval1f::MakeFromUnordered(HeightSettings.InnerBoundsDistance, HeightSettings.OuterBoundsDistance);
break;
}
Baker->AddEvaluator(HeightEval);
break;
}
case EBakeMapType::UVShell:
{
TSharedPtr<FMeshUVShellMapEvaluator> UVShellEval = MakeShared<FMeshUVShellMapEvaluator>();
UVShellEval->TexelSize = BakeSettings.Dimensions.GetTexelSize();
UVShellEval->UVLayer = UVShellSettings.UVLayer;
UVShellEval->WireframeThickness = UVShellSettings.WireframeThickness;
UVShellEval->WireframeColor = UVShellSettings.WireframeColor;
UVShellEval->ShellColor = UVShellSettings.ShellColor;
UVShellEval->BackgroundColor = UVShellSettings.BackgroundColor;
Baker->AddEvaluator(UVShellEval);
break;
}
case EBakeMapType::VertexColor:
{
TSharedPtr<FMeshPropertyMapEvaluator> PropertyEval = MakeShared<FMeshPropertyMapEvaluator>();
PropertyEval->Property = EMeshPropertyMapType::VertexColor;
Baker->AddEvaluator(PropertyEval);
break;
}
case EBakeMapType::Texture:
{
TSharedPtr<FMeshResampleImageEvaluator> TextureEval = MakeShared<FMeshResampleImageEvaluator>();
DetailSampler.SetTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailTexture(TextureImage.Get(), TextureSettings.UVLayer));
Baker->AddEvaluator(TextureEval);
break;
}
case EBakeMapType::MultiTexture:
{
TSharedPtr<FMeshMultiResampleImageEvaluator> TextureEval = MakeShared<FMeshMultiResampleImageEvaluator>();
TextureEval->DetailUVLayer = MultiTextureSettings.UVLayer;
TextureEval->MultiTextures = MaterialIDTextures;
Baker->AddEvaluator(TextureEval);
break;
}
default:
break;
}
}
Baker->Bake();
SetResult(MoveTemp(Baker));
}
// End TGenericDataOperator interface
};
/*
* Tool
*/
void UBakeMeshAttributeMapsTool::Setup()
{
TRACE_CPUPROFILER_EVENT_SCOPE(UBakeMeshAttributeMapsTool::Setup);
Super::Setup();
// Initialize preview mesh
bIsBakeToSelf = (Targets.Num() == 1);
PreviewMesh->ProcessMesh([this](const FDynamicMesh3& Mesh)
{
TargetMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
TargetMesh->Copy(Mesh);
TargetMeshTangents = MakeShared<FMeshTangentsd, ESPMode::ThreadSafe>(TargetMesh.Get());
TargetMeshTangents->CopyTriVertexTangents(Mesh);
});
UToolTarget* Target = Targets[0];
UToolTarget* DetailTarget = Targets[bIsBakeToSelf ? 0 : 1];
// Setup tool property sets
Settings = NewObject<UBakeMeshAttributeMapsToolProperties>(this);
Settings->RestoreProperties(this);
AddToolPropertySource(Settings);
Settings->WatchProperty(Settings->MapTypes, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); });
Settings->WatchProperty(Settings->MapPreview, [this](FString) { UpdateVisualization(); GetToolManager()->PostInvalidation(); });
Settings->WatchProperty(Settings->Resolution, [this](EBakeTextureResolution) { OpState |= EBakeOpState::Evaluate; });
Settings->WatchProperty(Settings->BitDepth, [this](EBakeTextureBitDepth) { OpState |= EBakeOpState::Evaluate; });
Settings->WatchProperty(Settings->SamplesPerPixel, [this](EBakeTextureSamplesPerPixel) { OpState |= EBakeOpState::Evaluate; });
Settings->WatchProperty(Settings->SampleFilterMask, [this](UTexture2D*){ OpState |= EBakeOpState::Evaluate; });
InputMeshSettings = NewObject<UBakeInputMeshProperties>(this);
InputMeshSettings->RestoreProperties(this);
AddToolPropertySource(InputMeshSettings);
SetToolPropertySourceEnabled(InputMeshSettings, true);
InputMeshSettings->bHasTargetUVLayer = true;
InputMeshSettings->bHasSourceNormalMap = true;
InputMeshSettings->TargetStaticMesh = UE::ToolTarget::GetStaticMeshFromTargetIfAvailable(Target);
InputMeshSettings->TargetSkeletalMesh = UE::ToolTarget::GetSkeletalMeshFromTargetIfAvailable(Target);
InputMeshSettings->TargetDynamicMesh = GetTargetActorViaIPersistentDynamicMeshSource(Target);
InputMeshSettings->SourceStaticMesh = !bIsBakeToSelf ? UE::ToolTarget::GetStaticMeshFromTargetIfAvailable(DetailTarget) : nullptr;
InputMeshSettings->SourceSkeletalMesh = !bIsBakeToSelf ? UE::ToolTarget::GetSkeletalMeshFromTargetIfAvailable(DetailTarget) : nullptr;
InputMeshSettings->SourceDynamicMesh = !bIsBakeToSelf ? GetTargetActorViaIPersistentDynamicMeshSource(DetailTarget) : nullptr;
InputMeshSettings->SourceNormalMap = nullptr;
UpdateUVLayerNames(InputMeshSettings->TargetUVLayer, InputMeshSettings->TargetUVLayerNamesList, *TargetMesh);
InputMeshSettings->WatchProperty(InputMeshSettings->bHideSourceMesh, [this](bool bState) { SetSourceObjectVisible(!bState); });
InputMeshSettings->WatchProperty(InputMeshSettings->TargetUVLayer, [this](FString) { OpState |= EBakeOpState::Evaluate; });
InputMeshSettings->WatchProperty(InputMeshSettings->ProjectionDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
InputMeshSettings->WatchProperty(InputMeshSettings->bProjectionInWorldSpace, [this](bool) { OpState |= EBakeOpState::EvaluateDetailMesh; });
InputMeshSettings->WatchProperty(InputMeshSettings->SourceNormalMapUVLayer, [this](FString) { OpState |= EBakeOpState::Evaluate; });
InputMeshSettings->WatchProperty(InputMeshSettings->SourceNormalMap, [this](UTexture2D*)
{
// Only invalidate detail mesh if we need to recompute tangents.
if (!DetailMeshTangents && InputMeshSettings->SourceNormalSpace == EBakeNormalSpace::Tangent)
{
OpState |= EBakeOpState::EvaluateDetailMesh;
}
OpState |= EBakeOpState::Evaluate;
});
InputMeshSettings->WatchProperty(InputMeshSettings->SourceNormalSpace, [this](EBakeNormalSpace Space)
{
if (!DetailMeshTangents && Space == EBakeNormalSpace::Tangent)
{
OpState |= EBakeOpState::EvaluateDetailMesh;
}
OpState |= EBakeOpState::Evaluate;
});
SetSourceObjectVisible(!InputMeshSettings->bHideSourceMesh);
ResultSettings = NewObject<UBakeMeshAttributeMapsResultToolProperties>(this);
ResultSettings->RestoreProperties(this);
AddToolPropertySource(ResultSettings);
SetToolPropertySourceEnabled(ResultSettings, true);
OcclusionSettings = NewObject<UBakeOcclusionMapToolProperties>(this);
OcclusionSettings->RestoreProperties(this);
AddToolPropertySource(OcclusionSettings);
SetToolPropertySourceEnabled(OcclusionSettings, false);
OcclusionSettings->WatchProperty(OcclusionSettings->OcclusionRays, [this](int32) { OpState |= EBakeOpState::Evaluate; });
OcclusionSettings->WatchProperty(OcclusionSettings->MaxDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
OcclusionSettings->WatchProperty(OcclusionSettings->SpreadAngle, [this](float) { OpState |= EBakeOpState::Evaluate; });
OcclusionSettings->WatchProperty(OcclusionSettings->BiasAngle, [this](float) { OpState |= EBakeOpState::Evaluate; });
OcclusionSettings->WatchProperty(OcclusionSettings->NormalSpace, [this](EBakeNormalSpace) { OpState |= EBakeOpState::Evaluate; });
CurvatureSettings = NewObject<UBakeCurvatureMapToolProperties>(this);
CurvatureSettings->RestoreProperties(this);
AddToolPropertySource(CurvatureSettings);
SetToolPropertySourceEnabled(CurvatureSettings, false);
CurvatureSettings->WatchProperty(CurvatureSettings->ColorRangeMultiplier, [this](float) { OpState |= EBakeOpState::Evaluate; });
CurvatureSettings->WatchProperty(CurvatureSettings->MinRangeMultiplier, [this](float) { OpState |= EBakeOpState::Evaluate; });
CurvatureSettings->WatchProperty(CurvatureSettings->CurvatureType, [this](EBakeCurvatureTypeMode) { OpState |= EBakeOpState::Evaluate; });
CurvatureSettings->WatchProperty(CurvatureSettings->ColorMapping, [this](EBakeCurvatureColorMode) { OpState |= EBakeOpState::Evaluate; });
CurvatureSettings->WatchProperty(CurvatureSettings->Clamping, [this](EBakeCurvatureClampMode) { OpState |= EBakeOpState::Evaluate; });
UVShellSettings = NewObject<UBakeUVShellMapToolProperties>(this);
UVShellSettings->RestoreProperties(this);
AddToolPropertySource(UVShellSettings);
SetToolPropertySourceEnabled(UVShellSettings, false);
UVShellSettings->WatchProperty(UVShellSettings->UVLayer, [this](int) { OpState |= EBakeOpState::Evaluate; });
UVShellSettings->WatchProperty(UVShellSettings->WireframeThickness, [this](float) { OpState |= EBakeOpState::Evaluate; });
UVShellSettings->WatchProperty(UVShellSettings->WireframeColor, [this](FLinearColor) { OpState |= EBakeOpState::Evaluate; });
UVShellSettings->WatchProperty(UVShellSettings->ShellColor, [this](FLinearColor) { OpState |= EBakeOpState::Evaluate; });
UVShellSettings->WatchProperty(UVShellSettings->BackgroundColor, [this](FLinearColor) { OpState |= EBakeOpState::Evaluate; });
HeightSettings = NewObject<UBakeHeightMapToolProperties>(this);
HeightSettings->RestoreProperties(this);
AddToolPropertySource(HeightSettings);
SetToolPropertySourceEnabled(HeightSettings, false);
HeightSettings->WatchProperty(HeightSettings->HeightRangeMode, [this](EBakeHeightRangeMode) { OpState |= EBakeOpState::Evaluate; });
HeightSettings->WatchProperty(HeightSettings->InnerDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
HeightSettings->WatchProperty(HeightSettings->OuterDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
HeightSettings->WatchProperty(HeightSettings->InnerBoundsDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
HeightSettings->WatchProperty(HeightSettings->OuterBoundsDistance, [this](float) { OpState |= EBakeOpState::Evaluate; });
TextureSettings = NewObject<UBakeTexture2DProperties>(this);
TextureSettings->RestoreProperties(this);
AddToolPropertySource(TextureSettings);
SetToolPropertySourceEnabled(TextureSettings, false);
TextureSettings->WatchProperty(TextureSettings->UVLayer, [this](FString) { OpState |= EBakeOpState::Evaluate; });
TextureSettings->WatchProperty(TextureSettings->SourceTexture, [this](UTexture2D*) { OpState |= EBakeOpState::Evaluate; });
MultiTextureSettings = NewObject<UBakeMultiTexture2DProperties>(this);
MultiTextureSettings->RestoreProperties(this);
AddToolPropertySource(MultiTextureSettings);
SetToolPropertySourceEnabled(MultiTextureSettings, false);
auto SetDirtyCallback = [this](decltype(MultiTextureSettings->MaterialIDSourceTextures)) { OpState |= EBakeOpState::Evaluate; };
auto NotEqualsCallback = [](const decltype(MultiTextureSettings->MaterialIDSourceTextures)& A, const decltype(MultiTextureSettings->MaterialIDSourceTextures)& B) -> bool { return A != B; };
MultiTextureSettings->WatchProperty(MultiTextureSettings->MaterialIDSourceTextures, SetDirtyCallback, NotEqualsCallback);
MultiTextureSettings->WatchProperty(MultiTextureSettings->UVLayer, [this](FString) { OpState |= EBakeOpState::Evaluate; });
UpdateMultiTextureMaterialIDs(DetailTarget, MultiTextureSettings->AllSourceTextures, MultiTextureSettings->MaterialIDSourceTextures);
UpdateOnModeChange();
UpdateDetailMesh();
SetToolDisplayName(LOCTEXT("ToolName", "Bake Textures"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartTool", "Bake Maps. Select Bake Mesh (LowPoly) first, then (optionally) Detail Mesh second. Texture Assets will be created on Accept. "),
EToolMessageLevel::UserNotification);
PostSetup();
}
bool UBakeMeshAttributeMapsTool::CanAccept() const
{
const bool bValidOp = (OpState & EBakeOpState::Invalid) != EBakeOpState::Invalid;
bool bCanAccept = bValidOp && Compute ? Compute->HaveValidResult() : false;
if (bCanAccept)
{
// Allow Accept if all non-None types have valid results.
for (const TTuple<EBakeMapType, TObjectPtr<UTexture2D>>& Result : ResultSettings->Result)
{
bCanAccept = bCanAccept && Result.Get<1>();
}
}
return bCanAccept;
}
TUniquePtr<UE::Geometry::TGenericDataOperator<FMeshMapBaker>> UBakeMeshAttributeMapsTool::MakeNewOperator()
{
TUniquePtr<FMeshMapBakerOp> Op = MakeUnique<FMeshMapBakerOp>();
Op->DetailMesh = DetailMesh;
Op->DetailSpatial = DetailSpatial;
Op->BaseMesh = TargetMesh;
Op->BakeSettings = CachedBakeSettings;
Op->BaseMeshUVCharts = TargetMeshUVCharts;
Op->bIsBakeToSelf = bIsBakeToSelf;
Op->SampleFilterMask = CachedSampleFilterMask;
constexpr EBakeMapType RequiresTangents = EBakeMapType::TangentSpaceNormal | EBakeMapType::BentNormal;
if ((bool)(CachedBakeSettings.BakeMapTypes & RequiresTangents))
{
Op->BaseMeshTangents = TargetMeshTangents;
}
if (CachedDetailNormalMap)
{
Op->DetailMeshTangents = DetailMeshTangents;
Op->DetailMeshNormalMap = CachedDetailNormalMap;
Op->DetailMeshNormalUVLayer = CachedDetailMeshSettings.UVLayer;
Op->DetailMeshNormalSpace = CachedDetailMeshSettings.NormalSpace == EBakeNormalSpace::Tangent ?
IMeshBakerDetailSampler::EBakeDetailNormalSpace::Tangent : IMeshBakerDetailSampler::EBakeDetailNormalSpace::Object;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::TangentSpaceNormal))
{
Op->NormalSettings = CachedNormalMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::AmbientOcclusion) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::BentNormal))
{
Op->OcclusionSettings = CachedOcclusionMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Curvature))
{
Op->CurvatureSettings = CachedCurvatureMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::ObjectSpaceNormal) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::FaceNormal) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Position) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::MaterialID) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::VertexColor))
{
Op->PropertySettings = CachedMeshPropertyMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Height))
{
Op->HeightSettings = CachedHeightMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::UVShell))
{
Op->UVShellSettings = CachedUVShellMapSettings;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Texture))
{
Op->TextureSettings = CachedTexture2DSettings;
Op->TextureImage = CachedTextureImage;
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::MultiTexture))
{
Op->MultiTextureSettings = CachedMultiTexture2DSettings;
Op->MaterialIDTextures = CachedMultiTextures;
}
return Op;
}
void UBakeMeshAttributeMapsTool::OnShutdown(EToolShutdownType ShutdownType)
{
TRACE_CPUPROFILER_EVENT_SCOPE(UBakeMeshAttributeMapsTool::Shutdown);
Super::OnShutdown(ShutdownType);
Settings->SaveProperties(this);
InputMeshSettings->SaveProperties(this);
OcclusionSettings->SaveProperties(this);
CurvatureSettings->SaveProperties(this);
HeightSettings->SaveProperties(this);
TextureSettings->SaveProperties(this);
MultiTextureSettings->SaveProperties(this);
SetSourceObjectVisible(true);
if (Compute)
{
Compute->Shutdown();
}
if (ShutdownType == EToolShutdownType::Accept)
{
// Check if we have a source asset to identify a location to store the texture assets.
IStaticMeshBackedTarget* StaticMeshTarget = Cast<IStaticMeshBackedTarget>(Targets[0]);
UObject* SourceAsset = StaticMeshTarget ? StaticMeshTarget->GetStaticMesh() : nullptr;
if (!SourceAsset)
{
// Check if our target is a Skeletal Mesh Asset
ISkeletalMeshBackedTarget* SkeletalMeshTarget = Cast<ISkeletalMeshBackedTarget>(Targets[0]);
SourceAsset = SkeletalMeshTarget ? SkeletalMeshTarget->GetSkeletalMesh() : nullptr;
}
const UPrimitiveComponent* SourceComponent = UE::ToolTarget::GetTargetComponent(Targets[0]);
CreateTextureAssets(ResultSettings->Result, SourceComponent->GetWorld(), SourceAsset);
}
}
bool UBakeMeshAttributeMapsTool::ValidDetailMeshTangents()
{
if (!DetailMesh)
{
return false;
}
if (bCheckDetailMeshTangents)
{
bValidDetailMeshTangents = FDynamicMeshTangents(DetailMesh.Get()).HasValidTangents(true);
bCheckDetailMeshTangents = false;
}
return bValidDetailMeshTangents;
}
void UBakeMeshAttributeMapsTool::UpdateDetailMesh()
{
UToolTarget* DetailTarget = Targets[bIsBakeToSelf ? 0 : 1];
const bool bWantMeshTangents = (InputMeshSettings->SourceNormalMap != nullptr);
FGetMeshParameters GetMeshParams;
GetMeshParams.bWantMeshTangents = bWantMeshTangents;
DetailMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>(UE::ToolTarget::GetDynamicMeshCopy(DetailTarget, GetMeshParams));
if (InputMeshSettings->bProjectionInWorldSpace && bIsBakeToSelf == false)
{
const FTransformSRT3d DetailToWorld = UE::ToolTarget::GetLocalToWorldTransform(DetailTarget);
MeshTransforms::ApplyTransform(*DetailMesh, DetailToWorld, true);
const FTransformSRT3d WorldToBase = UE::ToolTarget::GetLocalToWorldTransform(Targets[0]);
MeshTransforms::ApplyTransformInverse(*DetailMesh, WorldToBase, true);
}
DetailSpatial = MakeShared<FDynamicMeshAABBTree3, ESPMode::ThreadSafe>();
DetailSpatial->SetMesh(DetailMesh.Get(), true);
// Extract tangents if a DetailMesh normal map was provided.
if (bWantMeshTangents)
{
DetailMeshTangents = MakeShared<FMeshTangentsd, ESPMode::ThreadSafe>(DetailMesh.Get());
DetailMeshTangents->CopyTriVertexTangents(*DetailMesh);
}
else
{
DetailMeshTangents = nullptr;
}
UpdateUVLayerNames(InputMeshSettings->SourceNormalMapUVLayer, InputMeshSettings->SourceUVLayerNamesList, *DetailMesh);
UpdateUVLayerNames(TextureSettings->UVLayer, TextureSettings->UVLayerNamesList, *DetailMesh);
UpdateUVLayerNames(MultiTextureSettings->UVLayer, MultiTextureSettings->UVLayerNamesList, *DetailMesh);
// Clear detail mesh evaluation flag and mark evaluation.
OpState &= ~EBakeOpState::EvaluateDetailMesh;
OpState |= EBakeOpState::Evaluate;
CachedBakeSettings = FBakeSettings();
DetailMeshTimestamp++;
}
void UBakeMeshAttributeMapsTool::UpdateResult()
{
if (static_cast<bool>(OpState & EBakeOpState::EvaluateDetailMesh))
{
UpdateDetailMesh();
}
if (OpState == EBakeOpState::Clean)
{
return;
}
// clear warning (ugh)
GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning);
int32 ImageSize = (int32)Settings->Resolution;
FImageDimensions Dimensions(ImageSize, ImageSize);
FBakeSettings BakeSettings;
BakeSettings.Dimensions = Dimensions;
BakeSettings.BitDepth = Settings->BitDepth;
BakeSettings.TargetUVLayer = InputMeshSettings->TargetUVLayerNamesList.IndexOfByKey(InputMeshSettings->TargetUVLayer);
BakeSettings.DetailTimestamp = this->DetailMeshTimestamp;
BakeSettings.ProjectionDistance = InputMeshSettings->ProjectionDistance;
BakeSettings.SamplesPerPixel = (int32)Settings->SamplesPerPixel;
BakeSettings.bProjectionInWorldSpace = InputMeshSettings->bProjectionInWorldSpace;
// Record the original map types and process the raw bitfield which may add
// additional targets.
BakeSettings.SourceBakeMapTypes = static_cast<EBakeMapType>(Settings->MapTypes);
BakeSettings.BakeMapTypes = GetMapTypes(Settings->MapTypes);
// update bake cache settings
if (!(CachedBakeSettings == BakeSettings))
{
CachedBakeSettings = BakeSettings;
CachedNormalMapSettings = FNormalMapSettings();
CachedOcclusionMapSettings = FOcclusionMapSettings();
CachedCurvatureMapSettings = FCurvatureMapSettings();
CachedMeshPropertyMapSettings = FMeshPropertyMapSettings();
CachedTexture2DSettings = FTexture2DSettings();
CachedMultiTexture2DSettings = FTexture2DSettings();
}
// Clear our invalid bitflag to check again for valid inputs.
OpState &= ~EBakeOpState::Invalid;
OpState |= UpdateResult_TargetMeshTangents(CachedBakeSettings.BakeMapTypes);
OpState |= UpdateResult_DetailNormalMap();
OpState |= UpdateResult_SampleFilterMask(Settings->SampleFilterMask);
// Update map type settings
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::TangentSpaceNormal))
{
OpState |= UpdateResult_Normal(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::AmbientOcclusion) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::BentNormal))
{
OpState |= UpdateResult_Occlusion(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Curvature))
{
OpState |= UpdateResult_Curvature(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::ObjectSpaceNormal) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::FaceNormal) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Position) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::MaterialID) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::PolyGroupID) ||
(bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::VertexColor))
{
OpState |= UpdateResult_MeshProperty(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Height))
{
OpState |= UpdateResult_Height(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::UVShell))
{
OpState |= UpdateResult_UVShellMap(CachedBakeSettings.Dimensions);
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::Texture))
{
OpState |= UpdateResult_Texture2DImage(CachedBakeSettings.Dimensions, DetailMesh.Get());
}
if ((bool)(CachedBakeSettings.BakeMapTypes & EBakeMapType::MultiTexture))
{
OpState |= UpdateResult_MultiTexture(CachedBakeSettings.Dimensions, DetailMesh.Get());
}
// Early exit if op input parameters are invalid.
if ((bool)(OpState & EBakeOpState::Invalid))
{
InvalidateResults();
return;
}
// This should be the only point of compute invalidation to
// minimize synchronization issues.
InvalidateCompute();
}
EBakeOpState UBakeMeshAttributeMapsTool::UpdateResult_DetailMeshTangents(EBakeMapType BakeType)
{
EBakeOpState ResultState = EBakeOpState::Clean;
const bool bNeedDetailMeshTangents = (bool)(BakeType & (EBakeMapType::TangentSpaceNormal | EBakeMapType::BentNormal));
if (bNeedDetailMeshTangents && !ValidDetailMeshTangents())
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidSourceTangentsWarning", "The Source Mesh does not have valid tangents."), EToolMessageLevel::UserWarning);
ResultState = EBakeOpState::Invalid;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeMapsTool::UpdateResult_DetailNormalMap()
{
EBakeOpState ResultState = EBakeOpState::Clean;
const int DetailUVLayer = InputMeshSettings->SourceUVLayerNamesList.IndexOfByKey(InputMeshSettings->SourceNormalMapUVLayer);
const FDynamicMeshUVOverlay* UVOverlay = DetailMesh->Attributes()->GetUVLayer(DetailUVLayer);
if (UVOverlay == nullptr)
{
GetToolManager()->DisplayMessage(LOCTEXT("InvalidUVWarning", "The Source Mesh does not have the selected UV layer"), EToolMessageLevel::UserWarning);
return EBakeOpState::Invalid;
}
if (UTexture2D* DetailMeshNormalMap = InputMeshSettings->SourceNormalMap)
{
CachedDetailNormalMap = MakeShared<UE::Geometry::TImageBuilder<FVector4f>, ESPMode::ThreadSafe>();
if (!UE::AssetUtils::ReadTexture(DetailMeshNormalMap, *CachedDetailNormalMap, bPreferPlatformData))
{
// Report the failed texture read as a warning, but permit the bake to continue.
GetToolManager()->DisplayMessage(LOCTEXT("CannotReadTextureWarning", "Cannot read from the source normal map texture"), EToolMessageLevel::UserWarning);
}
// Validate detail mesh tangents for detail normal map
ResultState |= UpdateResult_DetailMeshTangents(CachedBakeSettings.BakeMapTypes);
}
else
{
CachedDetailNormalMap = nullptr;
}
FDetailMeshSettings DetailMeshSettings;
DetailMeshSettings.UVLayer = DetailUVLayer;
DetailMeshSettings.NormalSpace = InputMeshSettings->SourceNormalSpace;
if (!(CachedDetailMeshSettings == DetailMeshSettings))
{
CachedDetailMeshSettings = DetailMeshSettings;
ResultState |= EBakeOpState::Evaluate;
}
return ResultState;
}
EBakeOpState UBakeMeshAttributeMapsTool::UpdateResult_UVShellMap(const FImageDimensions& Dimensions)
{
EBakeOpState ResultState = EBakeOpState::Clean;
FUVShellMapSettings UVShellMapSettings;
UVShellMapSettings.UVLayer = UVShellSettings->UVLayer;
UVShellMapSettings.WireframeThickness = UVShellSettings->WireframeThickness;
UVShellMapSettings.WireframeColor = UVShellSettings->WireframeColor;
UVShellMapSettings.ShellColor = UVShellSettings->ShellColor;
UVShellMapSettings.BackgroundColor = UVShellSettings->BackgroundColor;
if (!(CachedUVShellMapSettings == UVShellMapSettings))
{
CachedUVShellMapSettings = UVShellMapSettings;
ResultState |= EBakeOpState::Evaluate;
}
return ResultState;
}
void UBakeMeshAttributeMapsTool::UpdateVisualization()
{
PreviewMesh->SetOverrideRenderMaterial(PreviewMaterial);
// Populate Settings->Result from CachedMaps
for (const TTuple<EBakeMapType, TObjectPtr<UTexture2D>>& Map : CachedMaps)
{
if (ResultSettings->Result.Contains(Map.Get<0>()))
{
ResultSettings->Result[Map.Get<0>()] = Map.Get<1>();
}
}
UpdatePreview(Settings->MapPreview, Settings->MapPreviewNamesMap);
}
void UBakeMeshAttributeMapsTool::UpdateOnModeChange()
{
OnMapTypesUpdated(
static_cast<EBakeMapType>(Settings->MapTypes),
ResultSettings->Result,
Settings->MapPreview,
Settings->MapPreviewNamesList,
Settings->MapPreviewNamesMap);
// Update tool property sets.
SetToolPropertySourceEnabled(OcclusionSettings, false);
SetToolPropertySourceEnabled(CurvatureSettings, false);
SetToolPropertySourceEnabled(HeightSettings, false);
SetToolPropertySourceEnabled(UVShellSettings, false);
SetToolPropertySourceEnabled(TextureSettings, false);
SetToolPropertySourceEnabled(MultiTextureSettings, false);
for (const EBakeMapType MapType : ENUM_EBAKEMAPTYPE_ALL)
{
switch ((EBakeMapType)Settings->MapTypes & MapType)
{
case EBakeMapType::TangentSpaceNormal:
break;
case EBakeMapType::AmbientOcclusion:
case EBakeMapType::BentNormal:
SetToolPropertySourceEnabled(OcclusionSettings, true);
break;
case EBakeMapType::Curvature:
SetToolPropertySourceEnabled(CurvatureSettings, true);
break;
case EBakeMapType::ObjectSpaceNormal:
case EBakeMapType::FaceNormal:
case EBakeMapType::Position:
case EBakeMapType::MaterialID:
case EBakeMapType::PolyGroupID:
case EBakeMapType::VertexColor:
break;
case EBakeMapType::Height:
SetToolPropertySourceEnabled(HeightSettings, true);
break;
case EBakeMapType::UVShell:
SetToolPropertySourceEnabled(UVShellSettings, true);
break;
case EBakeMapType::Texture:
SetToolPropertySourceEnabled(TextureSettings, true);
break;
case EBakeMapType::MultiTexture:
SetToolPropertySourceEnabled(MultiTextureSettings, true);
break;
default:
break;
}
}
}
void UBakeMeshAttributeMapsTool::InvalidateResults()
{
for (TTuple<EBakeMapType, TObjectPtr<UTexture2D>>& Result : ResultSettings->Result)
{
Result.Get<1>() = nullptr;
}
}
void UBakeMeshAttributeMapsTool::GatherAnalytics(FBakeAnalytics::FMeshSettings& Data)
{
if (FEngineAnalytics::IsAvailable())
{
Data.NumTargetMeshTris = TargetMesh->TriangleCount();
Data.NumDetailMesh = 1;
Data.NumDetailMeshTris = DetailMesh->TriangleCount();
}
}
#undef LOCTEXT_NAMESPACE