// 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(Component); USkeletalMeshComponent* SkeletalMesh = Cast(Component); UDynamicMeshComponent* DynMesh = Cast(Component); bValidTargets = bValidTargets && (StaticMesh || SkeletalMesh || DynMesh); }); return bValidTargets; } return false; } UMultiSelectionMeshEditingTool* UBakeMeshAttributeMapsToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { return NewObject(SceneState.ToolManager); } const TArray& UBakeMeshAttributeMapsToolProperties::GetMapPreviewNamesFunc() { return MapPreviewNamesList; } /* * Operators */ class FMeshMapBakerOp : public TGenericDataOperator { public: using ImagePtr = TSharedPtr, ESPMode::ThreadSafe>; // General bake settings TSharedPtr DetailMesh; TSharedPtr DetailSpatial; TSharedPtr, ESPMode::ThreadSafe> DetailMeshTangents; TSharedPtr BaseMesh; TUniquePtr Baker; UBakeMeshAttributeMapsTool::FBakeSettings BakeSettings; TSharedPtr, ESPMode::ThreadSafe> BaseMeshTangents; TSharedPtr, 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 MaterialIDTextures; // Begin TGenericDataOperator interface virtual void CalculateResult(FProgressCancel* Progress) override { Baker = MakeUnique(); 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(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& Eval) { if (!Eval) { Eval = MakeShared(); 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 OcclusionEval; for (const EBakeMapType MapType : ENUM_EBAKEMAPTYPE_ALL) { switch (BakeSettings.BakeMapTypes & MapType) { case EBakeMapType::TangentSpaceNormal: { TSharedPtr NormalEval = MakeShared(); 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 CurvatureEval = MakeShared(); 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 PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::Normal; DetailSampler.SetNormalTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailNormalTexture(DetailMeshNormalMap.Get(), DetailMeshNormalUVLayer, DetailMeshNormalSpace)); Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::FaceNormal: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::FacetNormal; Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::Position: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::Position; Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::MaterialID: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::MaterialID; Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::PolyGroupID: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::PolyGroupID; Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::Height: { TSharedPtr HeightEval = MakeShared(); 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 UVShellEval = MakeShared(); 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 PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::VertexColor; Baker->AddEvaluator(PropertyEval); break; } case EBakeMapType::Texture: { TSharedPtr TextureEval = MakeShared(); DetailSampler.SetTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailTexture(TextureImage.Get(), TextureSettings.UVLayer)); Baker->AddEvaluator(TextureEval); break; } case EBakeMapType::MultiTexture: { TSharedPtr TextureEval = MakeShared(); 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(); TargetMesh->Copy(Mesh); TargetMeshTangents = MakeShared(TargetMesh.Get()); TargetMeshTangents->CopyTriVertexTangents(Mesh); }); UToolTarget* Target = Targets[0]; UToolTarget* DetailTarget = Targets[bIsBakeToSelf ? 0 : 1]; // Setup tool property sets Settings = NewObject(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(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(this); ResultSettings->RestoreProperties(this); AddToolPropertySource(ResultSettings); SetToolPropertySourceEnabled(ResultSettings, true); OcclusionSettings = NewObject(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(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(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(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(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(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>& Result : ResultSettings->Result) { bCanAccept = bCanAccept && Result.Get<1>(); } } return bCanAccept; } TUniquePtr> UBakeMeshAttributeMapsTool::MakeNewOperator() { TUniquePtr Op = MakeUnique(); 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(Targets[0]); UObject* SourceAsset = StaticMeshTarget ? StaticMeshTarget->GetStaticMesh() : nullptr; if (!SourceAsset) { // Check if our target is a Skeletal Mesh Asset ISkeletalMeshBackedTarget* SkeletalMeshTarget = Cast(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(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(); DetailSpatial->SetMesh(DetailMesh.Get(), true); // Extract tangents if a DetailMesh normal map was provided. if (bWantMeshTangents) { DetailMeshTangents = MakeShared(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(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(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, 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>& 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(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>& 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