// Copyright Epic Games, Inc. All Rights Reserved. #include "BakeMeshAttributeVertexTool.h" #include "InteractiveToolManager.h" #include "ToolBuilderUtil.h" #include "ToolSetupUtil.h" #include "ModelingToolTargetUtil.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceDynamic.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/MeshResampleImageEvaluator.h" #include "Sampling/MeshHeightMapEvaluator.h" #include "TargetInterfaces/MaterialProvider.h" #include "TargetInterfaces/PrimitiveComponentBackedTarget.h" #include "TargetInterfaces/StaticMeshBackedTarget.h" #include "TargetInterfaces/SkeletalMeshBackedTarget.h" #include "ToolTargetManager.h" #include "AssetUtils/Texture2DUtil.h" #include "EngineAnalytics.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(BakeMeshAttributeVertexTool) using namespace UE::Geometry; #define LOCTEXT_NAMESPACE "UBakeMeshAttributeVertexTool" /* * ToolBuilder */ bool UBakeMeshAttributeVertexToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { const int32 NumTargets = SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()); return (NumTargets == 1 || NumTargets == 2); } UMultiSelectionMeshEditingTool* UBakeMeshAttributeVertexToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const { return NewObject(SceneState.ToolManager); } /* * Operators */ class FMeshVertexBakerOp : public TGenericDataOperator { public: // General bake settings TSharedPtr DetailMesh; TSharedPtr DetailSpatial; const FDynamicMesh3* BaseMesh; TSharedPtr, ESPMode::ThreadSafe> BaseMeshTangents; TUniquePtr Baker; bool bIsBakeToSelf = false; UBakeMeshAttributeVertexTool::FBakeSettings BakeSettings; FOcclusionMapSettings OcclusionSettings; FCurvatureMapSettings CurvatureSettings; FHeightMapSettings HeightSettings; FTexture2DSettings TextureSettings; FTexture2DSettings MultiTextureSettings; // Texture2DImage & MultiTexture settings using ImagePtr = TSharedPtr, ESPMode::ThreadSafe>; 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); Baker->SetTargetMeshTangents(BaseMeshTangents); Baker->SetProjectionDistance(BakeSettings.ProjectionDistance); if (bIsBakeToSelf) { Baker->SetCorrespondenceStrategy(FMeshBaseBaker::ECorrespondenceStrategy::Identity); } Baker->BakeMode = BakeSettings.OutputMode == EBakeVertexOutput::RGBA ? FMeshVertexBaker::EBakeMode::RGBA : FMeshVertexBaker::EBakeMode::PerChannel; FMeshBakerDynamicMeshSampler DetailSampler(DetailMesh.Get(), DetailSpatial.Get()); Baker->SetDetailSampler(&DetailSampler); auto InitOcclusionEvaluator = [this] (FMeshOcclusionMapEvaluator* OcclusionEval, const EMeshOcclusionMapType OcclusionType) { OcclusionEval->OcclusionType = OcclusionType; OcclusionEval->NumOcclusionRays = OcclusionSettings.OcclusionRays; OcclusionEval->MaxDistance = OcclusionSettings.MaxDistance; OcclusionEval->SpreadAngle = OcclusionSettings.SpreadAngle; OcclusionEval->BiasAngleDeg = OcclusionSettings.BiasAngle; switch (OcclusionSettings.NormalSpace) { case EBakeNormalSpace::Tangent: OcclusionEval->NormalSpace = FMeshOcclusionMapEvaluator::ESpace::Tangent; break; case EBakeNormalSpace::Object: OcclusionEval->NormalSpace = FMeshOcclusionMapEvaluator::ESpace::Object; break; } }; auto InitCurvatureEvaluator = [this] (FMeshCurvatureMapEvaluator* CurvatureEval) { CurvatureEval->RangeScale = FMathd::Clamp(CurvatureSettings.RangeMultiplier, 0.0001, 1000.0); CurvatureEval->MinRangeScale = FMathd::Clamp(CurvatureSettings.MinRangeMultiplier, 0.0, 1.0); CurvatureEval->UseCurvatureType = static_cast(CurvatureSettings.CurvatureType); CurvatureEval->UseColorMode = static_cast(CurvatureSettings.ColorMode); CurvatureEval->UseClampMode = static_cast(CurvatureSettings.ClampMode); }; auto InitHeightEvaluator = [this] (FMeshHeightMapEvaluator* HeightEval) { 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; } }; if (BakeSettings.OutputMode == EBakeVertexOutput::PerChannel) { for(int ChannelIdx = 0; ChannelIdx < 4; ++ChannelIdx) { switch(BakeSettings.OutputTypePerChannel[ChannelIdx]) { case EBakeMapType::AmbientOcclusion: { TSharedPtr OcclusionEval = MakeShared(); InitOcclusionEvaluator(OcclusionEval.Get(), EMeshOcclusionMapType::AmbientOcclusion); Baker->ChannelEvaluators[ChannelIdx] = OcclusionEval; break; } case EBakeMapType::Curvature: { TSharedPtr CurvatureEval = MakeShared(); InitCurvatureEvaluator(CurvatureEval.Get()); Baker->ChannelEvaluators[ChannelIdx] = CurvatureEval; break; } case EBakeMapType::Height: { TSharedPtr HeightEval = MakeShared(); InitHeightEvaluator(HeightEval.Get()); Baker->ChannelEvaluators[ChannelIdx] = HeightEval; break; } case EBakeMapType::One: case EBakeMapType::Zero: { const float Value = BakeSettings.OutputTypePerChannel[ChannelIdx] == EBakeMapType::One ? 1.0f : 0.0f; TSharedPtr ConstantEval = MakeShared(Value); Baker->ChannelEvaluators[ChannelIdx] = ConstantEval; break; } default: case EBakeMapType::None: { Baker->ChannelEvaluators[ChannelIdx] = nullptr; break; } } } } else // EBakeVertexOutput::RGBA { switch (BakeSettings.OutputType) { case EBakeMapType::TangentSpaceNormal: { Baker->ColorEvaluator = MakeShared(); break; } case EBakeMapType::AmbientOcclusion: { TSharedPtr OcclusionEval = MakeShared(); InitOcclusionEvaluator(OcclusionEval.Get(), EMeshOcclusionMapType::AmbientOcclusion); Baker->ColorEvaluator = OcclusionEval; break; } case EBakeMapType::BentNormal: { TSharedPtr OcclusionEval = MakeShared(); InitOcclusionEvaluator(OcclusionEval.Get(), EMeshOcclusionMapType::BentNormal); Baker->ColorEvaluator = OcclusionEval; break; } case EBakeMapType::Curvature: { TSharedPtr CurvatureEval = MakeShared(); InitCurvatureEvaluator(CurvatureEval.Get()); Baker->ColorEvaluator = CurvatureEval; break; } case EBakeMapType::Position: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::Position; Baker->ColorEvaluator = PropertyEval; break; } case EBakeMapType::ObjectSpaceNormal: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::Normal; Baker->ColorEvaluator = PropertyEval; break; } case EBakeMapType::FaceNormal: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::FacetNormal; Baker->ColorEvaluator = PropertyEval; break; } case EBakeMapType::MaterialID: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::MaterialID; Baker->ColorEvaluator = PropertyEval; break; } case EBakeMapType::PolyGroupID: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::PolyGroupID; Baker->ColorEvaluator = PropertyEval; break; } case EBakeMapType::Height: { TSharedPtr HeightEval = MakeShared(); InitHeightEvaluator(HeightEval.Get()); Baker->ColorEvaluator = HeightEval; break; } case EBakeMapType::Texture: { TSharedPtr TextureEval = MakeShared(); DetailSampler.SetTextureMap(DetailMesh.Get(), IMeshBakerDetailSampler::FBakeDetailTexture(TextureImage.Get(), TextureSettings.UVLayer)); Baker->ColorEvaluator = TextureEval; break; } case EBakeMapType::MultiTexture: { TSharedPtr TextureEval = MakeShared(); TextureEval->DetailUVLayer = MultiTextureSettings.UVLayer; TextureEval->MultiTextures = MaterialIDTextures; Baker->ColorEvaluator = TextureEval; break; } case EBakeMapType::VertexColor: { TSharedPtr PropertyEval = MakeShared(); PropertyEval->Property = EMeshPropertyMapType::VertexColor; Baker->ColorEvaluator = PropertyEval; break; } default: break; } } Baker->Bake(); SetResult(MoveTemp(Baker)); } // End TGenericDataOperator interface }; /* * Tool */ void UBakeMeshAttributeVertexTool::Setup() { TRACE_CPUPROFILER_EVENT_SCOPE(UBakeMeshAttributeVertexTool::Setup); Super::Setup(); UMaterial* Material = LoadObject(nullptr, TEXT("/MeshModelingToolsetExp/Materials/MeshVertexColorMaterial")); check(Material); if (Material != nullptr) { PreviewMaterial = UMaterialInstanceDynamic::Create(Material, GetToolManager()); } UMaterial* AlphaMaterial = LoadObject(nullptr, TEXT("/MeshModelingToolsetExp/Materials/MeshVertexAlphaMaterial")); check(AlphaMaterial); if (AlphaMaterial != nullptr) { PreviewAlphaMaterial = UMaterialInstanceDynamic::Create(AlphaMaterial, GetToolManager()); } bIsBakeToSelf = (Targets.Num() == 1); UE::ToolTarget::HideSourceObject(Targets[0]); // TargetMesh stores the original target mesh. It is intended to remain // const throughout this tool and is used to refresh the PreviewMesh back // to its original state. static FGetMeshParameters GetMeshParams; GetMeshParams.bWantMeshTangents = true; TargetMesh = MakeShared(UE::ToolTarget::GetDynamicMeshCopy(Targets[0], GetMeshParams)); TargetMeshTangents = MakeShared(TargetMesh.Get()); TargetMeshTangents->CopyTriVertexTangents(*TargetMesh); // PreviewMesh stores computed result mesh. On shutdown, PreviewMesh will be // used to commit the dynamic mesh to the target tool target. PreviewMesh = CreateBakePreviewMesh(this, Targets[0], GetTargetWorld()); PreviewMesh->SetOverrideRenderMaterial(PreviewMaterial); 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->OutputMode, [this](EBakeVertexOutput) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->OutputType, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->OutputTypeR, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->OutputTypeG, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->OutputTypeB, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->OutputTypeA, [this](int32) { OpState |= EBakeOpState::Evaluate; UpdateOnModeChange(); }); Settings->WatchProperty(Settings->PreviewMode, [this](EBakeVertexChannel) { UpdateVisualization(); }); Settings->WatchProperty(Settings->TopologyMode, [this](EBakeVertexTopology) { bColorTopologyValid = false; OpState |= EBakeOpState::Evaluate; }); Settings->WatchProperty(Settings->bSplitAtNormalSeams, [this](bool) { bColorTopologyValid = false; OpState |= EBakeOpState::Evaluate; }); Settings->WatchProperty(Settings->bSplitAtUVSeams, [this](bool) { bColorTopologyValid = false; OpState |= EBakeOpState::Evaluate; }); InputMeshSettings = NewObject(this); InputMeshSettings->RestoreProperties(this); AddToolPropertySource(InputMeshSettings); SetToolPropertySourceEnabled(InputMeshSettings, true); InputMeshSettings->bHasTargetUVLayer = false; InputMeshSettings->bHasSourceNormalMap = false; 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; InputMeshSettings->WatchProperty(InputMeshSettings->bHideSourceMesh, [this](bool bState) { SetSourceObjectVisible(!bState); }); InputMeshSettings->WatchProperty(InputMeshSettings->ProjectionDistance, [this](float) { OpState |= EBakeOpState::Evaluate; }); InputMeshSettings->WatchProperty(InputMeshSettings->bProjectionInWorldSpace, [this](bool) { OpState |= EBakeOpState::EvaluateDetailMesh; }); SetSourceObjectVisible(!InputMeshSettings->bHideSourceMesh); 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; }); 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 Vertex Colors")); GetToolManager()->DisplayMessage( LOCTEXT("OnStartTool", "Bake Vertex Colors. Select Bake Mesh (LowPoly) first, then (optionally) Detail Mesh second."), EToolMessageLevel::UserNotification); // Initialize background compute Compute = MakeUnique>(); Compute->Setup(this); Compute->OnResultUpdated.AddLambda([this](const TUniquePtr& NewResult) { OnResultUpdated(NewResult); }); GatherAnalytics(BakeAnalytics.MeshSettings); } void UBakeMeshAttributeVertexTool::OnShutdown(EToolShutdownType ShutdownType) { TRACE_CPUPROFILER_EVENT_SCOPE(UBakeMeshAttributeVertexTool::Shutdown); Settings->SaveProperties(this); InputMeshSettings->SaveProperties(this); OcclusionSettings->SaveProperties(this); CurvatureSettings->SaveProperties(this); HeightSettings->SaveProperties(this); TextureSettings->SaveProperties(this); MultiTextureSettings->SaveProperties(this); UE::ToolTarget::ShowSourceObject(Targets[0]); SetSourceObjectVisible(true); Compute->Shutdown(); if (ShutdownType == EToolShutdownType::Accept) { GetToolManager()->BeginUndoTransaction(LOCTEXT("BakeMeshAttributeVertexToolTransactionName", "Bake Mesh Attribute Vertex")); FConversionToMeshDescriptionOptions ConvertOptions; ConvertOptions.SetToVertexColorsOnly(); ConvertOptions.bTransformVtxColorsSRGBToLinear = true; UE::ToolTarget::CommitDynamicMeshUpdate( Targets[0], *PreviewMesh->GetMesh(), false, // bHaveModifiedTopology ConvertOptions); GetToolManager()->EndUndoTransaction(); } PreviewMesh->SetVisible(false); PreviewMesh->Disconnect(); PreviewMesh = nullptr; RecordAnalytics(BakeAnalytics, TEXT("BakeVertex")); } void UBakeMeshAttributeVertexTool::OnTick(float DeltaTime) { Compute->Tick(DeltaTime); if (static_cast(OpState & EBakeOpState::Invalid)) { PreviewMesh->SetOverrideRenderMaterial(ErrorPreviewMaterial); } else if (!CanAccept() && Compute->GetElapsedComputeTime() > SecondsBeforeWorkingMaterial) { PreviewMesh->SetOverrideRenderMaterial(WorkingPreviewMaterial); } } void UBakeMeshAttributeVertexTool::Render(IToolsContextRenderAPI* RenderAPI) { UpdateResult(); } bool UBakeMeshAttributeVertexTool::CanAccept() const { const bool bValidOp = (OpState & EBakeOpState::Invalid) != EBakeOpState::Invalid; return bValidOp && Compute->HaveValidResult(); } TUniquePtr> UBakeMeshAttributeVertexTool::MakeNewOperator() { TUniquePtr Op = MakeUnique(); Op->DetailMesh = DetailMesh; Op->DetailSpatial = DetailSpatial; // Pass the PreviewMesh here instead of the TargetMesh. The PreviewMesh // contains the updated color topology. TargetMesh holds onto the original // color topology and values. Op->BaseMesh = PreviewMesh->GetMesh(); Op->BaseMeshTangents = TargetMeshTangents; Op->BakeSettings = CachedBakeSettings; Op->OcclusionSettings = CachedOcclusionMapSettings; Op->CurvatureSettings = CachedCurvatureMapSettings; Op->HeightSettings = CachedHeightMapSettings; Op->TextureSettings = CachedTexture2DSettings; Op->MultiTextureSettings = CachedMultiTexture2DSettings; Op->bIsBakeToSelf = bIsBakeToSelf; // Texture2DImage & MultiTexture settings Op->TextureImage = CachedTextureImage; Op->MaterialIDTextures = CachedMultiTextures; return Op; } void UBakeMeshAttributeVertexTool::UpdateDetailMesh() { IPrimitiveComponentBackedTarget* TargetComponent = Cast(Targets[0]); IPrimitiveComponentBackedTarget* DetailComponent = Cast(Targets[bIsBakeToSelf ? 0 : 1]); UToolTarget* DetailTargetMesh = Targets[bIsBakeToSelf ? 0 : 1]; static FGetMeshParameters GetMeshParams; GetMeshParams.bWantMeshTangents = true; const FDynamicMesh3 DetailMeshCopy = UE::ToolTarget::GetDynamicMeshCopy(DetailTargetMesh, GetMeshParams); DetailMesh = MakeShared(); DetailMesh->Copy(DetailMeshCopy); if (InputMeshSettings->bProjectionInWorldSpace && bIsBakeToSelf == false) { const FTransformSRT3d DetailToWorld(DetailComponent->GetWorldTransform()); MeshTransforms::ApplyTransform(*DetailMesh, DetailToWorld, true); const FTransformSRT3d WorldToBase(TargetComponent->GetWorldTransform()); MeshTransforms::ApplyTransformInverse(*DetailMesh, WorldToBase, true); } DetailSpatial = MakeShared(); DetailSpatial->SetMesh(DetailMesh.Get(), true); UpdateUVLayerNames(TextureSettings->UVLayer, TextureSettings->UVLayerNamesList, *DetailMesh); UpdateUVLayerNames(MultiTextureSettings->UVLayer, MultiTextureSettings->UVLayerNamesList, *DetailMesh); OpState &= ~EBakeOpState::EvaluateDetailMesh; OpState |= EBakeOpState::Evaluate; DetailMeshTimestamp++; } void UBakeMeshAttributeVertexTool::UpdateOnModeChange() { SetToolPropertySourceEnabled(OcclusionSettings, false); SetToolPropertySourceEnabled(CurvatureSettings, false); SetToolPropertySourceEnabled(HeightSettings, false); SetToolPropertySourceEnabled(TextureSettings, false); SetToolPropertySourceEnabled(MultiTextureSettings, false); if (Settings->OutputMode == EBakeVertexOutput::RGBA) { switch (static_cast(Settings->OutputType)) { case EBakeMapType::AmbientOcclusion: case EBakeMapType::BentNormal: SetToolPropertySourceEnabled(OcclusionSettings, true); break; case EBakeMapType::Curvature: SetToolPropertySourceEnabled(CurvatureSettings, true); break; case EBakeMapType::Height: SetToolPropertySourceEnabled(HeightSettings, true); break; case EBakeMapType::Texture: SetToolPropertySourceEnabled(TextureSettings, true); break; case EBakeMapType::MultiTexture: SetToolPropertySourceEnabled(MultiTextureSettings, true); break; default: // No property sets to show. break; } } else // Settings->VertexMode == EBakeVertexOutput::PerChannel { const EBakeMapType PerChannelTypes[4] = { static_cast(Settings->OutputTypeR), static_cast(Settings->OutputTypeG), static_cast(Settings->OutputTypeB), static_cast(Settings->OutputTypeA) }; for(int Idx = 0; Idx < 4; ++Idx) { switch(PerChannelTypes[Idx]) { case EBakeMapType::AmbientOcclusion: SetToolPropertySourceEnabled(OcclusionSettings, true); break; case EBakeMapType::Curvature: SetToolPropertySourceEnabled(CurvatureSettings, true); break; case EBakeMapType::Height: SetToolPropertySourceEnabled(HeightSettings, true); break; case EBakeMapType::None: default: break; } } } } void UBakeMeshAttributeVertexTool::UpdateVisualization() { if (Settings->PreviewMode == EBakeVertexChannel::A) { PreviewMesh->SetOverrideRenderMaterial(PreviewAlphaMaterial); } else { FLinearColor Mask(FLinearColor::Black); switch(Settings->PreviewMode) { case EBakeVertexChannel::R: Mask.R = 1.0f; break; case EBakeVertexChannel::G: Mask.G = 1.0f; break; case EBakeVertexChannel::B: Mask.B = 1.0f; break; case EBakeVertexChannel::RGBA: default: Mask = FLinearColor::White; break; } PreviewMaterial->SetVectorParameterValue("VertexColorMask", Mask); PreviewMesh->SetOverrideRenderMaterial(PreviewMaterial); } } /** Regenerates the VertexColorOverlay topology of the PreviewMesh per our Settings */ void UBakeMeshAttributeVertexTool::UpdateColorTopology() { // Update PreviewMesh color topology if (Settings->TopologyMode == EBakeVertexTopology::CreateNew) { PreviewMesh->EditMesh([this](FDynamicMesh3& Mesh) { Mesh.EnableAttributes(); Mesh.Attributes()->EnablePrimaryColors(); Mesh.Attributes()->PrimaryColors()->ClearElements(); FDynamicMeshNormalOverlay* NormalOverlay = Mesh.Attributes()->PrimaryNormals(); FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes()->PrimaryUV(); Mesh.Attributes()->PrimaryColors()->CreateFromPredicate( [this, NormalOverlay, UVOverlay](int /*ParentVID*/, int TriIDA, int TriIDB) -> bool { auto OverlayCanShare = [TriIDA, TriIDB] (auto Overlay) -> bool { return Overlay ? Overlay->AreTrianglesConnected(TriIDA, TriIDB) : true; }; bool bCanShare = true; if (Settings->bSplitAtNormalSeams) { bCanShare = bCanShare && OverlayCanShare(NormalOverlay); } if (Settings->bSplitAtUVSeams) { bCanShare = bCanShare && OverlayCanShare(UVOverlay); } return bCanShare; }, 0.0f); }); } else { PreviewMesh->EditMesh([this](FDynamicMesh3& Mesh) { // Copy original color overlay from TargetMesh Mesh.EnableAttributes(); Mesh.Attributes()->EnablePrimaryColors(); Mesh.Attributes()->PrimaryColors()->ClearElements(); if (TargetMesh->Attributes() && TargetMesh->Attributes()->PrimaryColors()) { Mesh.Attributes()->PrimaryColors()->Copy(*TargetMesh->Attributes()->PrimaryColors()); } }); } // Copy source vertex colors onto new color overlay topology. UpdateSourceVertexColors(); NumColorElements = PreviewMesh->GetMesh()->Attributes()->PrimaryColors()->ElementCount(); bColorTopologyValid = true; } /** Copies the vertex colors from the TargetMesh to the PreviewMesh */ void UBakeMeshAttributeVertexTool::UpdateSourceVertexColors() { PreviewMesh->EditMesh([this](FDynamicMesh3& Mesh) { const FDynamicMeshColorOverlay* TargetColorOverlay = TargetMesh->HasAttributes() ? TargetMesh->Attributes()->PrimaryColors() : nullptr; FDynamicMeshColorOverlay* PreviewColorOverlay = Mesh.Attributes()->PrimaryColors(); if (TargetColorOverlay) { for (int VId : Mesh.VertexIndicesItr()) { Mesh.EnumerateVertexTriangles(VId, [VId, TargetColorOverlay, PreviewColorOverlay](int32 TriID) { const FVector4f TargetColor = TargetColorOverlay->GetElementAtVertex(TriID, VId); const int ElemId = PreviewColorOverlay->GetElementIDAtVertex(TriID, VId); PreviewColorOverlay->SetElement(ElemId, TargetColor); }); } } }); } void UBakeMeshAttributeVertexTool::UpdateResult() { if (static_cast(OpState & EBakeOpState::EvaluateDetailMesh)) { UpdateDetailMesh(); } if (!bColorTopologyValid) { UpdateColorTopology(); } if (OpState == EBakeOpState::Clean) { return; } // clear warning (ugh) GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning); FBakeSettings BakeSettings; BakeSettings.OutputMode = Settings->OutputMode; BakeSettings.OutputType = static_cast(Settings->OutputType); BakeSettings.OutputTypePerChannel[0] = static_cast(Settings->OutputTypeR); BakeSettings.OutputTypePerChannel[1] = static_cast(Settings->OutputTypeG); BakeSettings.OutputTypePerChannel[2] = static_cast(Settings->OutputTypeB); BakeSettings.OutputTypePerChannel[3] = static_cast(Settings->OutputTypeA); BakeSettings.TopologyMode = Settings->TopologyMode; BakeSettings.bSplitAtNormalSeams = Settings->bSplitAtNormalSeams; BakeSettings.bSplitAtUVSeams = Settings->bSplitAtUVSeams; BakeSettings.bProjectionInWorldSpace = InputMeshSettings->bProjectionInWorldSpace; BakeSettings.ProjectionDistance = InputMeshSettings->ProjectionDistance; if (!(BakeSettings == CachedBakeSettings)) { CachedBakeSettings = BakeSettings; } // Clear our invalid bitflag to check again for valid inputs. OpState &= ~EBakeOpState::Invalid; // Validate bake inputs if (NumColorElements <= 0) { if (Settings->TopologyMode == EBakeVertexTopology::UseExisting) { GetToolManager()->DisplayMessage(LOCTEXT("InvalidExistingVertexColorTopology", "Topology Mode is set to UseExisting, but no valid existing vertex color topology was found."), EToolMessageLevel::UserWarning); } else { GetToolManager()->DisplayMessage(LOCTEXT("InvalidNewVertexColorTopology", "Invalid vertex color topology."), EToolMessageLevel::UserWarning); } OpState |= EBakeOpState::Invalid; } const FImageDimensions Dimensions(NumColorElements, 1); if (CachedBakeSettings.OutputMode == EBakeVertexOutput::RGBA) { switch(CachedBakeSettings.OutputType) { case EBakeMapType::TangentSpaceNormal: OpState |= UpdateResult_Normal(Dimensions); break; case EBakeMapType::AmbientOcclusion: OpState |= UpdateResult_Occlusion(Dimensions); break; case EBakeMapType::BentNormal: OpState |= UpdateResult_Occlusion(Dimensions); break; case EBakeMapType::Curvature: OpState |= UpdateResult_Curvature(Dimensions); break; case EBakeMapType::ObjectSpaceNormal: case EBakeMapType::FaceNormal: case EBakeMapType::Position: case EBakeMapType::MaterialID: case EBakeMapType::PolyGroupID: OpState |= UpdateResult_MeshProperty(Dimensions); break; case EBakeMapType::Height: OpState |= UpdateResult_Height(Dimensions); break; case EBakeMapType::VertexColor: OpState |= UpdateResult_MeshProperty(Dimensions); // Force copy the original vertex colors to our PreviewMesh so that // the baker samples the source vertex colors for identity bakes. UpdateSourceVertexColors(); break; case EBakeMapType::Texture: OpState |= UpdateResult_Texture2DImage(Dimensions, DetailMesh.Get()); break; case EBakeMapType::MultiTexture: OpState |= UpdateResult_MultiTexture(Dimensions, DetailMesh.Get()); break; default: break; } OpState |= UpdateResult_TargetMeshTangents(CachedBakeSettings.OutputType); } else // CachedBakeSettings.VertexMode == EBakeVertexOutput::PerChannel { // The enabled state of these settings are precomputed in UpdateOnModeChange(). if (OcclusionSettings->IsPropertySetEnabled()) { OpState |= UpdateResult_Occlusion(Dimensions); } if (CurvatureSettings->IsPropertySetEnabled()) { OpState |= UpdateResult_Curvature(Dimensions); } if (HeightSettings->IsPropertySetEnabled()) { OpState |= UpdateResult_Height(Dimensions); } // Always force copy the original vertex colors to our PreviewMesh for // PerChannel bakes so that channels that are not targeted persist // through the bake. UpdateSourceVertexColors(); } // Early exit if op input parameters are invalid. if ((bool)(OpState & EBakeOpState::Invalid)) { return; } Compute->InvalidateResult(); OpState = EBakeOpState::Clean; } void UBakeMeshAttributeVertexTool::OnResultUpdated(const TUniquePtr& NewResult) { const TImageBuilder* ImageResult = NewResult->GetBakeResult(); if (!ImageResult) { return; } PreviewMesh->DeferredEditMesh([this, &ImageResult](FDynamicMesh3& Mesh) { if (CachedBakeSettings.OutputMode == EBakeVertexOutput::PerChannel) { // Precompute scale vectors for source and image pixel data to merge // the data according to the populated channels. FVector4f SrcScale = FVector4f::Zero(); FVector4f ImgScale = FVector4f::Zero(); for (int ChannelIdx = 0; ChannelIdx < 4; ++ChannelIdx) { const bool bOutputChannel = CachedBakeSettings.OutputTypePerChannel[ChannelIdx] != EBakeMapType::None; SrcScale[ChannelIdx] = static_cast(!bOutputChannel); ImgScale[ChannelIdx] = static_cast(bOutputChannel); } const int NumColors = Mesh.Attributes()->PrimaryColors()->ElementCount(); check(NumColors == ImageResult->GetDimensions().GetWidth()); for (int Idx = 0; Idx < NumColors; ++Idx) { if (const FDynamicMeshColorOverlay* ColorOverlay = Mesh.Attributes()->PrimaryColors()) { FVector4f Pixel; ColorOverlay->GetElement(Idx, Pixel); Pixel *= SrcScale; // Merge the ImageResult pixels with the source vertex colors based on the requested channels. const FVector4f& ImagePixel = ImageResult->GetPixel(Idx); Pixel += ImagePixel * ImgScale; Mesh.Attributes()->PrimaryColors()->SetElement(Idx, Pixel); } } } else //if (Settings->OutputMode == EBakeVertexOutput::RGBA) { const int NumColors = Mesh.Attributes()->PrimaryColors()->ElementCount(); check(NumColors == ImageResult->GetDimensions().GetWidth()); for (int Idx = 0; Idx < NumColors; ++Idx) { const FVector4f& Pixel = ImageResult->GetPixel(Idx); Mesh.Attributes()->PrimaryColors()->SetElement(Idx, Pixel); } } }, false); PreviewMesh->NotifyDeferredEditCompleted(UPreviewMesh::ERenderUpdateMode::FastUpdate, EMeshRenderAttributeFlags::VertexColors, false); UpdateVisualization(); GatherAnalytics(*NewResult, CachedBakeSettings, BakeAnalytics); } void UBakeMeshAttributeVertexTool::GatherAnalytics(FBakeAnalytics::FMeshSettings& Data) { if (!FEngineAnalytics::IsAvailable()) { return; } Data.NumTargetMeshVerts = TargetMesh->VertexCount(); Data.NumTargetMeshTris = TargetMesh->TriangleCount(); Data.NumDetailMesh = 1; Data.NumDetailMeshTris = DetailMesh->TriangleCount(); } void UBakeMeshAttributeVertexTool::GatherAnalytics( const FMeshVertexBaker& Result, const FBakeSettings& Settings, FBakeAnalytics& Data) { if (!FEngineAnalytics::IsAvailable()) { return; } Data.TotalBakeDuration = Result.TotalBakeDuration; Data.BakeSettings = Settings; auto GatherEvaluatorData = [&Data](const FMeshMapEvaluator* Eval) { if (Eval) { switch(Eval->Type()) { case EMeshMapEvaluatorType::Occlusion: { const FMeshOcclusionMapEvaluator* OcclusionEval = static_cast(Eval); Data.OcclusionSettings.OcclusionRays = OcclusionEval->NumOcclusionRays; Data.OcclusionSettings.MaxDistance = OcclusionEval->MaxDistance; Data.OcclusionSettings.SpreadAngle = OcclusionEval->SpreadAngle; Data.OcclusionSettings.BiasAngle = OcclusionEval->BiasAngleDeg; break; } case EMeshMapEvaluatorType::Curvature: { const FMeshCurvatureMapEvaluator* CurvatureEval = static_cast(Eval); Data.CurvatureSettings.CurvatureType = static_cast(CurvatureEval->UseCurvatureType); Data.CurvatureSettings.RangeMultiplier = CurvatureEval->RangeScale; Data.CurvatureSettings.MinRangeMultiplier = CurvatureEval->MinRangeScale; Data.CurvatureSettings.ColorMode = static_cast(CurvatureEval->UseColorMode); Data.CurvatureSettings.ClampMode = static_cast(CurvatureEval->UseClampMode); break; } default: break; }; } }; if (Result.BakeMode == FMeshVertexBaker::EBakeMode::RGBA) { GatherEvaluatorData(Result.ColorEvaluator.Get()); } else // Result.BakeMode == FMeshVertexBaker::EBakeMode::Channel { for (int EvalId = 0; EvalId < 4; ++EvalId) { GatherEvaluatorData(Result.ChannelEvaluators[EvalId].Get()); } } } void UBakeMeshAttributeVertexTool::RecordAnalytics(const FBakeAnalytics& Data, const FString& EventName) { if (!FEngineAnalytics::IsAvailable()) { return; } TArray Attributes; // General Attributes.Add(FAnalyticsEventAttribute(TEXT("Bake.Duration.Total.Seconds"), Data.TotalBakeDuration)); // Mesh data Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.TargetMesh.NumTriangles"), Data.MeshSettings.NumTargetMeshTris)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.TargetMesh.NumVertices"), Data.MeshSettings.NumTargetMeshVerts)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.DetailMesh.NumMeshes"), Data.MeshSettings.NumDetailMesh)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Input.DetailMesh.NumTriangles"), Data.MeshSettings.NumDetailMeshTris)); // Bake settings Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.Split.NormalSeams"), Data.BakeSettings.bSplitAtNormalSeams)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.Split.UVSeams"), Data.BakeSettings.bSplitAtUVSeams)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.ProjectionDistance"), Data.BakeSettings.ProjectionDistance)); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.ProjectionInWorldSpace"), Data.BakeSettings.bProjectionInWorldSpace)); const FString OutputType = Data.BakeSettings.OutputMode == EBakeVertexOutput::RGBA ? TEXT("RGBA") : TEXT("PerChannel"); Attributes.Add(FAnalyticsEventAttribute(TEXT("Settings.Output.Type"), OutputType)); auto RecordAmbientOcclusionSettings = [&Attributes, &Data](const FString& ModeName) { Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.AmbientOcclusion.OcclusionRays"), *ModeName), Data.OcclusionSettings.OcclusionRays)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.AmbientOcclusion.MaxDistance"), *ModeName), Data.OcclusionSettings.MaxDistance)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.AmbientOcclusion.SpreadAngle"), *ModeName), Data.OcclusionSettings.SpreadAngle)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.AmbientOcclusion.BiasAngle"), *ModeName), Data.OcclusionSettings.BiasAngle)); }; auto RecordBentNormalSettings = [&Attributes, &Data](const FString& ModeName) { Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.BentNormal.OcclusionRays"), *ModeName), Data.OcclusionSettings.OcclusionRays)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.BentNormal.MaxDistance"), *ModeName), Data.OcclusionSettings.MaxDistance)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.BentNormal.SpreadAngle"), *ModeName), Data.OcclusionSettings.SpreadAngle)); }; auto RecordCurvatureSettings = [&Attributes, &Data](const FString& ModeName) { Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Curvature.CurvatureType"), *ModeName), Data.CurvatureSettings.CurvatureType)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Curvature.RangeMultiplier"), *ModeName), Data.CurvatureSettings.RangeMultiplier)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Curvature.MinRangeMultiplier"), *ModeName), Data.CurvatureSettings.MinRangeMultiplier)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Curvature.ClampMode"), *ModeName), Data.CurvatureSettings.ClampMode)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Curvature.ColorMode"), *ModeName), Data.CurvatureSettings.ColorMode)); }; if (Data.BakeSettings.OutputMode == EBakeVertexOutput::RGBA) { const FString OutputName(TEXT("RGBA")); FString OutputTypeName = StaticEnum()->GetNameStringByValue(static_cast(Data.BakeSettings.OutputType)); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Type"), *OutputName), OutputTypeName)); switch (Data.BakeSettings.OutputType) { case EBakeMapType::AmbientOcclusion: RecordAmbientOcclusionSettings(OutputName); break; case EBakeMapType::BentNormal: RecordBentNormalSettings(OutputName); break; case EBakeMapType::Curvature: RecordCurvatureSettings(OutputName); break; default: break; } } else { ensure(Data.BakeSettings.OutputMode == EBakeVertexOutput::PerChannel); for (int EvalId = 0; EvalId < 4; ++EvalId) { FString OutputName = StaticEnum()->GetNameStringByIndex(EvalId); FString OutputTypeName = StaticEnum()->GetNameStringByValue(static_cast(Data.BakeSettings.OutputTypePerChannel[EvalId])); Attributes.Add(FAnalyticsEventAttribute(FString::Printf(TEXT("Settings.Output.%s.Type"), *OutputName), OutputTypeName)); switch (Data.BakeSettings.OutputTypePerChannel[EvalId]) { case EBakeMapType::AmbientOcclusion: RecordAmbientOcclusionSettings(OutputName); break; case EBakeMapType::Curvature: RecordCurvatureSettings(OutputName); break; default: break; } } } FEngineAnalytics::GetProvider().RecordEvent(FString(TEXT("Editor.Usage.MeshModelingMode.")) + EventName, Attributes); constexpr bool bLogAnalytics = false; if constexpr (bLogAnalytics) { for (const FAnalyticsEventAttribute& Attr : Attributes) { UE_LOG(LogGeometry, Log, TEXT("[%s] %s = %s"), *EventName, *Attr.GetName(), *Attr.GetValue()); } } } #undef LOCTEXT_NAMESPACE