Files
Andron666 9c38e93fa4 part7
2022-12-05 20:31:35 +05:00

6018 lines
206 KiB
C++

/*
* Copyright (c) <2021> Side Effects Software Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. The name of Side Effects Software may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "HoudiniEngineBakeUtils.h"
#include "HoudiniEngineEditorPrivatePCH.h"
#include "HoudiniEngineUtils.h"
#include "HoudiniAssetActor.h"
#include "HoudiniAsset.h"
#include "HoudiniAssetComponent.h"
#include "HoudiniOutput.h"
#include "HoudiniSplineComponent.h"
#include "HoudiniGeoPartObject.h"
#include "HoudiniPackageParams.h"
#include "HoudiniEnginePrivatePCH.h"
#include "HoudiniRuntimeSettings.h"
#include "HoudiniEngineUtils.h"
#include "UnrealLandscapeTranslator.h"
#include "HoudiniInstanceTranslator.h"
#include "HoudiniInstancedActorComponent.h"
#include "HoudiniMeshSplitInstancerComponent.h"
#include "HoudiniPDGAssetLink.h"
#include "HoudiniStringResolver.h"
#include "HoudiniEngineCommands.h"
#include "HoudiniEngineRuntimeUtils.h"
#include "Engine/StaticMesh.h"
#include "Engine/World.h"
#include "RawMesh.h"
#include "UObject/Package.h"
#include "PackageTools.h"
#include "UObject/MetaData.h"
#include "AssetRegistryModule.h"
#include "Materials/Material.h"
#include "LandscapeProxy.h"
#include "LandscapeStreamingProxy.h"
#include "LandscapeInfo.h"
#include "Factories/WorldFactory.h"
#include "AssetToolsModule.h"
#include "InstancedFoliageActor.h"
#include "Components/SplineComponent.h"
#include "GameFramework/Actor.h"
#include "Engine/StaticMeshActor.h"
#include "Components/StaticMeshComponent.h"
#include "PhysicsEngine/BodySetup.h"
#include "ActorFactories/ActorFactoryStaticMesh.h"
#include "ActorFactories/ActorFactoryEmptyActor.h"
#include "BusyCursor.h"
#include "Editor.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "FileHelpers.h"
#include "HoudiniEngineEditor.h"
#include "HoudiniEngine.h"
#include "HoudiniLandscapeTranslator.h"
#include "HoudiniOutputTranslator.h"
#include "Editor/EditorEngine.h"
#include "Factories/BlueprintFactory.h"
#include "Engine/SimpleConstructionScript.h"
#include "Misc/Paths.h"
#include "HAL/FileManager.h"
#include "LandscapeEdit.h"
#include "Containers/UnrealString.h"
#include "Components/AudioComponent.h"
#include "Engine/WorldComposition.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "MaterialEditor/Public/MaterialEditingLibrary.h"
#include "MaterialGraph/MaterialGraph.h"
#include "Materials/MaterialInstance.h"
#include "Particles/ParticleSystemComponent.h"
#include "Sound/SoundBase.h"
#include "UObject/UnrealType.h"
#include "Math/Box.h"
#include "Misc/ScopedSlowTask.h"
HOUDINI_BAKING_DEFINE_LOG_CATEGORY();
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
FHoudiniEngineBakedActor::FHoudiniEngineBakedActor()
: Actor(nullptr)
, OutputIndex(INDEX_NONE)
, OutputObjectIdentifier()
, ActorBakeName(NAME_None)
, BakedObject(nullptr)
, SourceObject(nullptr)
, BakeFolderPath()
, bInstancerOutput(false)
, bPostBakeProcessPostponed(false)
{
}
FHoudiniEngineBakedActor::FHoudiniEngineBakedActor(
AActor* InActor,
FName InActorBakeName,
FName InWorldOutlinerFolder,
int32 InOutputIndex,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
UObject* InBakedObject,
UObject* InSourceObject,
UObject* InBakedComponent,
const FString& InBakeFolderPath,
const FHoudiniPackageParams& InBakedObjectPackageParams)
: Actor(InActor)
, OutputIndex(InOutputIndex)
, OutputObjectIdentifier(InOutputObjectIdentifier)
, ActorBakeName(InActorBakeName)
, WorldOutlinerFolder(InWorldOutlinerFolder)
, BakedObject(InBakedObject)
, SourceObject(InSourceObject)
, BakedComponent(InBakedComponent)
, BakeFolderPath(InBakeFolderPath)
, BakedObjectPackageParams(InBakedObjectPackageParams)
, bInstancerOutput(false)
, bPostBakeProcessPostponed(false)
{
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(
UHoudiniAssetComponent* InHACToBake,
bool bInReplacePreviousBake,
EHoudiniEngineBakeOption InBakeOption,
bool bInRemoveHACOutputOnSuccess,
bool bInRecenterBakedActors)
{
if (!IsValid(InHACToBake))
return false;
// Handle proxies: if the output has any current proxies, first refine them
bool bHACNeedsToReCook;
if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors, bHACNeedsToReCook))
{
// Either the component is invalid, or needs a recook to refine a proxy mesh
return false;
}
bool bSuccess = false;
switch (InBakeOption)
{
case EHoudiniEngineBakeOption::ToActor:
{
bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake, bInRecenterBakedActors);
}
break;
case EHoudiniEngineBakeOption::ToBlueprint:
{
bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake, bInRecenterBakedActors);
}
break;
case EHoudiniEngineBakeOption::ToFoliage:
{
TMap<UMaterialInterface *, UMaterialInterface *> AlreadyBakedMaterialsMap;
bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake, AlreadyBakedMaterialsMap);
}
break;
case EHoudiniEngineBakeOption::ToWorldOutliner:
{
//Todo
bSuccess = false;
}
break;
}
if (bSuccess && bInRemoveHACOutputOnSuccess)
{
TArray<UHoudiniOutput*> DeferredClearOutputs;
FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true);
}
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(
UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
TArray<FHoudiniEngineBakedActor> NewActors;
TArray<UPackage*> PackagesToSave;
FHoudiniEngineOutputStats BakeStats;
const bool bBakedWithErrors = !FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(
HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats);
if (bBakedWithErrors)
{
// TODO ?
HOUDINI_LOG_WARNING(TEXT("Errors when baking"));
}
// Save the created packages
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Recenter and select the baked actors
if (GEditor && NewActors.Num() > 0)
GEditor->SelectNone(false, true);
for (const FHoudiniEngineBakedActor& Entry : NewActors)
{
if (!IsValid(Entry.Actor))
continue;
if (bInRecenterBakedActors)
CenterActorToBoundingBoxCenter(Entry.Actor);
if (GEditor)
GEditor->SelectActor(Entry.Actor, true, false);
}
if (GEditor && NewActors.Num() > 0)
GEditor->NoteSelectionChange();
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
// Broadcast that the bake is complete
HoudiniAssetComponent->HandleOnPostBake(!bBakedWithErrors);
return true;
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(
UHoudiniAssetComponent* HoudiniAssetComponent,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutNewActors,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats,
TArray<EHoudiniOutputType> const* InOutputTypesToBake,
TArray<EHoudiniInstancerComponentType> const* InInstancerComponentTypesToBake,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
AActor* OwnerActor = HoudiniAssetComponent->GetOwner();
if (!IsValid(OwnerActor))
return false;
const FString HoudiniAssetName = OwnerActor->GetName();
// Get an array of the outputs
const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs();
TArray<UHoudiniOutput*> Outputs;
Outputs.Reserve(NumOutputs);
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx)
{
Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx));
}
// Get the previous bake objects and grow/shrink to match asset outputs
TArray<FHoudiniBakedOutput>& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs();
// Ensure we have an entry for each output
if (BakedOutputs.Num() != NumOutputs)
BakedOutputs.SetNum(NumOutputs);
return BakeHoudiniOutputsToActors(
HoudiniAssetComponent,
Outputs,
BakedOutputs,
HoudiniAssetName,
HoudiniAssetComponent->GetComponentTransform(),
HoudiniAssetComponent->BakeFolder,
HoudiniAssetComponent->TemporaryCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutNewActors,
OutPackagesToSave,
OutBakeStats,
InOutputTypesToBake,
InInstancerComponentTypesToBake,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors(
const UHoudiniAssetComponent* HoudiniAssetComponent,
const TArray<UHoudiniOutput*>& InOutputs,
TArray<FHoudiniBakedOutput>& InBakedOutputs,
const FString& InHoudiniAssetName,
const FTransform& InParentTransform,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutNewActors,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats,
TArray<EHoudiniOutputType> const* InOutputTypesToBake,
TArray<EHoudiniInstancerComponentType> const* InInstancerComponentTypesToBake,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
const int32 NumOutputs = InOutputs.Num();
const FString MsgTemplate = TEXT("Baking output: {0}/{1}.");
FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs });
FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg));
TArray<FHoudiniEngineBakedActor> BakedActors;
// Ensure that InBakedOutputs is the same size as InOutputs
if (InBakedOutputs.Num() != NumOutputs)
InBakedOutputs.SetNum(NumOutputs);
// First bake everything except instancers, then bake instancers. Since instancers might use meshes in
// from the other outputs.
bool bHasAnyInstancers = false;
int32 NumProcessedOutputs = 0;
TMap<UMaterialInterface *, UMaterialInterface *> AlreadyBakedMaterialsMap;
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx)
{
UHoudiniOutput* Output = InOutputs[OutputIdx];
if (!Output || Output->IsPendingKill())
{
NumProcessedOutputs++;
continue;
}
Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs });
FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg));
const EHoudiniOutputType OutputType = Output->GetType();
// Check if we should skip this output type
if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE)
{
NumProcessedOutputs++;
continue;
}
switch (OutputType)
{
case EHoudiniOutputType::Mesh:
{
FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors(
HoudiniAssetComponent,
OutputIdx,
InOutputs,
InBakedOutputs,
InHoudiniAssetName,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
BakedActors,
OutPackagesToSave,
AlreadyBakedMaterialsMap,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
break;
case EHoudiniOutputType::Instancer:
{
if (!bHasAnyInstancers)
bHasAnyInstancers = true;
NumProcessedOutputs--;
}
break;
case EHoudiniOutputType::Landscape:
{
const bool bResult = BakeLandscape(
HoudiniAssetComponent,
OutputIdx,
InOutputs,
InBakedOutputs,
bInReplaceActors,
bInReplaceAssets,
InBakeFolder.Path,
InHoudiniAssetName,
OutBakeStats);
}
break;
case EHoudiniOutputType::Skeletal:
break;
case EHoudiniOutputType::Curve:
{
FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors(
HoudiniAssetComponent,
OutputIdx,
InOutputs,
InBakedOutputs,
InHoudiniAssetName,
InBakeFolder,
bInReplaceActors,
bInReplaceAssets,
BakedActors,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
break;
case EHoudiniOutputType::Invalid:
break;
}
NumProcessedOutputs++;
}
if (bHasAnyInstancers)
{
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx)
{
UHoudiniOutput* Output = InOutputs[OutputIdx];
if (!Output || Output->IsPendingKill())
{
NumProcessedOutputs++;
continue;
}
Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs });
FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg));
if (Output->GetType() == EHoudiniOutputType::Instancer)
{
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors(
HoudiniAssetComponent,
OutputIdx,
InOutputs,
InBakedOutputs,
InParentTransform,
InHoudiniAssetName,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
BakedActors,
OutPackagesToSave,
AlreadyBakedMaterialsMap,
InInstancerComponentTypesToBake,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
NumProcessedOutputs++;
}
}
// Only do the post bake post-process once per Actor
TSet<AActor*> UniqueActors;
for (FHoudiniEngineBakedActor& BakedActor : BakedActors)
{
if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor)
{
BakedActor.bPostBakeProcessPostponed = false;
AActor* Actor = BakedActor.Actor;
bool bIsAlreadyInSet = false;
UniqueActors.Add(Actor, &bIsAlreadyInSet);
if (!bIsAlreadyInSet)
{
Actor->InvalidateLightingCache();
Actor->PostEditMove(true);
Actor->MarkPackageDirty();
}
}
}
OutNewActors.Append(BakedActors);
return true;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
const FString& InHoudiniAssetName,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
UHoudiniOutput* Output = InAllOutputs[InOutputIndex];
if (!Output || Output->IsPendingKill())
return false;
if (Output->GetType() != EHoudiniOutputType::Instancer)
return false;
if (!IsValid(InOutputObject.OutputComponent))
return false;
UStaticMeshComponent* SMC = Cast<UStaticMeshComponent>(InOutputObject.OutputComponent);
if (!IsValid(SMC))
{
HOUDINI_LOG_WARNING(
TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName()));
return false;
}
UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh();
if (!IsValid(InstancedStaticMesh))
{
// No mesh, skip this instancer
return false;
}
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets
? EPackageReplaceMode::ReplaceExistingAssets
: EPackageReplaceMode::CreateNewAssets;
UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld;
// Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params
// for baking from it.
// If not temporary set the ObjectName from the its package. (Also use this as a fallback default)
FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh);
UStaticMesh* PreviousStaticMesh = Cast<UStaticMesh>(InBakedOutputObject.GetBakedObjectIfValid());
UStaticMesh* BakedStaticMesh = nullptr;
int32 MeshOutputIndex = INDEX_NONE;
FHoudiniOutputObjectIdentifier MeshIdentifier;
FHoudiniAttributeResolver MeshResolver;
FHoudiniPackageParams MeshPackageParams;
const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier);
if (bFoundMeshOutput)
{
// Found the mesh in the mesh outputs, is temporary
const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier);
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName,
InHoudiniAssetName, MeshPackageParams, MeshResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// Update with resolved object name
ObjectName = MeshPackageParams.ObjectName;
// This will bake/duplicate the mesh if temporary, or return the input one if it is not
BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
}
else
{
BakedStaticMesh = InstancedStaticMesh;
}
// Update the baked object
InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString();
// const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier));
// Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone
// disk package for the instancer, but certain attributes (such as level path) use tokens populated from the
// package params.
FHoudiniPackageParams InstancerPackageParams;
FHoudiniAttributeResolver InstancerResolver;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName,
InHoudiniAssetName, InstancerPackageParams, InstancerResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_path attribute
FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath);
return false;
}
// If we have created a new level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if (!DesiredLevel)
return false;
// Get foliage actor for the level
const bool bCreateIfNone = true;
AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone);
if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill())
{
HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName()));
return false;
}
// Get the previous bake data for this instancer
InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString();
// Foliage type is replaced in replacement mode if:
// the previous baked object is this foliage type
// and we haven't bake this foliage type during this bake (BakeResults)
// NOTE: foliage type is only recorded as the previous bake object if we created the foliage type
// TODO: replacement mode should probably only affect the instances themselves and not the foliage type
// since the foliage type is already linked to whatever mesh we are using (which will be replaced
// incremented already). To track instances it looks like we would have to use the locations of the
// baked instances (likely cannot use the indices, since the user might modify/add/remove instances
// after the bake).
// See if we already have a FoliageType for that static mesh
UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh);
if (!FoliageType || FoliageType->IsPendingKill())
{
// We need to create a new FoliageType for this Static Mesh
// TODO: Add foliage default settings
InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType);
// Update the previous bake results with the foliage type we created
InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString();
}
else
{
const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString();
if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath &&
!OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; }))
{
InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1);
// Update the previous bake results with the foliage type
InBakedOutputObject.BakedComponent = FoliageTypePath;
}
else
{
// If we didn't create the foliage type, don't set the baked component
InBakedOutputObject.BakedComponent.Empty();
}
}
// Record the foliage bake in the current results
FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor());
NewResult.OutputIndex = InOutputIndex;
NewResult.OutputObjectIdentifier = InOutputObjectIdentifier;
NewResult.SourceObject = InstancedStaticMesh;
NewResult.BakedObject = BakedStaticMesh;
NewResult.BakedComponent = FoliageType;
// Get the FoliageMeshInfo for this Foliage type so we can add the instance to it
FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType);
if (!FoliageInfo)
return false;
int32 CurrentInstanceCount = 0;
if (SMC->IsA<UInstancedStaticMeshComponent>())
{
UInstancedStaticMeshComponent* ISMC = Cast<UInstancedStaticMeshComponent>(SMC);
const int32 NumInstances = ISMC->GetInstanceCount();
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
FTransform InstanceTransform;
const bool bWorldSpace = true;
if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace))
{
FFoliageInstance FoliageInstance;
FoliageInstance.Location = InstanceTransform.GetLocation();
FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator();
FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D();
FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance);
CurrentInstanceCount++;
}
}
}
else
{
const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld();
FFoliageInstance FoliageInstance;
FoliageInstance.Location = ComponentToWorldTransform.GetLocation();
FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator();
FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D();
FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance);
CurrentInstanceCount++;
}
// TODO: This was due to a bug in UE4.22-20, check if still needed!
if (FoliageInfo->GetComponent())
FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true);
// Notify the user that we succesfully bake the instances to foliage
FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage");
FHoudiniEngineUtils::CreateSlateNotification(Notification);
InstancedFoliageActor->RegisterAllComponents();
// Update / repopulate the foliage editor mode's mesh list
if (CurrentInstanceCount > 0)
FHoudiniEngineUtils::RepopulateFoliageTypeListInUI();
return true;
}
bool
FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n)
{
UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n);
if (!Output || Output->IsPendingKill())
continue;
if (Output->GetType() != EHoudiniOutputType::Instancer)
continue;
if (Output->GetInstancedOutputs().Num() > 0)
return true;
/*
// TODO: Is this needed? check we have components to bake?
for (auto& OutputObjectPair : Output->GetOutputObjects())
{
if (OutputObjectPair.Value.OutputCompoent!= nullpt)
return true;
}
*/
}
return false;
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
AActor * OwnerActor = HoudiniAssetComponent->GetOwner();
if (!OwnerActor || OwnerActor->IsPendingKill())
return false;
TArray<UPackage*> PackagesToSave;
TArray<FHoudiniEngineBakedActor> BakedResults;
FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform();
const FString HoudiniAssetName = OwnerActor->GetName();
// Build an array of the outputs so that we can search for meshes/previous baked meshes
TArray<UHoudiniOutput*> Outputs;
HoudiniAssetComponent->GetOutputs(Outputs);
const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs();
// Get the previous bake outputs and match the output array size
TArray<FHoudiniBakedOutput>& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs();
if (BakedOutputs.Num() != NumOutputs)
BakedOutputs.SetNum(NumOutputs);
bool bSuccess = true;
// Map storing original and baked Static Meshes
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++)
{
UHoudiniOutput* Output = Outputs[OutputIdx];
if (!Output || Output->IsPendingKill())
continue;
if (Output->GetType() != EHoudiniOutputType::Instancer)
continue;
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = Output->GetOutputObjects();
const TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject>& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects;
TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject> NewBakedOutputObjects;
for (auto & Pair : OutputObjects)
{
const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key;
FHoudiniOutputObject& OutputObject = Pair.Value;
FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier);
if (OldBakedOutputObjects.Contains(Identifier))
BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier);
const bool bInReplaceActors = false;
bSuccess &= BakeInstancerOutputToFoliage(
HoudiniAssetComponent,
OutputIdx,
Outputs,
Identifier,
OutputObject,
BakedOutputObject,
HoudiniAssetName,
HoudiniAssetComponent->BakeFolder,
HoudiniAssetComponent->TemporaryCookFolder,
bInReplaceActors,
bInReplaceAssets,
BakedResults,
PackagesToSave,
InOutAlreadyBakedMaterialsMap);
}
// Update the cached baked output data
BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects;
}
if (PackagesToSave.Num() > 0)
{
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
}
// Broadcast that the bake is complete
HoudiniAssetComponent->HandleOnPostBake(bSuccess);
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
TArray<FHoudiniBakedOutput>& InBakedOutputs,
const FTransform& InTransform,
const FString& InHoudiniAssetName,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap,
TArray<EHoudiniInstancerComponentType> const* InInstancerComponentTypesToBake,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
if (!InAllOutputs.IsValidIndex(InOutputIndex))
return false;
UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex];
if (!InOutput || InOutput->IsPendingKill())
return false;
// Ensure we have the same number of baked outputs and asset outputs
if (InBakedOutputs.Num() != InAllOutputs.Num())
InBakedOutputs.SetNum(InAllOutputs.Num());
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = InOutput->GetOutputObjects();
const TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject>& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects;
TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject> NewBakedOutputObjects;
// Iterate on the output objects, baking their object/component as we go
for (auto& Pair : OutputObjects)
{
const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key;
FHoudiniOutputObject& CurrentOutputObject = Pair.Value;
FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier);
if (OldBakedOutputObjects.Contains(Identifier))
BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier);
if (CurrentOutputObject.bProxyIsCurrent)
{
// TODO: we need to refine the SM first!
// ??
}
if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill())
continue;
if (CurrentOutputObject.OutputComponent->IsA<UFoliageInstancedStaticMeshComponent>())
{
// Bake foliage as foliage
if (!InInstancerComponentTypesToBake ||
InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent))
{
BakeInstancerOutputToFoliage(
HoudiniAssetComponent,
InOutputIndex,
InAllOutputs,
// InBakedOutputs,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InHoudiniAssetName,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap);
}
else if (!InInstancerComponentTypesToBake ||
InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent))
{
BakeInstancerOutputToActors_ISMC(
HoudiniAssetComponent,
InOutputIndex,
InAllOutputs,
// InBakedOutputs,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InTransform,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
}
else if (CurrentOutputObject.OutputComponent->IsA<UInstancedStaticMeshComponent>()
&& (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent)))
{
BakeInstancerOutputToActors_ISMC(
HoudiniAssetComponent,
InOutputIndex,
InAllOutputs,
// InBakedOutputs,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InTransform,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
else if (CurrentOutputObject.OutputComponent->IsA<UHoudiniInstancedActorComponent>()
&& (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent)))
{
BakeInstancerOutputToActors_IAC(
HoudiniAssetComponent,
InOutputIndex,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InBakeFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave);
}
else if (CurrentOutputObject.OutputComponent->IsA<UHoudiniMeshSplitInstancerComponent>()
&& (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent)))
{
BakeInstancerOutputToActors_MSIC(
HoudiniAssetComponent,
InOutputIndex,
InAllOutputs,
// InBakedOutputs,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InTransform,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
else if (CurrentOutputObject.OutputComponent->IsA<UStaticMeshComponent>()
&& (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent)))
{
BakeInstancerOutputToActors_SMC(
HoudiniAssetComponent,
InOutputIndex,
InAllOutputs,
// InBakedOutputs,
Pair.Key,
CurrentOutputObject,
BakedOutputObject,
InBakeFolder,
InTempCookFolder,
bInReplaceActors,
bInReplaceAssets,
OutActors,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap,
InFallbackActor,
InFallbackWorldOutlinerFolder);
}
else
{
// Unsupported component!
}
}
// Update the cached baked output data
InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects;
return true;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
// const TArray<FHoudiniBakedOutput>& InAllBakedOutputs,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
const FTransform& InTransform,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
UInstancedStaticMeshComponent * InISMC = Cast<UInstancedStaticMeshComponent>(InOutputObject.OutputComponent);
if (!InISMC || InISMC->IsPendingKill())
return false;
AActor * OwnerActor = InISMC->GetOwner();
if (!OwnerActor || OwnerActor->IsPendingKill())
return false;
UStaticMesh * StaticMesh = InISMC->GetStaticMesh();
if (!StaticMesh || StaticMesh->IsPendingKill())
return false;
// Certain SMC materials may need to be duplicated if we didn't generate the mesh object.
TArray<UMaterialInterface *> DuplicatedISMCOverrideMaterials;
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld;
// Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params
// for baking from it.
// If not temporary set the ObjectName from the its package. (Also use this as a fallback default)
FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh);
UStaticMesh* PreviousStaticMesh = Cast<UStaticMesh>(InBakedOutputObject.GetBakedObjectIfValid());
UStaticMesh* BakedStaticMesh = nullptr;
int32 MeshOutputIndex = INDEX_NONE;
FHoudiniOutputObjectIdentifier MeshIdentifier;
FHoudiniAttributeResolver MeshResolver;
FHoudiniPackageParams MeshPackageParams;
// Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone
// disk package for the instancer, but certain attributes (such as level path) use tokens populated from the
// package params.
FHoudiniPackageParams InstancerPackageParams;
FHoudiniAttributeResolver InstancerResolver;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName,
OwnerActor->GetName(), InstancerPackageParams, InstancerResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier);
if (bFoundMeshOutput)
{
// Found the mesh in the mesh outputs, is temporary
const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier);
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName,
OwnerActor->GetName(), MeshPackageParams, MeshResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// Update with resolved object name
ObjectName = MeshPackageParams.ObjectName;
// This will bake/duplicate the mesh if temporary, or return the input one if it is not
BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
}
else
{
BakedStaticMesh = StaticMesh;
// We still need to duplicate materials, if they are temporary.
TArray<UMaterialInterface *> Materials = InISMC->GetMaterials();
for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx)
{
UMaterialInterface* MaterialInterface = Materials[MaterialIdx];
if (!MaterialInterface || MaterialInterface->IsPendingKill())
continue;
// Only duplicate the material if it is temporary
if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path))
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
DuplicatedISMCOverrideMaterials.Add(DuplicatedMaterial);
}
}
}
// Update the baked object
InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString();
// Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM)
const FString BaseName = OwnerActor->GetName();
const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier;
const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder));
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_apth attribute
FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a new level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if(!DesiredLevel)
return false;
// Try to find the unreal_bake_actor, if specified, or fallback to the default named actor
FName BakeActorName;
AActor* FoundActor = nullptr;
bool bHasBakeActorName = false;
if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName))
return false;
/*
// TODO: Get the bake name!
// Bake override, the output name
// The bake name override has priority
FString InstancerName = InOutputObject.BakeName;
if (InstancerName.IsEmpty())
{
// .. then use the output name
InstancerName = Resolver.ResolveOutputName();
}
*/
// Should we create one actor with an ISMC or multiple actors with one SMC?
bool bSpawnMultipleSMC = false;
if (bSpawnMultipleSMC)
{
// TODO: Double check, Has a crash here!
// Get the StaticMesh ActorFactory
UActorFactory* SMFactory = nullptr;
if (!FoundActor)
{
SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr;
if (!SMFactory)
return false;
}
// Split the instances to multiple StaticMeshActors
for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++)
{
FTransform InstanceTransform;
InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true);
if (!FoundActor)
{
FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform);
if (!FoundActor || FoundActor->IsPendingKill())
continue;
}
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor);
RenameAndRelabelActor(FoundActor, NewNameStr, false);
// The folder is named after the original actor and contains all generated actors
SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath);
AStaticMeshActor* SMActor = Cast<AStaticMeshActor>(FoundActor);
if (!SMActor || SMActor->IsPendingKill())
continue;
// Copy properties from the existing component
CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC);
FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor(
FoundActor,
BakeActorName,
WorldOutlinerFolderPath,
InOutputIndex,
InOutputObjectIdentifier,
BakedStaticMesh,
StaticMesh,
SMActor->GetStaticMeshComponent(),
bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(),
MeshPackageParams));
OutputEntry.bInstancerOutput = true;
OutputEntry.InstancerPackageParams = InstancerPackageParams;
}
}
else
{
bool bSpawnedActor = false;
if (!FoundActor)
{
// Only create one actor
FActorSpawnParameters SpawnInfo;
SpawnInfo.OverrideLevel = DesiredLevel;
SpawnInfo.ObjectFlags = RF_Transactional;
SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString()));
SpawnInfo.bDeferConstruction = true;
// Spawn the new Actor
FoundActor = DesiredLevel->OwningWorld->SpawnActor<AActor>(SpawnInfo);
if (!FoundActor || FoundActor->IsPendingKill())
return false;
bSpawnedActor = true;
FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName());
FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame);
}
else
{
// If there is a previously baked component, and we are in replace mode, remove it
if (bInReplaceAssets)
{
USceneComponent* InPrevComponent = Cast<USceneComponent>(InBakedOutputObject.GetBakedComponentIfValid());
if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor)
RemovePreviouslyBakedComponent(InPrevComponent);
}
const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor);
RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false);
}
// The folder is named after the original actor and contains all generated actors
SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath);
// Get/create the actor's root component
const bool bCreateIfMissing = true;
USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing);
if (bSpawnedActor && IsValid(RootComponent))
RootComponent->SetWorldTransform(InTransform);
// Duplicate the instancer component, create a Hierarchical ISMC if needed
UInstancedStaticMeshComponent* NewISMC = nullptr;
UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast<UHierarchicalInstancedStaticMeshComponent>(InISMC);
if (InHISMC)
{
// Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can
// from the foliage component
if (InHISMC->IsA<UFoliageInstancedStaticMeshComponent>())
{
NewISMC = NewObject<UHierarchicalInstancedStaticMeshComponent>(
FoundActor,
FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName())));
CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC);
}
else
{
NewISMC = DuplicateObject<UHierarchicalInstancedStaticMeshComponent>(
InHISMC,
FoundActor,
FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName())));
}
}
else
{
NewISMC = DuplicateObject<UInstancedStaticMeshComponent>(
InISMC,
FoundActor,
FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName())));
}
if (!NewISMC)
{
//DesiredLevel->OwningWorld->
return false;
}
InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString();
NewISMC->RegisterComponent();
// NewISMC->SetupAttachment(nullptr);
NewISMC->SetStaticMesh(BakedStaticMesh);
FoundActor->AddInstanceComponent(NewISMC);
if (DuplicatedISMCOverrideMaterials.Num() > 0)
{
UMaterialInterface * InstancerMaterial = DuplicatedISMCOverrideMaterials[0];
if (InstancerMaterial)
{
NewISMC->OverrideMaterials.Empty();
int32 MeshMaterialCount = BakedStaticMesh->StaticMaterials.Num();
for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx)
NewISMC->SetMaterial(Idx, InstancerMaterial);
}
}
// NewActor->SetRootComponent(NewISMC);
if (IsValid(RootComponent))
NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
NewISMC->SetWorldTransform(InISMC->GetComponentTransform());
// TODO: do we need to copy properties here, we duplicated the component
// // Copy properties from the existing component
// CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC);
if (bSpawnedActor)
FoundActor->FinishSpawning(InTransform);
InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString();
FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor(
FoundActor,
BakeActorName,
WorldOutlinerFolderPath,
InOutputIndex,
InOutputObjectIdentifier,
BakedStaticMesh,
StaticMesh,
NewISMC,
bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(),
MeshPackageParams));
OutputEntry.bInstancerOutput = true;
OutputEntry.InstancerPackageParams = InstancerPackageParams;
// Postpone post-bake calls to do them once per actor
OutActors.Last().bPostBakeProcessPostponed = true;
}
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = false;
const bool bInDestroyBakedInstancedActors = true;
const bool bInDestroyBakedInstancedComponents = true;
DestroyPreviousBakeOutput(
InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
// const TArray<FHoudiniBakedOutput>& InAllBakedOutputs,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
UStaticMeshComponent* InSMC = Cast<UStaticMeshComponent>(InOutputObject.OutputComponent);
if (!InSMC || InSMC->IsPendingKill())
return false;
AActor* OwnerActor = InSMC->GetOwner();
if (!OwnerActor || OwnerActor->IsPendingKill())
return false;
UStaticMesh* StaticMesh = InSMC->GetStaticMesh();
if (!StaticMesh || StaticMesh->IsPendingKill())
return false;
UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld;
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Certain SMC materials may need to be duplicated if we didn't generate the mesh object.
TArray<UMaterialInterface *> DuplicatedSMCOverrideMaterials;
// Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params
// for baking from it.
// If not temporary set the ObjectName from the its package. (Also use this as a fallback default)
FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh);
UStaticMesh* PreviousStaticMesh = Cast<UStaticMesh>(InBakedOutputObject.GetBakedObjectIfValid());
UStaticMesh* BakedStaticMesh = nullptr;
int32 MeshOutputIndex = INDEX_NONE;
FHoudiniOutputObjectIdentifier MeshIdentifier;
FHoudiniAttributeResolver MeshResolver;
FHoudiniPackageParams MeshPackageParams;
// Package params for the instancer
// See if the instanced static mesh is still a temporary Houdini created Static Mesh
// If it is, we need to bake the StaticMesh first
FHoudiniPackageParams InstancerPackageParams;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniAttributeResolver InstancerResolver;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName,
OwnerActor->GetName(), InstancerPackageParams, InstancerResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier);
if (bFoundMeshOutput)
{
// Found the mesh in the mesh outputs, is temporary
const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier);
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName,
OwnerActor->GetName(), MeshPackageParams, MeshResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// Update with resolved object name
ObjectName = MeshPackageParams.ObjectName;
// This will bake/duplicate the mesh if temporary, or return the input one if it is not
BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
}
else
{
BakedStaticMesh = StaticMesh;
// We still need to duplicate materials, if they are temporary.
TArray<UMaterialInterface *> Materials = InSMC->GetMaterials();
for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx)
{
UMaterialInterface* MaterialInterface = Materials[MaterialIdx];
if (!MaterialInterface || MaterialInterface->IsPendingKill())
continue;
// Only duplicate the material if it is temporary
if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path))
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
DuplicatedSMCOverrideMaterials.Add(DuplicatedMaterial);
}
}
}
// Update the previous baked object
InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString();
// BaseName holds the Actor / HDA name
// Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM)
const FString BaseName = OwnerActor->GetName();
const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier;
const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder));
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_apth attribute
FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if (!DesiredLevel)
return false;
// Try to find the unreal_bake_actor, if specified
FName BakeActorName;
AActor* FoundActor = nullptr;
bool bHasBakeActorName = false;
if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName))
return false;
UStaticMeshComponent* StaticMeshComponent = nullptr;
// Create an actor if we didn't find one
if (!FoundActor)
{
// Get the StaticMesh ActorFactory
UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr;
if (!SMFactory)
return false;
FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform());
if (!FoundActor || FoundActor->IsPendingKill())
return false;
AStaticMeshActor* SMActor = Cast<AStaticMeshActor>(FoundActor);
if (!SMActor || SMActor->IsPendingKill())
return false;
StaticMeshComponent = SMActor->GetStaticMeshComponent();
}
else
{
USceneComponent* RootComponent = GetActorRootComponent(FoundActor);
if (!IsValid(RootComponent))
return false;
if (bInReplaceAssets)
{
// Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it
UStaticMeshComponent* PrevSMC = Cast<UStaticMeshComponent>(InBakedOutputObject.GetBakedComponentIfValid());
if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor))
{
StaticMeshComponent = PrevSMC;
}
}
if (!IsValid(StaticMeshComponent))
{
// Create a new static mesh component
StaticMeshComponent = NewObject<UStaticMeshComponent>(FoundActor, NAME_None, RF_Transactional);
FoundActor->AddInstanceComponent(StaticMeshComponent);
StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
StaticMeshComponent->RegisterComponent();
}
}
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor);
RenameAndRelabelActor(FoundActor, NewNameStr, false);
// The folder is named after the original actor and contains all generated actors
SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath);
// Update the previous baked component
InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString();
if (!IsValid(StaticMeshComponent))
return false;
// Copy properties from the existing component
const bool bCopyWorldTransform = true;
CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform);
StaticMeshComponent->SetStaticMesh(BakedStaticMesh);
if (DuplicatedSMCOverrideMaterials.Num() > 0)
{
UMaterialInterface * InstancerMaterial = DuplicatedSMCOverrideMaterials[0];
if (InstancerMaterial)
{
StaticMeshComponent->OverrideMaterials.Empty();
int32 MeshMaterialCount = BakedStaticMesh->StaticMaterials.Num();
for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx)
StaticMeshComponent->SetMaterial(Idx, InstancerMaterial);
}
}
InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString();
FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor(
FoundActor,
BakeActorName,
WorldOutlinerFolderPath,
InOutputIndex,
InOutputObjectIdentifier,
BakedStaticMesh,
StaticMesh,
StaticMeshComponent,
MeshPackageParams.BakeFolder,
MeshPackageParams));
OutputEntry.bInstancerOutput = true;
OutputEntry.InstancerPackageParams = InstancerPackageParams;
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = false;
const bool bInDestroyBakedInstancedActors = true;
const bool bInDestroyBakedInstancedComponents = true;
DestroyPreviousBakeOutput(
InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
const FDirectoryPath& InBakeFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave)
{
UHoudiniInstancedActorComponent* InIAC = Cast<UHoudiniInstancedActorComponent>(InOutputObject.OutputComponent);
if (!InIAC || InIAC->IsPendingKill())
return false;
AActor * OwnerActor = InIAC->GetOwner();
if (!OwnerActor || OwnerActor->IsPendingKill())
return false;
// BaseName holds the Actor / HDA name
const FName BaseName = FName(OwnerActor->GetName());
// Get the object instanced by this IAC
UObject* InstancedObject = InIAC->GetInstancedObject();
if (!InstancedObject || InstancedObject->IsPendingKill())
return false;
FHoudiniPackageParams PackageParams;
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniAttributeResolver Resolver;
UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(),
OwnerActor->GetName(), PackageParams, Resolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_apth attribute
FString LevelPackagePath = Resolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if (!DesiredLevel)
return false;
// If we are baking in actor replacement mode, remove any previously baked instanced actors for this output
if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0)
{
UWorld* LevelWorld = DesiredLevel->GetWorld();
if (IsValid(LevelWorld))
{
for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors)
{
const FSoftObjectPath ActorPath(ActorPathStr);
if (!ActorPath.IsValid())
continue;
AActor* Actor = Cast<AActor>(ActorPath.TryLoad());
// Destroy Actor if it is valid and part of DesiredLevel
if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel)
{
#if WITH_EDITOR
LevelWorld->EditorDestroyActor(Actor, true);
#else
LevelWorld->DestroyActor(Actor);
#endif
}
}
}
}
// Empty and reserve enough space for new instanced actors
InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num());
// Iterates on all the instances of the IAC
for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors())
{
if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill())
continue;
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString());
FTransform CurrentTransform = CurrentInstancedActor->GetTransform();
AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC);
if (!NewActor || NewActor->IsPendingKill())
continue;
const auto CopyOptions = (EditorUtilities::ECopyOptions::Type)
(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties |
EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances);
EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor);
const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName);
FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr);
SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath);
NewActor->SetActorTransform(CurrentTransform);
InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString());
FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor(
NewActor,
BaseName,
WorldOutlinerFolderPath,
InOutputIndex,
InOutputObjectIdentifier,
nullptr,
InstancedObject,
nullptr,
PackageParams.BakeFolder,
PackageParams));
OutputEntry.bInstancerOutput = true;
OutputEntry.InstancerPackageParams = PackageParams;
}
// TODO:
// Move Actors to DesiredLevel if needed??
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = true;
const bool bInDestroyBakedInstancedActors = false;
const bool bInDestroyBakedInstancedComponents = true;
DestroyPreviousBakeOutput(
InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
// const TArray<FHoudiniBakedOutput>& InAllBakedOutputs,
const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier,
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
const FTransform& InTransform,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
UHoudiniMeshSplitInstancerComponent * InMSIC = Cast<UHoudiniMeshSplitInstancerComponent>(InOutputObject.OutputComponent);
if (!InMSIC || InMSIC->IsPendingKill())
return false;
AActor * OwnerActor = InMSIC->GetOwner();
if (!OwnerActor || OwnerActor->IsPendingKill())
return false;
UStaticMesh * StaticMesh = InMSIC->GetStaticMesh();
if (!StaticMesh || StaticMesh->IsPendingKill())
return false;
// Certain SMC materials may need to be duplicated if we didn't generate the mesh object.
TArray<UMaterialInterface *> DuplicatedMSICOverrideMaterials;
UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld;
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params
// for baking from it.
// If not temporary set the ObjectName from the its package. (Also use this as a fallback default)
FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh);
UStaticMesh* PreviousStaticMesh = Cast<UStaticMesh>(InBakedOutputObject.GetBakedObjectIfValid());
UStaticMesh* BakedStaticMesh = nullptr;
int32 MeshOutputIndex = INDEX_NONE;
FHoudiniOutputObjectIdentifier MeshIdentifier;
FHoudiniAttributeResolver MeshResolver;
FHoudiniPackageParams MeshPackageParams;
// See if the instanced static mesh is still a temporary Houdini created Static Mesh
// If it is, we need to bake the StaticMesh first
FHoudiniPackageParams InstancerPackageParams;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniAttributeResolver InstancerResolver;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName,
OwnerActor->GetName(), InstancerPackageParams, InstancerResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier);
if (bFoundMeshOutput)
{
// Found the mesh in the mesh outputs, is temporary
const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier);
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName,
OwnerActor->GetName(), MeshPackageParams, MeshResolver,
InBakeFolder.Path, AssetPackageReplaceMode);
// Update with resolved object name
ObjectName = MeshPackageParams.ObjectName;
// This will bake/duplicate the mesh if temporary, or return the input one if it is not
BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
}
else
{
BakedStaticMesh = StaticMesh;
// We still need to duplicate materials, if they are temporary.
TArray<UMaterialInterface *> Materials = InMSIC->GetOverrideMaterials();
for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx)
{
UMaterialInterface* MaterialInterface = Materials[MaterialIdx];
if (!MaterialInterface || MaterialInterface->IsPendingKill())
continue;
// Only duplicate the material if it is temporary
if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path))
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
DuplicatedMSICOverrideMaterials.Add(DuplicatedMaterial);
}
}
}
// Update the baked output
InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString();
// Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM)
const FString BaseName = OwnerActor->GetName();
const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier;
const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder));
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_path attribute
FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if (!DesiredLevel)
return false;
// Try to find the unreal_bake_actor, if specified
FName BakeActorName;
AActor* FoundActor = nullptr;
bool bHasBakeActorName = false;
bool bSpawnedActor = false;
if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName))
return false;
if (!FoundActor)
{
// This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC
FActorSpawnParameters SpawnInfo;
SpawnInfo.OverrideLevel = DesiredLevel;
SpawnInfo.ObjectFlags = RF_Transactional;
SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString()));
SpawnInfo.bDeferConstruction = true;
// Spawn the new Actor
FoundActor = DesiredLevel->OwningWorld->SpawnActor<AActor>(SpawnInfo);
if (!FoundActor || FoundActor->IsPendingKill())
return false;
bSpawnedActor = true;
FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName());
FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame);
}
else
{
// If we are baking in replacement mode, remove the previous components (if they belong to FoundActor)
for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents)
{
const FSoftObjectPath PrevComponentPath(PrevComponentPathStr);
if (!PrevComponentPath.IsValid())
continue;
UActorComponent* PrevComponent = Cast<UActorComponent>(PrevComponentPath.TryLoad());
if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor)
continue;
RemovePreviouslyBakedComponent(PrevComponent);
}
const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor);
RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false);
}
// The folder is named after the original actor and contains all generated actors
SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath);
// Get/create the actor's root component
const bool bCreateIfMissing = true;
USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing);
if (bSpawnedActor && IsValid(RootComponent))
RootComponent->SetWorldTransform(InTransform);
// Empty and reserve enough space in the baked components array for the new components
InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num());
// Now add s SMC component for each of the SMC's instance
for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances())
{
if (!CurrentSMC || CurrentSMC->IsPendingKill())
continue;
UStaticMeshComponent* NewSMC = DuplicateObject<UStaticMeshComponent>(
CurrentSMC,
FoundActor,
FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName())));
if (!NewSMC || NewSMC->IsPendingKill())
continue;
InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString());
NewSMC->RegisterComponent();
// NewSMC->SetupAttachment(nullptr);
NewSMC->SetStaticMesh(BakedStaticMesh);
FoundActor->AddInstanceComponent(NewSMC);
NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform());
if (DuplicatedMSICOverrideMaterials.Num() > 0)
{
UMaterialInterface * InstancerMaterial = DuplicatedMSICOverrideMaterials[0];
if (InstancerMaterial)
{
NewSMC->OverrideMaterials.Empty();
int32 MeshMaterialCount = BakedStaticMesh->StaticMaterials.Num();
for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx)
NewSMC->SetMaterial(Idx, InstancerMaterial);
}
}
if (IsValid(RootComponent))
NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
// TODO: Do we need to copy properties here, we duplicated the component
// // Copy properties from the existing component
// CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC);
}
if (bSpawnedActor)
FoundActor->FinishSpawning(InTransform);
InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString();
FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor(
FoundActor,
BakeActorName,
WorldOutlinerFolderPath,
InOutputIndex,
InOutputObjectIdentifier,
BakedStaticMesh,
StaticMesh,
nullptr,
MeshPackageParams.BakeFolder,
MeshPackageParams));
OutputEntry.bInstancerOutput = true;
OutputEntry.InstancerPackageParams = InstancerPackageParams;
// Postpone these calls to do them once per actor
OutActors.Last().bPostBakeProcessPostponed = true;
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = true;
const bool bInDestroyBakedInstancedActors = true;
const bool bInDestroyBakedInstancedComponents = false;
DestroyPreviousBakeOutput(
InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
return true;
}
bool
FHoudiniEngineBakeUtils::FindHGPO(
const FHoudiniOutputObjectIdentifier& InIdentifier,
const TArray<FHoudiniGeoPartObject>& InHGPOs,
FHoudiniGeoPartObject const*& OutHGPO)
{
// Find the HGPO that matches this output identifier
const FHoudiniGeoPartObject* FoundHGPO = nullptr;
for (auto & NextHGPO : InHGPOs)
{
// We use Matches() here as it handles the case where the HDA was loaded,
// which likely means that the the obj/geo/part ids dont match the output identifier
if(InIdentifier.Matches(NextHGPO))
{
FoundHGPO = &NextHGPO;
break;
}
}
OutHGPO = FoundHGPO;
return !OutHGPO;
}
void
FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName(
const UObject* InObject,
const FHoudiniOutputObject& InMeshOutputObject,
FString& OutBakeName)
{
// The bake name override has priority
OutBakeName = InMeshOutputObject.BakeName;
if (OutBakeName.IsEmpty())
{
FHoudiniAttributeResolver Resolver;
Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes);
Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens);
const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject);
// The default output name (if not set via attributes) is {object_name}, which look for an object_name
// key-value token
if (!Resolver.GetCachedTokens().Contains(TEXT("object_name")))
Resolver.SetToken(TEXT("object_name"), DefaultObjectName);
OutBakeName = Resolver.ResolveOutputName();
// const TArray<FHoudiniGeoPartObject>& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects();
// const FHoudiniGeoPartObject* FoundHGPO = nullptr;
// FindHGPO(MeshIdentifier, HGPOs, FoundHGPO);
// // ... finally the part name
// if (FoundHGPO && FoundHGPO->bHasCustomPartName)
// OutBakeName = FoundHGPO->PartName;
if (OutBakeName.IsEmpty())
OutBakeName = DefaultObjectName;
}
}
bool
FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName(
const UObject* InObject,
EHoudiniOutputType InOutputType,
const TArray<UHoudiniOutput*>& InAllOutputs,
FString& OutBakeName)
{
if (!IsValid(InObject))
return false;
OutBakeName.Empty();
int32 MeshOutputIdx = INDEX_NONE;
FHoudiniOutputObjectIdentifier MeshIdentifier;
if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier))
{
// Found the mesh, get its name
const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier);
GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName);
return true;
}
return false;
}
bool
FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
TArray<FHoudiniBakedOutput>& InBakedOutputs,
const FString& InHoudiniAssetName,
const FDirectoryPath& InBakeFolder,
const FDirectoryPath& InTempCookFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
// Check that index is not negative
if (InOutputIndex < 0)
return false;
if (!InAllOutputs.IsValidIndex(InOutputIndex))
return false;
UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex];
if (!InOutput || InOutput->IsPendingKill())
return false;
UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr;
if (!Factory)
return false;
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = InOutput->GetOutputObjects();
const TArray<FHoudiniGeoPartObject>& HGPOs = InOutput->GetHoudiniGeoPartObjects();
// Get the previous bake objects
if (!InBakedOutputs.IsValidIndex(InOutputIndex))
InBakedOutputs.SetNum(InOutputIndex + 1);
const TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject>& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects;
TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject> NewBakedOutputObjects;
for (auto& Pair : OutputObjects)
{
const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key;
const FHoudiniOutputObject& OutputObject = Pair.Value;
// Add a new baked output object entry and update it with the previous bake's data, if available
FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier);
if (OldBakedOutputObjects.Contains(Identifier))
BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier);
UStaticMesh* StaticMesh = Cast<UStaticMesh>(OutputObject.OutputObject);
if (!StaticMesh || StaticMesh->IsPendingKill())
continue;
UStaticMeshComponent* InSMC = Cast<UStaticMeshComponent>(OutputObject.OutputComponent);
if (!InSMC || InSMC->IsPendingKill())
continue;
// Find the HGPO that matches this output identifier
const FHoudiniGeoPartObject* FoundHGPO = nullptr;
FindHGPO(Identifier, HGPOs, FoundHGPO);
// We do not bake templated geos
if (FoundHGPO && FoundHGPO->bIsTemplated)
continue;
const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh);
UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld;
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
FHoudiniPackageParams PackageParams;
const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder));
if (!ResolvePackageParams(
HoudiniAssetComponent,
InOutput,
Identifier,
OutputObject,
InHoudiniAssetName,
DefaultObjectName,
InBakeFolder,
bInReplaceAssets,
PackageParams,
OutPackagesToSave))
{
continue;
}
// Bake the static mesh if it is still temporary
UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
StaticMesh,
Cast<UStaticMesh>(BakedOutputObject.GetBakedObjectIfValid()),
PackageParams,
InAllOutputs,
OutActors,
InTempCookFolder.Path,
OutPackagesToSave,
InOutAlreadyBakedMaterialsMap);
if (!BakedSM || BakedSM->IsPendingKill())
continue;
// Record the baked object
BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString();
// Make sure we have a level to spawn to
if (!DesiredLevel || DesiredLevel->IsPendingKill())
continue;
// Try to find the unreal_bake_actor, if specified
FName BakeActorName;
AActor* FoundActor = nullptr;
bool bHasBakeActorName = false;
if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName))
return false;
UStaticMeshComponent* SMC = nullptr;
if (!FoundActor)
{
// Spawn the new actor
FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform());
if (!FoundActor || FoundActor->IsPendingKill())
continue;
// Copy properties to new actor
AStaticMeshActor* SMActor = Cast<AStaticMeshActor>(FoundActor);
if (!SMActor || SMActor->IsPendingKill())
continue;
SMC = SMActor->GetStaticMeshComponent();
}
else
{
if (bInReplaceAssets)
{
// Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it
UStaticMeshComponent* PrevSMC = Cast<UStaticMeshComponent>(BakedOutputObject.GetBakedComponentIfValid());
if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor))
{
SMC = PrevSMC;
}
}
const bool bCreateIfMissing = true;
USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing);
if (!IsValid(SMC))
{
// Create a new static mesh component on the existing actor
SMC = NewObject<UStaticMeshComponent>(FoundActor, NAME_None, RF_Transactional);
FoundActor->AddInstanceComponent(SMC);
if (IsValid(RootComponent))
SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
else
FoundActor->SetRootComponent(SMC);
SMC->RegisterComponent();
}
}
// We need to make a unique name for the actor, renaming an object on top of another is a fatal error
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor);
RenameAndRelabelActor(FoundActor, NewNameStr, false);
SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath);
if (IsValid(SMC))
{
const bool bCopyWorldTransform = true;
CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform);
SMC->SetStaticMesh(BakedSM);
BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString();
}
BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString();
OutActors.Add(FHoudiniEngineBakedActor(
FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC,
PackageParams.BakeFolder, PackageParams));
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = false;
const bool bInDestroyBakedInstancedActors = true;
const bool bInDestroyBakedInstancedComponents = true;
DestroyPreviousBakeOutput(
BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
}
// Update the cached baked output data
InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects;
return true;
}
bool FHoudiniEngineBakeUtils::ResolvePackageParams(
const UHoudiniAssetComponent* HoudiniAssetComponent,
UHoudiniOutput* InOutput,
const FHoudiniOutputObjectIdentifier& Identifier,
const FHoudiniOutputObject& InOutputObject,
const FString& InHoudiniAssetName,
const FString& DefaultObjectName,
const FDirectoryPath& InBakeFolder,
const bool bInReplaceAssets,
FHoudiniPackageParams& OutPackageParams,
TArray<UPackage*>& OutPackagesToSave)
{
FHoudiniAttributeResolver Resolver;
UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld;
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
// Set the replace mode based on if we are doing a replacement or incremental asset bake
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName,
InHoudiniAssetName, OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode);
// See if this output object has an unreal_level_path attribute specified
// In which case, we need to create/find the desired level for baking instead of using the current one
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
// Get the package path from the unreal_level_path attribute
FString LevelPackagePath = Resolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a level, add it to the packages to save
// TODO: ? always add the level to the packages to save?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
TArray<FHoudiniBakedOutput>& InBakedOutputs,
const FString& InHoudiniAssetName,
const FDirectoryPath& InBakeFolder,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
// Check that index is not negative
if (InOutputIndex < 0)
return false;
if (!InAllOutputs.IsValidIndex(InOutputIndex))
return false;
UHoudiniOutput* const Output = InAllOutputs[InOutputIndex];
if (!Output || Output->IsPendingKill())
return false;
TArray<UPackage*> PackagesToSave;
// Find the previous baked output data for this output index. If an entry
// does not exist, create entries up to and including this output index
if (!InBakedOutputs.IsValidIndex(InOutputIndex))
InBakedOutputs.SetNum(InOutputIndex + 1);
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = Output->GetOutputObjects();
FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex];
const TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject>& OldBakedOutputObjects = BakedOutput.BakedOutputObjects;
TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject> NewBakedOutputObjects;
const TArray<FHoudiniGeoPartObject> & HGPOs = Output->GetHoudiniGeoPartObjects();
for (auto & Pair : OutputObjects)
{
FHoudiniOutputObject& OutputObject = Pair.Value;
USplineComponent* SplineComponent = Cast<USplineComponent>(OutputObject.OutputComponent);
if (!SplineComponent || SplineComponent->IsPendingKill())
continue;
const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key;
FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier);
if (OldBakedOutputObjects.Contains(Identifier))
BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier);
// TODO: FIX ME!! May not work 100%
const FHoudiniGeoPartObject* FoundHGPO = nullptr;
for (auto & NextHGPO : HGPOs)
{
if (Identifier.GeoId == NextHGPO.GeoId &&
Identifier.ObjectId == NextHGPO.ObjectId &&
Identifier.PartId == NextHGPO.PartId)
{
FoundHGPO = &NextHGPO;
break;
}
}
if (!FoundHGPO)
continue;
const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName();
FHoudiniPackageParams PackageParams;
// Set the replace mode based on if we are doing a replacement or incremental asset bake
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniAttributeResolver Resolver;
UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName,
InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode);
BakeCurve(
OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets,
OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder);
}
// Update the cached bake output results
BakedOutput.BakedOutputObjects = NewBakedOutputObjects;
SaveBakedPackages(PackagesToSave);
return true;
}
bool
FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint)
{
if (!InActor || InActor->IsPendingKill())
return false;
if (!OutBlueprint || OutBlueprint->IsPendingKill())
return false;
if (InActor->GetInstanceComponents().Num() > 0)
FKismetEditorUtilities::AddComponentsToBlueprint(
OutBlueprint,
InActor->GetInstanceComponents());
if (OutBlueprint->GeneratedClass)
{
AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject());
if (!CDO || CDO->IsPendingKill())
return false;
const auto CopyOptions = (EditorUtilities::ECopyOptions::Type)
(EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties |
EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances);
EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions);
USceneComponent * Scene = CDO->GetRootComponent();
if (Scene && !Scene->IsPendingKill())
{
Scene->SetRelativeLocation(FVector::ZeroVector);
Scene->SetRelativeRotation(FRotator::ZeroRotator);
// Clear out the attachment info after having copied the properties from the source actor
Scene->SetupAttachment(nullptr);
while (true)
{
const int32 ChildCount = Scene->GetAttachChildren().Num();
if (ChildCount < 1)
break;
USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1];
if (Component && !Component->IsPendingKill())
Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
}
check(Scene->GetAttachChildren().Num() == 0);
// Ensure the light mass information is cleaned up
Scene->InvalidateLightingCache();
// Copy relative scale from source to target.
if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent())
{
Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D());
}
}
}
// Compile our blueprint and notify asset system about blueprint.
//FKismetEditorUtilities::CompileBlueprint(OutBlueprint);
//FAssetRegistryModule::AssetCreated(OutBlueprint);
return true;
}
bool
FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors)
{
FHoudiniEngineOutputStats BakeStats;
TArray<UPackage*> PackagesToSave;
TArray<UBlueprint*> Blueprints;
const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, bInRecenterBakedActors, BakeStats, Blueprints, PackagesToSave);
if (!bSuccess)
{
// TODO: ?
HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints."));
}
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Sync the CB to the baked objects
if(GEditor && Blueprints.Num() > 0)
{
TArray<UObject*> Assets;
Assets.Reserve(Blueprints.Num());
for (UBlueprint* Blueprint : Blueprints)
{
Assets.Add(Blueprint);
}
GEditor->SyncBrowserToObjects(Assets);
}
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
// Broadcast that the bake is complete
HoudiniAssetComponent->HandleOnPostBake(bSuccess);
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakeBlueprints(
UHoudiniAssetComponent* HoudiniAssetComponent,
bool bInReplaceAssets,
bool bInRecenterBakedActors,
FHoudiniEngineOutputStats& InBakeStats,
TArray<UBlueprint*>& OutBlueprints,
TArray<UPackage*>& OutPackagesToSave)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
AActor* OwnerActor = HoudiniAssetComponent->GetOwner();
const bool bIsOwnerActorValid = IsValid(OwnerActor);
TArray<FHoudiniEngineBakedActor> Actors;
// Don't process outputs that are not supported in blueprints
TArray<EHoudiniOutputType> OutputsToBake = {
EHoudiniOutputType::Mesh,
EHoudiniOutputType::Instancer,
EHoudiniOutputType::Curve
};
TArray<EHoudiniInstancerComponentType> InstancerComponentTypesToBake = {
EHoudiniInstancerComponentType::StaticMeshComponent,
EHoudiniInstancerComponentType::InstancedStaticMeshComponent,
EHoudiniInstancerComponentType::MeshSplitInstancerComponent,
EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent
};
// When baking blueprints we always create new actors since they are deleted from the world once copied into the
// blueprint
const bool bReplaceActors = false;
bool bBakeSuccess = BakeHoudiniActorToActors(
HoudiniAssetComponent,
bReplaceActors,
bInReplaceAssets,
Actors,
OutPackagesToSave,
InBakeStats,
&OutputsToBake,
&InstancerComponentTypesToBake);
if (!bBakeSuccess)
{
HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint."));
return false;
}
// Get the previous baked outputs
TArray<FHoudiniBakedOutput>& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs();
bBakeSuccess = BakeBlueprintsFromBakedActors(
Actors,
bInRecenterBakedActors,
bInReplaceAssets,
bIsOwnerActorValid ? OwnerActor->GetName() : FString(),
HoudiniAssetComponent->BakeFolder,
&BakedOutputs,
nullptr,
OutBlueprints,
OutPackagesToSave);
return bBakeSuccess;
}
UStaticMesh*
FHoudiniEngineBakeUtils::BakeStaticMesh(
UStaticMesh * StaticMesh,
const FHoudiniPackageParams& PackageParams,
const TArray<UHoudiniOutput*>& InAllOutputs,
const FDirectoryPath& InTempCookFolder,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
if (!StaticMesh || StaticMesh->IsPendingKill())
return nullptr;
TArray<UPackage*> PackagesToSave;
TArray<UHoudiniOutput*> Outputs;
const TArray<FHoudiniEngineBakedActor> BakedResults;
UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, InOutAlreadyBakedMaterialsMap);
if (BakedStaticMesh)
{
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Sync the CB to the baked objects
if(GEditor)
{
TArray<UObject*> Objects;
Objects.Add(BakedStaticMesh);
GEditor->SyncBrowserToObjects(Objects);
}
}
return BakedStaticMesh;
}
bool
FHoudiniEngineBakeUtils::BakeLandscape(
const UHoudiniAssetComponent* HoudiniAssetComponent,
int32 InOutputIndex,
const TArray<UHoudiniOutput*>& InAllOutputs,
TArray<FHoudiniBakedOutput>& InBakedOutputs,
bool bInReplaceActors,
bool bInReplaceAssets,
FString BakePath,
FString HoudiniAssetName,
FHoudiniEngineOutputStats& BakeStats
)
{
// Check that index is not negative
if (InOutputIndex < 0)
return false;
if (!InAllOutputs.IsValidIndex(InOutputIndex))
return false;
UHoudiniOutput* const Output = InAllOutputs[InOutputIndex];
if (!IsValid(Output))
return false;
// Find the previous baked output data for this output index. If an entry
// does not exist, create entries up to and including this output index
if (!InBakedOutputs.IsValidIndex(InOutputIndex))
InBakedOutputs.SetNum(InOutputIndex + 1);
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = Output->GetOutputObjects();
FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex];
const TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject>& OldBakedOutputObjects = BakedOutput.BakedOutputObjects;
TMap<FHoudiniBakedOutputObjectIdentifier, FHoudiniBakedOutputObject> NewBakedOutputObjects;
TArray<UPackage*> PackagesToSave;
TArray<UWorld*> LandscapeWorldsToUpdate;
FHoudiniPackageParams PackageParams;
for (auto& Elem : OutputObjects)
{
const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key;
FHoudiniOutputObject& OutputObject = Elem.Value;
FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier);
if (OldBakedOutputObjects.Contains(ObjectIdentifier))
BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier);
// Populate the package params for baking this output object.
if (!IsValid(OutputObject.OutputObject))
continue;
if (!OutputObject.OutputObject->IsA<UHoudiniLandscapePtr>())
continue;
UHoudiniLandscapePtr* LandscapePtr = Cast<UHoudiniLandscapePtr>(OutputObject.OutputObject);
ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr();
if (!IsValid(Landscape))
continue;
FString ObjectName = Landscape->GetName();
// Set the replace mode based on if we are doing a replacement or incremental asset bake
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
// Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder.
// The resolver is then also configured with the package params for subsequent resolving (level_path etc)
FHoudiniAttributeResolver Resolver;
UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver(
DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName,
HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode);
BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets,
PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats);
}
// Update the cached baked output data
BakedOutput.BakedOutputObjects = NewBakedOutputObjects;
if (PackagesToSave.Num() > 0)
{
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false);
}
for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate)
{
if (!LandscapeWorld)
continue;
FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld);
ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true);
if (LandscapeWorld->WorldComposition)
{
UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld);
}
}
if (PackagesToSave.Num() > 0)
{
// These packages were either created during the Bake process or they weren't
// loaded in the first place so be sure to unload them again to preserve their "state".
TArray<UPackage*> PackagesToUnload;
for (UPackage* Package : PackagesToSave)
{
if (!Package->IsDirty())
PackagesToUnload.Add(Package);
}
UPackageTools::UnloadPackages(PackagesToUnload);
}
#if WITH_EDITOR
FEditorDelegates::RefreshLevelBrowser.Broadcast();
FEditorDelegates::RefreshAllBrowsers.Broadcast();
#endif
return true;
}
bool
FHoudiniEngineBakeUtils::BakeLandscapeObject(
FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
bool bInReplaceActors,
bool bInReplaceAssets,
FHoudiniPackageParams& PackageParams,
FHoudiniAttributeResolver& InResolver,
TArray<UWorld*>& WorldsToUpdate,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& BakeStats)
{
UHoudiniLandscapePtr* LandscapePointer = Cast<UHoudiniLandscapePtr>(InOutputObject.OutputObject);
if (!LandscapePointer)
return false;
ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr();
if (!TileActor)
return false;
// Fetch the previous bake's pointer and proxy (if available)
ALandscapeProxy* PreviousTileActor = Cast<ALandscapeProxy>(InBakedOutputObject.GetBakedObjectIfValid());
UWorld* TileWorld = TileActor->GetWorld();
ULevel* TileLevel = TileActor->GetLevel();
ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true);
// If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC
// and has the appropriate name.
ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor();
check(SharedLandscapeActor);
// Fetch the previous bake's shared landscape actor (if available)
ALandscape* PreviousSharedLandscapeActor = nullptr;
if (IsValid(PreviousTileActor))
PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor();
const bool bHasSharedLandscape = SharedLandscapeActor != TileActor;
bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor;
if (bHasPreviousSharedLandscape)
{
// Ignore the previous shared landscape if the world's are different
// Typically in baking we treat completely different asset/output names in a bake as detached from the "previous" bake
if (PreviousSharedLandscapeActor->GetWorld() != SharedLandscapeActor->GetWorld())
bHasPreviousSharedLandscape = false;
}
bool bLandscapeReplaced = false;
if (bHasSharedLandscape)
{
// If we are baking in replace mode and we have a previous shared landscape actor, use the name of that
// actor
FString SharedLandscapeName = InResolver.ResolveAttribute(
HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME,
SharedLandscapeActor->GetName());
// If the shared landscape is still attached, or it's base name does not match the desired name, "bake" it
AActor* const AttachedParent = SharedLandscapeActor->GetAttachParentActor();
if (AttachedParent || SharedLandscapeActor->GetFName().GetPlainNameString() != SharedLandscapeName)
{
if (bHasPreviousSharedLandscape && bInReplaceActors &&
PreviousSharedLandscapeActor->GetFName().GetPlainNameString() == SharedLandscapeName)
{
SharedLandscapeName = PreviousSharedLandscapeActor->GetName();
}
else if (!bInReplaceActors)
{
// If we are not baking in replacement mode, create a unique name if the name is already in use
SharedLandscapeName = MakeUniqueObjectNameIfNeeded(
SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *SharedLandscapeName, SharedLandscapeActor);
}
if (SharedLandscapeActor->GetName() != SharedLandscapeName)
{
AActor* FoundActor = nullptr;
ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor<ALandscape>(TileWorld, SharedLandscapeName, FoundActor);
if (ExistingLandscape && bInReplaceActors)
{
// Even though we found an existing landscape with the desired type, we're just going to destroy/replace
// it for now.
FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0");
ExistingLandscape->Destroy();
bLandscapeReplaced = true;
}
// Fix name of shared landscape
FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName);
}
SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld());
}
}
// Find the world where the landscape tile should be placed.
TArray<ALandscapeProxy*> ValidLandscapes;
FString ActorName = InResolver.ResolveOutputName();
// If the unreal_level_path was not specified, then fallback to the tile world's package
FString PackagePath = TileWorld->GetOutermost() ? TileWorld->GetOutermost()->GetPathName() : FString();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
PackagePath = InResolver.ResolveFullLevelPath();
// Get the previous baked actor (if available) name, but only if it is in the
// same target level, and it's plain name (no numeric suffix) matches ActorName
// In replacement mode we'll then replace the previous tile actor.
if (bInReplaceActors && IsValid(PreviousTileActor))
{
UPackage* PreviousPackage = PreviousTileActor->GetOutermost();//GetPackage();
if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath &&
PreviousTileActor->GetFName().GetPlainNameString() == ActorName)
{
ActorName = PreviousTileActor->GetName();
}
}
bool bCreatedPackage = false;
UWorld* TargetWorld = nullptr;
ULevel* TargetLevel = nullptr;
ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake(
TileActor->GetWorld(),
nullptr, //unused in bake mode
ValidLandscapes,//unused in bake mode
-1, //unused in bake mode
-1, //unused in bake mode
ActorName,
PackagePath,
TargetWorld,
TargetLevel,
bCreatedPackage
);
check(TargetLevel)
check(TargetWorld)
if (TargetActor && TargetActor != TileActor)
{
if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor))
{
// We found an target matching the name that we want. For now, rename it and then nuke it, so that
// at the very least we can spawn a new actor with the desired name. At a later stage we'll implement
// a content update, if possible.
FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0"));
TargetActor->Destroy();
}
else
{
// incremental, keep existing actor and create a unique name for the new one
ActorName = MakeUniqueObjectNameIfNeeded(TargetActor->GetOuter(), TargetActor->GetClass(), ActorName, TileActor);
}
TargetActor = nullptr;
}
if (TargetLevel != TileActor->GetLevel())
{
bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel);
ALandscape* SharedLandscape = TileActor->GetLandscapeActor();
ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo();
check(LandscapeInfo);
// We can now move the current landscape to the new world / level
// if (TileActor->GetClass()->IsChildOf<ALandscapeStreamingProxy>())
{
// We can only move streaming proxies to sublevels for now.
TArray<AActor*> ActorsToMove = {TileActor};
ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel);
// We have now moved the landscape components into the new level. We can (hopefully) safely delete the
// old tile actor.
TileActor->Destroy();
TargetLevel->MarkPackageDirty();
TileActor = NewLandscapeProxy;
}
}
else
{
// Ensure the landscape actor is detached.
TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
}
UPackage * CreatedPackage = TargetLevel->GetOutermost();
TMap<UMaterialInterface *, UMaterialInterface *> AlreadyBakedMaterialsMap;
// Replace materials
if (TileActor->LandscapeMaterial)
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap);
TileActor->LandscapeMaterial = DuplicatedMaterial;
}
if (TileActor->LandscapeHoleMaterial)
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeHoleMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap);
TileActor->LandscapeHoleMaterial = DuplicatedMaterial;
}
// Ensure the tile actor has the desired name.
FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName);
if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass()))
{
// This is not a shared landscape. Be sure to update this landscape's world when
// baking is done.
WorldsToUpdate.AddUnique(TileActor->GetWorld());
}
if (bCreatedPackage)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(TargetLevel->GetOutermost());
}
// Record the landscape in the baked output object via a new UHoudiniLandscapePtr
// UHoudiniLandscapePtr* BakedLandscapePtr = NewObject<UHoudiniLandscapePtr>(LandscapePointer->GetOuter());
// if (IsValid(BakedLandscapePtr))
// {
// BakedLandscapePtr->SetSoftPtr(TileActor);
InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString();
// }
// else
// {
// InBakedOutputObject.BakedObject = nullptr;
// }
// Bake the landscape layer uassets
ULandscapeInfo* const LandscapeInfo = TileActor->GetLandscapeInfo();
if (IsValid(LandscapeInfo) && LandscapeInfo->Layers.Num() > 0)
{
TSet<ULandscapeLayerInfoObject*> TempLayers;
const int32 NumLayers = LandscapeInfo->Layers.Num();
TempLayers.Reserve(NumLayers);
for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex)
{
const FLandscapeInfoLayerSettings& Layer = LandscapeInfo->Layers[LayerIndex];
if (!IsValid(Layer.LayerInfoObj))
continue;
if (!IsObjectInTempFolder(Layer.LayerInfoObj, PackageParams.TempCookFolder))
continue;
if (!TempLayers.Contains(Layer.LayerInfoObj))
TempLayers.Add(Layer.LayerInfoObj);
}
// Setup package params to duplicate each layer
FHoudiniPackageParams LayerPackageParams = PackageParams;
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
LayerPackageParams.ReplaceMode = AssetPackageReplaceMode;
// Determine the final bake name of the "owning" landscape (shared landscape in tiled mode, or just the
// landscape actor itself in non-tiled mode
FString OwningLandscapeActorBakeName;
if (bHasSharedLandscape && IsValid(SharedLandscapeActor))
{
SharedLandscapeActor->GetName(OwningLandscapeActorBakeName);
}
else
{
TileActor->GetName(OwningLandscapeActorBakeName);
}
// Keep track of the landscape layers we are baking this time around, and replace in the baked output object
// at the end.
TMap<FName, FString> ThisBakedLandscapeLayers;
// Bake/duplicate temp layers and replace temp layers via LandscapeInfo
for (ULandscapeLayerInfoObject* const LayerInfo : TempLayers)
{
const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerInfo->LayerName.ToString());
LayerPackageParams.SplitStr = SanitizedLayerName;
LayerPackageParams.ObjectName = OwningLandscapeActorBakeName + TEXT("_layer_") + SanitizedLayerName;
// Get the previously baked layer info for this layer, if any
ULandscapeLayerInfoObject* const PreviousBakedLayerInfo = InBakedOutputObject.GetLandscapeLayerInfoIfValid(
LayerInfo->LayerName);
// If our name is the base name (no number) of the previous, then we can fetch the bake counter for
// replacement / incrementing from it
int32 BakeCounter = 0;
if (IsValid(PreviousBakedLayerInfo) && LayerPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakedLayerInfo))
{
// Get the bake counter from the previous bake
FHoudiniPackageParams::GetBakeCounterFromBakedAsset(PreviousBakedLayerInfo, BakeCounter);
}
FString LayerPackageName;
UPackage* const LayerPackage = LayerPackageParams.CreatePackageForObject(LayerPackageName, BakeCounter);
if (IsValid(LayerPackage))
{
BakeStats.NotifyPackageCreated(1);
ULandscapeLayerInfoObject* BakedLayer = DuplicateObject<ULandscapeLayerInfoObject>(
LayerInfo, LayerPackage, *LayerPackageName);
if (IsValid(BakedLayer))
{
OutPackagesToSave.Add(LayerPackage);
// Trigger update of the Layer Info
BakedLayer->PreEditChange(nullptr);
BakedLayer->PostEditChange();
BakedLayer->MarkPackageDirty();
// Mark the package dirty...
LayerPackage->MarkPackageDirty();
LandscapeInfo->ReplaceLayer(LayerInfo, BakedLayer);
// Record as the new baked result for the LayerName
ThisBakedLandscapeLayers.Add(LayerInfo->LayerName, FSoftObjectPath(BakedLayer).ToString());
}
}
}
// Update the baked landscape layers in InBakedOutputObject
InBakedOutputObject.LandscapeLayers = ThisBakedLandscapeLayers;
}
// Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks
InOutputObject.OutputObject = nullptr;
DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true);
// ----------------------------------------------------
// Collect baking stats
// ----------------------------------------------------
if (bLandscapeReplaced)
BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1);
else
BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1);
if (bCreatedPackage)
BakeStats.NotifyPackageCreated(1);
else
if (TileLevel != TargetLevel)
BakeStats.NotifyPackageUpdated(1);
return true;
}
UStaticMesh *
FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded(
UStaticMesh * InStaticMesh,
UStaticMesh * InPreviousBakeStaticMesh,
const FHoudiniPackageParams &PackageParams,
const TArray<UHoudiniOutput*>& InParentOutputs,
const TArray<FHoudiniEngineBakedActor>& InCurrentBakedActors,
const FString& InTemporaryCookFolder,
TArray<UPackage*> & OutCreatedPackages,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
if (!InStaticMesh || InStaticMesh->IsPendingKill())
return nullptr;
const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder);
if (!bIsTemporaryStaticMesh)
{
// The Static Mesh is not a temporary one/already baked, we can simply reuse it
// instead of duplicating it
return InStaticMesh;
}
// Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with
// a previous output: instancers etc)
for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors)
{
if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject)
&& BakedActor.BakedObject->IsA(InStaticMesh->GetClass()))
{
// We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject
// of a compatible class
return Cast<UStaticMesh>(BakedActor.BakedObject);
}
}
// InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it
// If we have a previously baked static mesh, get the bake counter from it so that both replace and increment
// is consistent with the bake counter
int32 BakeCounter = 0;
bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh);
TArray<FStaticMaterial> PreviousBakeMaterials;
if (bPreviousBakeStaticMeshValid)
{
bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh);
if (bPreviousBakeStaticMeshValid)
{
PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter);
PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials();
}
}
FString CreatedPackageName;
UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter);
if (!MeshPackage || MeshPackage->IsPendingKill())
return nullptr;
OutCreatedPackages.Add(MeshPackage);
// We need to be sure the package has been fully loaded before calling DuplicateObject
if (!MeshPackage->IsFullyLoaded())
{
FlushAsyncLoading();
if (!MeshPackage->GetOuter())
{
MeshPackage->FullyLoad();
}
else
{
MeshPackage->GetOutermost()->FullyLoad();
}
}
// If the a UStaticMesh with that name already exists then detach it from all of its components before replacing
// it so that its render resources can be safely replaced/updated, and then reattach it
UStaticMesh * DuplicatedStaticMesh = nullptr;
UStaticMesh* ExistingMesh = FindObject<UStaticMesh>(MeshPackage, *CreatedPackageName);
bool bFoundExistingMesh = false;
if (IsValid(ExistingMesh))
{
FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh);
DuplicatedStaticMesh = DuplicateObject<UStaticMesh>(InStaticMesh, MeshPackage, *CreatedPackageName);
bFoundExistingMesh = true;
}
else
{
DuplicatedStaticMesh = DuplicateObject<UStaticMesh>(InStaticMesh, MeshPackage, *CreatedPackageName);
}
if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill())
return nullptr;
// Add meta information.
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MeshPackage, DuplicatedStaticMesh,
HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true"));
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MeshPackage, DuplicatedStaticMesh,
HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName);
// See if we need to duplicate materials and textures.
TArray<FStaticMaterial>DuplicatedMaterials;
TArray<FStaticMaterial>& Materials = DuplicatedStaticMesh->StaticMaterials;
for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx)
{
UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface;
if (!MaterialInterface || MaterialInterface->IsPendingKill())
continue;
// Only duplicate the material if it is temporary
if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder))
{
UPackage * MaterialPackage = Cast<UPackage>(MaterialInterface->GetOuter());
if (MaterialPackage && !MaterialPackage->IsPendingKill())
{
FString MaterialName;
if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(
MeshPackage, DuplicatedStaticMesh, MaterialName))
{
MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1);
// We only deal with materials.
if (!MaterialInterface->IsA(UMaterial::StaticClass()) && !MaterialInterface->IsA(UMaterialInstance::StaticClass()))
{
continue;
}
UMaterialInterface * Material = MaterialInterface;
if (Material && !Material->IsPendingKill())
{
// Look for a previous bake material at this index
UMaterialInterface* PreviousBakeMaterial = nullptr;
if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx))
{
PreviousBakeMaterial = Cast<UMaterialInterface>(PreviousBakeMaterials[MaterialIdx].MaterialInterface);
}
// Duplicate material resource.
UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage(
Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap);
if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill())
continue;
// Store duplicated material.
FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx];
DupeStaticMaterial.MaterialInterface = DuplicatedMaterial;
DuplicatedMaterials.Add(DupeStaticMaterial);
continue;
}
}
}
}
// We can simply reuse the source material
DuplicatedMaterials.Add(Materials[MaterialIdx]);
}
// Assign duplicated materials.
DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials;
// Notify registry that we have created a new duplicate mesh.
if (!bFoundExistingMesh)
FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh);
// Dirty the static mesh package.
DuplicatedStaticMesh->MarkPackageDirty();
return DuplicatedStaticMesh;
}
ALandscapeProxy*
FHoudiniEngineBakeUtils::BakeHeightfield(
ALandscapeProxy * InLandscapeProxy,
const FHoudiniPackageParams & PackageParams,
const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType)
{
if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill())
return nullptr;
const FString & BakeFolder = PackageParams.BakeFolder;
const FString & AssetName = PackageParams.HoudiniAssetName;
TArray<UPackage*> PackagesToSave;
switch (LandscapeOutputBakeType)
{
case EHoudiniLandscapeOutputBakeType::Detachment:
{
// Detach the landscape from the Houdini Asset Actor
InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform);
}
break;
case EHoudiniLandscapeOutputBakeType::BakeToImage:
{
// Create heightmap image to the bake folder
ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo();
if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill())
return nullptr;
// bake to image must use absoluate path,
// and the file name has a file extension (.png)
FString BakeFolderInFullPath = BakeFolder;
// Figure absolute path,
if (!BakeFolderInFullPath.EndsWith("/"))
BakeFolderInFullPath += "/";
if (BakeFolderInFullPath.StartsWith("/Game"))
BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5);
if (BakeFolderInFullPath.StartsWith("/"))
BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1);
FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png";
InLandscapeInfo->ExportHeightmap(FullPath);
// TODO:
// We should update this to have an asset/package..
}
break;
case EHoudiniLandscapeOutputBakeType::BakeToWorld:
{
ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo();
if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill())
return nullptr;
// 0. Get Landscape Data //
// Extract landscape height data
TArray<uint16> InLandscapeHeightData;
int32 XSize, YSize;
FVector Min, Max;
if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max))
return nullptr;
// Extract landscape Layers data
TArray<FLandscapeImportLayerInfo> InLandscapeImportLayerInfos;
for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n)
{
TArray<uint8> CurrentLayerIntData;
FLinearColor LayerUsageDebugColor;
FString LayerName;
if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeProxy, InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName))
continue;
FLandscapeImportLayerInfo CurrentLayerInfo;
CurrentLayerInfo.LayerName = FName(LayerName);
CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj;
CurrentLayerInfo.LayerData = CurrentLayerIntData;
CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor;
InLandscapeImportLayerInfos.Add(CurrentLayerInfo);
}
// 1. Create package //
FString PackagePath = PackageParams.GetPackagePath();
FString PackageName = PackageParams.GetPackageName();
UPackage *CreatedPackage = nullptr;
FString CreatedPackageName;
CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName);
if (!CreatedPackage)
return nullptr;
// 2. Create a new world asset with dialog //
UWorldFactory* Factory = NewObject<UWorldFactory>();
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog(
PackageName, PackagePath,
UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset"));
UWorld* NewWorld = Cast<UWorld>(Asset);
if (!NewWorld)
return nullptr;
NewWorld->SetCurrentLevel(NewWorld->PersistentLevel);
// 4. Spawn a landscape proxy actor in the created world
ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor<ALandscapeStreamingProxy>();
if (!BakedLandscapeProxy)
return nullptr;
// Create a new GUID
FGuid currentGUID = FGuid::NewGuid();
BakedLandscapeProxy->SetLandscapeGuid(currentGUID);
// Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue
BakedLandscapeProxy->bCastStaticShadow = false;
// 5. Import data to the created landscape proxy
TMap<FGuid, TArray<uint16>> HeightmapDataPerLayers;
TMap<FGuid, TArray<FLandscapeImportLayerInfo>> MaterialLayerDataPerLayer;
HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData);
MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos);
ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive;
BakedLandscapeProxy->Import(
currentGUID,
0, 0, XSize-1, YSize-1,
InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads,
HeightmapDataPerLayers, NULL,
MaterialLayerDataPerLayer, ImportLayerType);
BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2);
TMap<UMaterialInterface *, UMaterialInterface *> AlreadyBakedMaterialsMap;
if (BakedLandscapeProxy->LandscapeMaterial)
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap);
BakedLandscapeProxy->LandscapeMaterial = DuplicatedMaterial;
}
if (BakedLandscapeProxy->LandscapeHoleMaterial)
{
UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeHoleMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap);
BakedLandscapeProxy->LandscapeHoleMaterial = DuplicatedMaterial;
}
// 6. Register all the landscape components, and set landscape actor transform
BakedLandscapeProxy->RegisterAllComponents();
BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform());
// 7. Save Package
PackagesToSave.Add(CreatedPackage);
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Sync the CB to the baked objects
if(GEditor)
{
TArray<UObject*> Objects;
Objects.Add(NewWorld);
GEditor->SyncBrowserToObjects(Objects);
}
}
break;
}
return InLandscapeProxy;
}
bool
FHoudiniEngineBakeUtils::BakeCurve(
USplineComponent* InSplineComponent,
ULevel* InLevel,
const FHoudiniPackageParams &PackageParams,
AActor*& OutActor,
USplineComponent*& OutSplineComponent,
FName InOverrideFolderPath,
AActor* InActor)
{
if (!IsValid(InActor))
{
UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr;
if (!Factory)
return false;
OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform());
}
else
{
OutActor = InActor;
}
// The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset
const FName BaseActorName(PackageParams.ObjectName);
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor);
RenameAndRelabelActor(OutActor, NewNameStr, false);
OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath);
USplineComponent* DuplicatedSplineComponent = DuplicateObject<USplineComponent>(
InSplineComponent,
OutActor,
FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName)));
OutActor->AddInstanceComponent(DuplicatedSplineComponent);
const bool bCreateIfMissing = true;
USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing);
DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
// We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the
// world transform
DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform());
FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent);
DuplicatedSplineComponent->RegisterComponent();
OutSplineComponent = DuplicatedSplineComponent;
return true;
}
bool
FHoudiniEngineBakeUtils::BakeCurve(
const FHoudiniOutputObject& InOutputObject,
FHoudiniBakedOutputObject& InBakedOutputObject,
// const TArray<FHoudiniBakedOutput>& InAllBakedOutputs,
const FHoudiniPackageParams &PackageParams,
FHoudiniAttributeResolver& InResolver,
bool bInReplaceActors,
bool bInReplaceAssets,
TArray<FHoudiniEngineBakedActor>& OutActors,
TArray<UPackage*>& OutPackagesToSave,
AActor* InFallbackActor,
const FString& InFallbackWorldOutlinerFolder)
{
USplineComponent* SplineComponent = Cast<USplineComponent>(InOutputObject.OutputComponent);
if (!IsValid(SplineComponent))
return false;
// By default spawn in the current level unless specified via the unreal_level_path attribute
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH);
if (bHasLevelPathAttribute)
{
UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld;
// Get the package path from the unreal_level_apth attribute
FString LevelPackagePath = InResolver.ResolveFullLevelPath();
bool bCreatedPackage = false;
if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
LevelPackagePath,
DesiredLevel,
DesiredWorld,
bCreatedPackage))
{
// TODO: LOG ERROR IF NO LEVEL
return false;
}
// If we have created a new level, add it to the packages to save
// TODO: ? always add?
if (bCreatedPackage && DesiredLevel)
{
// We can now save the package again, and unload it.
OutPackagesToSave.Add(DesiredLevel->GetOutermost());
}
}
if(!DesiredLevel)
return false;
// Try to find the unreal_bake_actor, if specified, or fallback to the default named actor
FName BakeActorName;
AActor* FoundActor = nullptr;
bool bHasBakeActorName = false;
if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName))
return false;
// If we are baking in replace mode, remove the previous bake component
if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty())
{
UActorComponent* PrevComponent = Cast<UActorComponent>(InBakedOutputObject.GetBakedComponentIfValid());
if (PrevComponent && PrevComponent->GetOwner() == FoundActor)
{
RemovePreviouslyBakedComponent(PrevComponent);
}
}
FHoudiniPackageParams CurvePackageParams = PackageParams;
CurvePackageParams.ObjectName = BakeActorName.ToString();
USplineComponent* NewSplineComponent = nullptr;
const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName));
if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor))
return false;
InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString();
InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString();
// If we are baking in replace mode, remove previously baked components/instancers
if (bInReplaceActors && bInReplaceAssets)
{
const bool bInDestroyBakedComponent = false;
const bool bInDestroyBakedInstancedActors = true;
const bool bInDestroyBakedInstancedComponents = true;
DestroyPreviousBakeOutput(
InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents);
}
FHoudiniEngineBakedActor Result;
Result.Actor = FoundActor;
Result.ActorBakeName = BakeActorName;
Result.BakeFolderPath = PackageParams.BakeFolder;
Result.BakedObjectPackageParams = PackageParams;
OutActors.Add(Result);
return true;
}
AActor*
FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor(
UHoudiniSplineComponent * InHoudiniSplineComponent,
const FHoudiniPackageParams & PackageParams,
UWorld* WorldToSpawn,
const FTransform & SpawnTransform)
{
if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill())
return nullptr;
TArray<FVector> & DisplayPoints = InHoudiniSplineComponent->DisplayPoints;
if (DisplayPoints.Num() < 2)
return nullptr;
ULevel* DesiredLevel = GWorld->GetCurrentLevel();
UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr;
if (!Factory)
return nullptr;
// Remove the actor if it exists
for (auto & Actor : DesiredLevel->Actors)
{
if (!Actor)
continue;
if (Actor->GetName() == PackageParams.ObjectName)
{
UWorld* World = Actor->GetWorld();
if (!World)
World = GWorld;
Actor->RemoveFromRoot();
Actor->ConditionalBeginDestroy();
World->EditorDestroyActor(Actor, true);
break;
}
}
AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform());
USplineComponent* BakedUnrealSplineComponent = NewObject<USplineComponent>(NewActor);
if (!BakedUnrealSplineComponent)
return nullptr;
// add display points to created unreal spline component
for (int32 n = 0; n < DisplayPoints.Num(); ++n)
{
FVector & NextPoint = DisplayPoints[n];
BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local);
// Set the curve point type to be linear, since we are using display points
BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear);
}
NewActor->AddInstanceComponent(BakedUnrealSplineComponent);
BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
FAssetRegistryModule::AssetCreated(NewActor);
FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent);
BakedUnrealSplineComponent->RegisterComponent();
// The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset
const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor);
RenameAndRelabelActor(NewActor, NewNameStr, false);
NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName));
return NewActor;
}
UBlueprint*
FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint(
UHoudiniSplineComponent * InHoudiniSplineComponent,
const FHoudiniPackageParams & PackageParams,
UWorld* WorldToSpawn,
const FTransform & SpawnTransform)
{
if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill())
return nullptr;
FGuid BakeGUID = FGuid::NewGuid();
if (!BakeGUID.IsValid())
BakeGUID = FGuid::NewGuid();
// We only want half of generated guid string.
FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength);
// Generate Blueprint name.
FString BlueprintName = PackageParams.ObjectName + "_BP";
// Generate unique package name.
FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName;
PackageName = UPackageTools::SanitizePackageName(PackageName);
// See if package exists, if it does, we need to regenerate the name.
UPackage * Package = FindPackage(nullptr, *PackageName);
if (Package && !Package->IsPendingKill())
{
// Package does exist, there's a collision, we need to generate a new name.
BakeGUID.Invalidate();
}
else
{
// Create actual package.
Package = CreatePackage(nullptr, *PackageName);
}
AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor(
InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform);
TArray<UPackage*> PackagesToSave;
UBlueprint * Blueprint = nullptr;
if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill())
{
UObject* Asset = nullptr;
Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName));
if (!Asset)
{
UBlueprintFactory* Factory = NewObject<UBlueprintFactory>();
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
Asset = AssetToolsModule.Get().CreateAsset(
BlueprintName, PackageParams.BakeFolder,
UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset"));
}
TArray<UActorComponent*> Components;
for (auto & Next : CreatedHoudiniSplineActor->GetComponents())
{
Components.Add(Next);
}
Blueprint = Cast<UBlueprint>(Asset);
// Clear old Blueprint Node tree
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
int32 NodeSize = SCS->GetAllNodes().Num();
for (int32 n = NodeSize - 1; n >= 0; --n)
SCS->RemoveNode(SCS->GetAllNodes()[n]);
FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components);
CreatedHoudiniSplineActor->RemoveFromRoot();
CreatedHoudiniSplineActor->ConditionalBeginDestroy();
GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true);
Package->MarkPackageDirty();
PackagesToSave.Add(Package);
}
// Save the created BP package.
FHoudiniEngineBakeUtils::SaveBakedPackages
(PackagesToSave);
return Blueprint;
}
void
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
UPackage * Package, UObject * Object, const TCHAR * Key,
const TCHAR * Value)
{
if (!Package || Package->IsPendingKill())
return;
UMetaData * MetaData = Package->GetMetaData();
if (MetaData && !MetaData->IsPendingKill())
MetaData->SetValue(Object, Key, Value);
}
bool
FHoudiniEngineBakeUtils::
GetHoudiniGeneratedNameFromMetaInformation(
UPackage * Package, UObject * Object, FString & HoudiniName)
{
if (!Package || Package->IsPendingKill())
return false;
UMetaData * MetaData = Package->GetMetaData();
if (!MetaData || MetaData->IsPendingKill())
return false;
if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT))
{
// Retrieve name used for package generation.
const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME);
//HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength));
HoudiniName = NameFull;
return true;
}
return false;
}
UMaterialInterface *
FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage(
UMaterialInterface * Material, UMaterialInterface* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams,
TArray<UPackage*> & OutGeneratedPackages,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
if (InOutAlreadyBakedMaterialsMap.Contains(Material))
{
return InOutAlreadyBakedMaterialsMap[Material];
}
UMaterialInterface * DuplicatedMaterial = nullptr;
FString CreatedMaterialName;
// Create material package. Use the same package params as static mesh, but with the material's name
FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams;
MaterialPackageParams.ObjectName = MaterialName;
// Check if there is a valid previous material. If so, get the bake counter for consistency in
// replace or iterative package naming
bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial);
int32 BakeCounter = 0;
TArray<UMaterialExpression*> PreviousBakeMaterialExpressions;
if (bIsPreviousBakeMaterialValid && PreviousBakeMaterial->IsA(UMaterial::StaticClass()))
{
UMaterial * PreviousMaterialCast = Cast<UMaterial>(PreviousBakeMaterial);
bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial);
if (bIsPreviousBakeMaterialValid && PreviousMaterialCast)
{
MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter);
PreviousBakeMaterialExpressions = PreviousMaterialCast->Expressions;
}
}
UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter);
if (!MaterialPackage || MaterialPackage->IsPendingKill())
return nullptr;
// Clone material.
DuplicatedMaterial = DuplicateObject< UMaterialInterface >(Material, MaterialPackage, *CreatedMaterialName);
if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill())
return nullptr;
// Add meta information.
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MaterialPackage, DuplicatedMaterial,
HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true"));
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
MaterialPackage, DuplicatedMaterial,
HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName);
// Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them.
UMaterial * DuplicatedMaterialCast = Cast<UMaterial>(DuplicatedMaterial);
if (DuplicatedMaterialCast)
{
const int32 NumExpressions = DuplicatedMaterialCast->Expressions.Num();
for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx)
{
UMaterialExpression* Expression = DuplicatedMaterialCast->Expressions[ExpressionIdx];
UMaterialExpression* PreviousBakeExpression = nullptr;
if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx))
{
PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx];
}
FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample(
Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages);
}
}
// Notify registry that we have created a new duplicate material.
FAssetRegistryModule::AssetCreated(DuplicatedMaterial);
// Dirty the material package.
DuplicatedMaterial->MarkPackageDirty();
// Recompile the baked material
// DuplicatedMaterial->ForceRecompileForRendering();
// Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material
// which ForceRecompileForRendering does not do
if (DuplicatedMaterialCast)
{
UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterialCast);
}
OutGeneratedPackages.Add(MaterialPackage);
InOutAlreadyBakedMaterialsMap.Add(Material, DuplicatedMaterial);
return DuplicatedMaterial;
}
void
FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample(
UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression,
const FHoudiniPackageParams& PackageParams, TArray<UPackage*> & OutCreatedPackages)
{
UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression);
if (!TextureSample || TextureSample->IsPendingKill())
return;
UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture);
if (!Texture || Texture->IsPendingKill())
return;
UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter());
if (!TexturePackage || TexturePackage->IsPendingKill())
return;
// Try to get the previous bake's texture
UTexture2D* PreviousBakeTexture = nullptr;
if (IsValid(PreviousBakeMaterialExpression))
{
UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression);
if (IsValid(PreviousBakeTextureSample))
PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture);
}
FString GeneratedTextureName;
if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(
TexturePackage, Texture, GeneratedTextureName))
{
// Duplicate texture.
UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage(
Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages);
// Re-assign generated texture.
TextureSample->Texture = DuplicatedTexture;
}
}
UTexture2D *
FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage(
UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams,
TArray<UPackage*> & OutCreatedPackages)
{
UTexture2D* DuplicatedTexture = nullptr;
#if WITH_EDITOR
// Retrieve original package of this texture.
UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter());
if (!TexturePackage || TexturePackage->IsPendingKill())
return nullptr;
FString GeneratedTextureName;
if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName))
{
UMetaData * MetaData = TexturePackage->GetMetaData();
if (!MetaData || MetaData->IsPendingKill())
return nullptr;
// Retrieve texture type.
const FString & TextureType =
MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE);
FString CreatedTextureName;
// Create texture package. Use the same package params as material's, but with object name appended by generated texture's name
FHoudiniPackageParams TexturePackageParams = PackageParams;
TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName;
// Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when
// replacing/iterating
bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture);
int32 BakeCounter = 0;
if (bIsPreviousBakeTextureValid)
{
bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture);
if (bIsPreviousBakeTextureValid)
{
TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter);
}
}
UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter);
if (!NewTexturePackage || NewTexturePackage->IsPendingKill())
return nullptr;
// Clone texture.
DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName);
if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill())
return nullptr;
// Add meta information.
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
NewTexturePackage, DuplicatedTexture,
HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true"));
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
NewTexturePackage, DuplicatedTexture,
HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName);
FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage(
NewTexturePackage, DuplicatedTexture,
HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType);
// Notify registry that we have created a new duplicate texture.
FAssetRegistryModule::AssetCreated(DuplicatedTexture);
// Dirty the texture package.
DuplicatedTexture->MarkPackageDirty();
OutCreatedPackages.Add(NewTexturePackage);
}
#endif
return DuplicatedTexture;
}
bool
FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent)
{
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
return false;
AActor * ActorOwner = HoudiniAssetComponent->GetOwner();
if (!ActorOwner || ActorOwner->IsPendingKill())
return false;
UWorld* World = ActorOwner->GetWorld();
if (!World)
World = GWorld;
World->EditorDestroyActor(ActorOwner, false);
return true;
}
void
FHoudiniEngineBakeUtils::SaveBakedPackages(TArray<UPackage*> & PackagesToSave, bool bSaveCurrentWorld)
{
UWorld * CurrentWorld = nullptr;
if (bSaveCurrentWorld && GEditor)
CurrentWorld = GEditor->GetEditorWorldContext().World();
if (CurrentWorld)
{
// Save the current map
FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false);
UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath);
if (CurrentWorldPackage)
{
CurrentWorldPackage->MarkPackageDirty();
PackagesToSave.Add(CurrentWorldPackage);
}
}
FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false);
}
bool
FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray<UHoudiniOutput*> InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier)
{
if (!InObjectToFind || InObjectToFind->IsPendingKill())
return false;
const int32 NumOutputs = InOutputs.Num();
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx)
{
const UHoudiniOutput* CurOutput = InOutputs[OutputIdx];
if (!IsValid(CurOutput))
continue;
if (CurOutput->GetType() != InOutputType)
continue;
for (auto& CurOutputObject : CurOutput->GetOutputObjects())
{
if (CurOutputObject.Value.OutputObject == InObjectToFind
|| CurOutputObject.Value.OutputComponent == InObjectToFind
|| CurOutputObject.Value.ProxyObject == InObjectToFind
|| CurOutputObject.Value.ProxyComponent == InObjectToFind)
{
OutOutputIndex = OutputIdx;
OutIdentifier = CurOutputObject.Key;
return true;
}
}
}
return false;
}
bool
FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC)
{
if (!InObject || InObject->IsPendingKill())
return false;
FString TempPath = FString();
// TODO: Get the HAC outputs in a better way?
TArray<UHoudiniOutput*> Outputs;
if (InHAC && !InHAC->IsPendingKill())
{
const int32 NumOutputs = InHAC->GetNumOutputs();
Outputs.Reserve(NumOutputs);
for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx)
{
Outputs.Add(InHAC->GetOutputAt(OutputIdx));
}
TempPath = InHAC->TemporaryCookFolder.Path;
}
return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath);
}
bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder)
{
if (!IsValid(InObject))
return false;
// Check the package path for this object
// If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated
UPackage* ObjectPackage = InObject->GetOutermost();
if (ObjectPackage && !ObjectPackage->IsPendingKill())
{
const FString PathName = ObjectPackage->GetPathName();
if (PathName.StartsWith(InTemporaryCookFolder))
return true;
// Also check the default temp folder
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder))
return true;
}
return false;
}
bool FHoudiniEngineBakeUtils::IsObjectTemporary(
UObject* InObject, EHoudiniOutputType InOutputType, const TArray<UHoudiniOutput*>& InParentOutputs, const FString& InTemporaryCookFolder)
{
if (!InObject || InObject->IsPendingKill())
return false;
int32 ParentOutputIndex = -1;
FHoudiniOutputObjectIdentifier Identifier;
if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier))
return true;
// Check the package path for this object
// If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated
if (IsObjectInTempFolder(InObject, InTemporaryCookFolder))
return true;
/*
UPackage* ObjectPackage = InObject->GetOutermost();
if (ObjectPackage && !ObjectPackage->IsPendingKill())
{
// TODO: this just indicates that the object was generated by H
// it could as well have been baked before...
// we should probably add a "temp" metadata
// Look in the meta info as well??
UMetaData * MetaData = ObjectPackage->GetMetaData();
if (!MetaData || MetaData->IsPendingKill())
return false;
if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT))
return true;
}
*/
return false;
}
void
FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(
AActor* NewActor,
UStaticMeshComponent* NewSMC,
UStaticMeshComponent* InSMC,
bool bInCopyWorldTransform)
{
if (!NewSMC || NewSMC->IsPendingKill())
return;
if (!InSMC || InSMC->IsPendingKill())
return;
// Copy properties to new actor
//UStaticMeshComponent* OtherSMC_NonConst = const_cast<UStaticMeshComponent*>(StaticMeshComponent);
NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName());
NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled());
NewSMC->LightmassSettings = InSMC->LightmassSettings;
NewSMC->CastShadow = InSMC->CastShadow;
NewSMC->SetMobility(InSMC->Mobility);
UBodySetup* InBodySetup = InSMC->GetBodySetup();
UBodySetup* NewBodySetup = NewSMC->GetBodySetup();
if (InBodySetup && NewBodySetup)
{
// Copy the BodySetup
NewBodySetup->CopyBodyPropertiesFrom(InBodySetup);
// We need to recreate the physics mesh for the new body setup
NewBodySetup->InvalidatePhysicsData();
NewBodySetup->CreatePhysicsMeshes();
// Only copy the physical material if it's different from the default one,
// As this was causing crashes on BakeToActors in some cases
if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial)
NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial());
}
if (NewActor && !NewActor->IsPendingKill())
NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame);
NewSMC->SetVisibility(InSMC->IsVisible());
// TODO:
// // Reapply the uproperties modified by attributes on the new component
// FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO);
// The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another
UClass* ComponentClass = nullptr;
if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass()))
{
ComponentClass = NewSMC->GetClass();
}
else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass()))
{
ComponentClass = InSMC->GetClass();
}
else
{
HOUDINI_LOG_WARNING(
TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"),
*(InSMC->GetName()),
*(NewSMC->GetClass()->GetName()));
NewSMC->PostEditChange();
return;
}
TSet<const FProperty*> SourceUCSModifiedProperties;
InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties);
AActor* SourceActor = InSMC->GetOwner();
if (!IsValid(SourceActor))
{
NewSMC->PostEditChange();
return;
}
TArray<UObject*> ModifiedObjects;
const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty);
// Copy component properties
for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext )
{
const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient );
const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC );
const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) );
const bool bIsTransform =
Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() ||
Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() ||
Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName();
// auto SourceComponentIsRoot = [&]()
// {
// USceneComponent* RootComponent = SourceActor->GetRootComponent();
// if (InSMC == RootComponent)
// {
// return true;
// }
// return false;
// };
// if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property)
// && ( !bIsTransform || !SourceComponentIsRoot() ) )
if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property)
&& !bIsTransform )
{
// const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp)))
// && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate)));
const bool bIsSafeToCopy = true;
if( bIsSafeToCopy )
{
if (!Options.CanCopyProperty(*Property, *SourceActor))
{
continue;
}
if( !ModifiedObjects.Contains(NewSMC) )
{
NewSMC->SetFlags(RF_Transactional);
NewSMC->Modify();
ModifiedObjects.Add(NewSMC);
}
if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty)
{
// @todo simulate: Should we be calling this on the component instead?
NewActor->PreEditChange( Property );
}
EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property);
if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty)
{
FPropertyChangedEvent PropertyChangedEvent( Property );
NewActor->PostEditChangeProperty( PropertyChangedEvent );
}
}
}
}
if (bInCopyWorldTransform)
{
NewSMC->SetWorldTransform(InSMC->GetComponentTransform());
}
NewSMC->PostEditChange();
};
bool
FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor(
AActor* InNewBakedActor,
ULevel* InLevel,
const FHoudiniPackageParams& InPackageParams)
{
// Remove a previous bake actor if it exists
for (auto & Actor : InLevel->Actors)
{
if (!Actor)
continue;
if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName)
{
UWorld* World = Actor->GetWorld();
if (!World)
World = GWorld;
Actor->RemoveFromRoot();
Actor->ConditionalBeginDestroy();
World->EditorDestroyActor(Actor, true);
return true;
}
}
return false;
}
bool
FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent)
{
if (!IsValid(InComponent))
return false;
// Remove from its actor first
if (InComponent->GetOwner())
InComponent->GetOwner()->RemoveOwnedComponent(InComponent);
// Detach from its parent component if attached
USceneComponent* SceneComponent = Cast<USceneComponent>(InComponent);
if (IsValid(SceneComponent))
SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
InComponent->UnregisterComponent();
InComponent->DestroyComponent();
return true;
}
FName
FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner)
{
// Get an output folder path for PDG outputs generated by InOutputOwner.
// The folder path is: <InOutputOwner's folder path (if it is an actor)>/<InOutputOwner's name>
FString FolderName;
FName FolderDirName;
AActor* OuterActor = Cast<AActor>(InOutputOwner);
if (OuterActor)
{
FolderName = OuterActor->GetActorLabel();
FolderDirName = OuterActor->GetFolderPath();
}
else
{
FolderName = InOutputOwner->GetName();
}
if (!FolderDirName.IsNone())
return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName));
else
return FName(FolderName);
}
void
FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique)
{
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
const FSoftObjectPath OldPath = FSoftObjectPath(InAsset);
FString NewName;
if (bMakeUniqueIfNotUnique)
NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetOutermost()/*GetPackage()*/, InAsset->GetClass(), InNewName, InAsset);
else
NewName = InNewName;
FHoudiniEngineUtils::RenameObject(InAsset, *NewName);
const FSoftObjectPath NewPath = FSoftObjectPath(InAsset);
if (OldPath != NewPath)
{
TArray<FAssetRenameData> RenameData;
RenameData.Add(FAssetRenameData(OldPath, NewPath, true));
AssetToolsModule.Get().RenameAssets(RenameData);
}
}
void
FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique)
{
if (!IsValid(InActor))
return;
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
const FSoftObjectPath OldPath = FSoftObjectPath(InActor);
FString NewName;
if (bMakeUniqueIfNotUnique)
NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor);
else
NewName = InNewName;
FHoudiniEngineUtils::RenameObject(InActor, *NewName);
FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName);
const FSoftObjectPath NewPath = FSoftObjectPath(InActor);
if (OldPath != NewPath)
{
TArray<FAssetRenameData> RenameData;
RenameData.Add(FAssetRenameData(OldPath, NewPath, true));
AssetToolsModule.Get().RenameAssets(RenameData);
}
}
bool
FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor(
AActor* InActor,
const FString& InNewName,
const FName& InFolderPath)
{
if (!IsValid(InActor))
{
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null."));
return false;
}
if (InNewName.TrimStartAndEnd().IsEmpty())
{
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified."));
return false;
}
// Detach from parent
InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
// Rename
const bool bMakeUniqueIfNotUnique = true;
RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique);
InActor->SetFolderPath(InFolderPath);
return true;
}
bool
FHoudiniEngineBakeUtils::BakePDGWorkResultObject(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNode* InNode,
int32 InWorkResultArrayIndex,
int32 InWorkResultObjectArrayIndex,
bool bInReplaceActors,
bool bInReplaceAssets,
bool bInBakeToWorkResultActor,
bool bInIsAutoBake,
TArray<FHoudiniEngineBakedActor>& OutBakedActors,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats,
TArray<EHoudiniOutputType> const* InOutputTypesToBake,
TArray<EHoudiniInstancerComponentType> const* InInstancerComponentTypesToBake,
const FString& InFallbackWorldOutlinerFolder)
{
if (!IsValid(InPDGAssetLink))
return false;
if (!IsValid(InNode))
return false;
if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex))
return false;
FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex];
if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex))
return false;
FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex];
TArray<UHoudiniOutput*>& Outputs = WorkResultObject.GetResultOutputs();
if (Outputs.Num() == 0)
return true;
if (WorkResultObject.State != EPDGWorkResultState::Loaded)
{
if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad())
{
HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name);
return true;
}
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name);
return false;
}
AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor();
if (!IsValid(WorkResultObjectActor))
{
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name);
return false;
}
// OutBakedActors contains each actor that contains baked PDG results. Actors may
// appear in the array more than once if they have more than one baked result/component associated with
// them
// TArray<FHoudiniEngineBakedActor> BakedActorsForWorkResultObject;
const int32 NumBakedPre = OutBakedActors.Num();
const FString HoudiniAssetName(WorkResultObject.Name);
// Find the previous bake output for this work result object
FString Key;
InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key);
FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key);
const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink);
check(IsValid(HoudiniAssetComponent));
BakeHoudiniOutputsToActors(
HoudiniAssetComponent,
Outputs,
BakedOutputContainer.BakedOutputs,
HoudiniAssetName,
WorkResultObjectActor->GetActorTransform(),
InPDGAssetLink->BakeFolder,
InPDGAssetLink->GetTemporaryCookFolder(),
bInReplaceActors,
bInReplaceAssets,
OutBakedActors,
OutPackagesToSave,
OutBakeStats,
InOutputTypesToBake,
InInstancerComponentTypesToBake,
bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr,
InFallbackWorldOutlinerFolder);
// Set the PDG indices on the output baked actor entries
FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner();
AActor* const WROActor = OutputActorOwner.GetOutputActor();
FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr;
const int32 NumBakedPost = OutBakedActors.Num();
if (NumBakedPost > NumBakedPre)
{
for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index)
{
FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index];
BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex;
BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex;
BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex;
if (WROActor && BakedActorEntry.Actor == WROActor)
{
BakedWROActorEntry = &BakedActorEntry;
}
}
}
// If anything was baked to WorkResultObjectActor, detach it from its parent
if (bInBakeToWorkResultActor)
{
// if we re-used the temp actor as a bake actor, then remove its temp outputs
WorkResultObject.DestroyResultOutputs();
if (WROActor)
{
if (BakedWROActorEntry)
{
OutputActorOwner.SetOutputActor(nullptr);
const FString OldActorPath = FSoftObjectPath(WROActor).ToString();
DetachAndRenameBakedPDGOutputActor(
WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder);
const FString NewActorPath = FSoftObjectPath(WROActor).ToString();
if (OldActorPath != NewActorPath)
{
// Fix cached string reference in baked outputs to WROActor
for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs)
{
for (auto& Entry : BakedOutput.BakedOutputObjects)
{
if (Entry.Value.Actor == OldActorPath)
Entry.Value.Actor = NewActorPath;
}
}
}
}
else
{
OutputActorOwner.DestroyOutputActor();
}
}
}
if (bInIsAutoBake)
WorkResultObject.SetAutoBakedSinceLastLoad(true);
// OutBakedActors.Append(BakedActorsForWorkResultObject);
return true;
}
void
FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNode* InNode,
int32 InWorkItemHAPIIndex,
int32 InWorkItemResultInfoIndex)
{
if (!IsValid(InPDGAssetLink))
return;
if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded)
return;
if (!IsValid(InNode))
return;
// Check if the node is ready for baking: all work items must be complete
bool bDoNotBake = false;
bool bPendingBakeItems = false;
if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed())
bDoNotBake = true;
// Check if the node is ready for baking: all work items must be loaded
if (!bDoNotBake)
{
for (const FTOPWorkResult& WorkResult : InNode->WorkResult)
{
for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects)
{
if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad())
{
bDoNotBake = true;
break;
}
}
if (bDoNotBake)
break;
}
}
if (!bDoNotBake)
{
// Check which outputs are selected for baking: selected node, selected network or all
// And only bake if the node falls within the criteria
UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork();
UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode();
switch (InPDGAssetLink->PDGBakeSelectionOption)
{
case EPDGBakeSelectionOption::SelectedNetwork:
if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork))
{
HOUDINI_LOG_WARNING(
TEXT("Not baking Node %s (Net %s): not in selected network"),
InNode ? *InNode->GetName() : TEXT(""),
SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT(""));
bDoNotBake = true;
}
break;
case EPDGBakeSelectionOption::SelectedNode:
if (InNode != SelectedTOPNode)
{
HOUDINI_LOG_WARNING(
TEXT("Not baking Node %s (Net %s): not the selected node"),
InNode ? *InNode->GetName() : TEXT(""),
SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT(""));
bDoNotBake = true;
}
break;
case EPDGBakeSelectionOption::All:
default:
break;
}
}
// If there are no nodes left to auto-bake, broadcast the onpostbake delegate
if (bDoNotBake && !InPDGAssetLink->AnyRemainingAutoBakeNodes())
InPDGAssetLink->HandleOnPostBake(true);
if (bDoNotBake)
return;
bool bSuccess = false;
const bool bIsAutoBake = true;
switch (InPDGAssetLink->HoudiniEngineBakeOption)
{
case EHoudiniEngineBakeOption::ToActor:
bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors);
break;
case EHoudiniEngineBakeOption::ToBlueprint:
bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors);
break;
default:
HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption);
}
if (!InPDGAssetLink->AnyRemainingAutoBakeNodes())
InPDGAssetLink->HandleOnPostBake(bSuccess);
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNode* InNode,
bool bInBakeForBlueprint,
bool bInIsAutoBake,
const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode,
TArray<FHoudiniEngineBakedActor>& OutBakedActors,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats)
{
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
if (!IsValid(InNode))
return false;
// Determine the output world outliner folder path via the PDG asset link's
// owner's folder path and name
UObject* PDGOwner = InPDGAssetLink->GetOwnerActor();
if (!PDGOwner)
PDGOwner = InPDGAssetLink->GetOuter();
const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner);
// Determine the actor/package replacement settings
const bool bReplaceActors = !bInBakeForBlueprint && InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets;
const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets;
// Determine the output types to bake: don't bake landscapes in blueprint baking mode
TArray<EHoudiniOutputType> OutputTypesToBake;
TArray<EHoudiniInstancerComponentType> InstancerComponentTypesToBake;
if (bInBakeForBlueprint)
{
OutputTypesToBake.Add(EHoudiniOutputType::Mesh);
OutputTypesToBake.Add(EHoudiniOutputType::Instancer);
OutputTypesToBake.Add(EHoudiniOutputType::Curve);
InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent);
InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent);
InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent);
InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent);
}
const int32 NumWorkResults = InNode->WorkResult.Num();
FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName())));
Progress.MakeDialog();
for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx)
{
FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx];
const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num();
for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx)
{
Progress.EnterProgressFrame(1.0f);
BakePDGWorkResultObject(
InPDGAssetLink,
InNode,
WorkResultArrayIdx,
WorkResultObjectArrayIdx,
bReplaceActors,
bReplaceAssets,
!bInBakeForBlueprint,
bInIsAutoBake,
OutBakedActors,
OutPackagesToSave,
OutBakeStats,
OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr,
InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr,
FallbackWorldOutlinerFolderPath.ToString()
);
}
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors)
{
TArray<UPackage*> PackagesToSave;
FHoudiniEngineOutputStats BakeStats;
TArray<FHoudiniEngineBakedActor> BakedActors;
const bool bBakeBlueprints = false;
bool bSuccess = BakePDGTOPNodeOutputsKeepActors(
InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats);
SaveBakedPackages(PackagesToSave);
// Recenter and select the baked actors
if (GEditor && BakedActors.Num() > 0)
GEditor->SelectNone(false, true);
for (const FHoudiniEngineBakedActor& Entry : BakedActors)
{
if (!IsValid(Entry.Actor))
continue;
if (bInRecenterBakedActors)
CenterActorToBoundingBoxCenter(Entry.Actor);
if (GEditor)
GEditor->SelectActor(Entry.Actor, true, false);
}
if (GEditor && BakedActors.Num() > 0)
GEditor->NoteSelectionChange();
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNetwork* InNetwork,
bool bInBakeForBlueprint,
bool bInIsAutoBake,
const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode,
TArray<FHoudiniEngineBakedActor>& BakedActors,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats)
{
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
if (!IsValid(InNetwork))
return false;
bool bSuccess = true;
for (UTOPNode* Node : InNetwork->AllTOPNodes)
{
if (!IsValid(Node))
continue;
bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats);
}
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors)
{
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
TArray<UPackage*> PackagesToSave;
FHoudiniEngineOutputStats BakeStats;
TArray<FHoudiniEngineBakedActor> BakedActors;
const bool bBakeBlueprints = false;
const bool bIsAutoBake = false;
bool bSuccess = true;
switch(InBakeSelectionOption)
{
case EPDGBakeSelectionOption::All:
for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks)
{
if (!IsValid(Network))
continue;
for (UTOPNode* Node : Network->AllTOPNodes)
{
if (!IsValid(Node))
continue;
bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats);
}
}
break;
case EPDGBakeSelectionOption::SelectedNetwork:
bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats);
case EPDGBakeSelectionOption::SelectedNode:
bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats);
}
SaveBakedPackages(PackagesToSave);
// Recenter and select the baked actors
if (GEditor && BakedActors.Num() > 0)
GEditor->SelectNone(false, true);
for (const FHoudiniEngineBakedActor& Entry : BakedActors)
{
if (!IsValid(Entry.Actor))
continue;
if (bInRecenterBakedActors)
CenterActorToBoundingBoxCenter(Entry.Actor);
if (GEditor)
GEditor->SelectActor(Entry.Actor, true, false);
}
if (GEditor && BakedActors.Num() > 0)
GEditor->NoteSelectionChange();
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
// Broadcast that the bake is complete
InPDGAssetLink->HandleOnPostBake(bSuccess);
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors(
const TArray<FHoudiniEngineBakedActor>& InBakedActors,
bool bInRecenterBakedActors,
bool bInReplaceAssets,
const FString& InAssetName,
const FDirectoryPath& InBakeFolder,
TArray<FHoudiniBakedOutput>* const InNonPDGBakedOutputs,
TMap<FString, FHoudiniPDGWorkResultObjectBakedOutput>* const InPDGBakedOutputs,
TArray<UBlueprint*>& OutBlueprints,
TArray<UPackage*>& OutPackagesToSave)
{
// // Clear selection
// if (GEditor)
// {
// GEditor->SelectNone(false, true);
// GEditor->NoteSelectionChange();
// }
// Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were
// baked to the same actor, so keep track of actors we have already processed in BakedActorSet
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem);
TArray<UObject*> AssetsToReOpenEditors;
TSet<AActor*> BakedActorSet;
for (const FHoudiniEngineBakedActor& Entry : InBakedActors)
{
AActor *Actor = Entry.Actor;
if (!Actor || Actor->IsPendingKill())
continue;
if (BakedActorSet.Contains(Actor))
continue;
BakedActorSet.Add(Actor);
UObject* Asset = nullptr;
// Recenter the actor to its bounding box center
if (bInRecenterBakedActors)
CenterActorToBoundingBoxCenter(Actor);
// Create package for out Blueprint
FString BlueprintName;
// For instancers we determine the bake folder from the instancer,
// for everything else we use the baked object's bake folder
// If all of that is blank, we fall back to InBakeFolder.
FString BakeFolderPath;
if (Entry.bInstancerOutput)
BakeFolderPath = Entry.InstancerPackageParams.BakeFolder;
else
BakeFolderPath = Entry.BakeFolderPath;
if (BakeFolderPath.IsEmpty())
BakeFolderPath = InBakeFolder.Path;
FHoudiniPackageParams PackageParams;
// Set the replace mode based on if we are doing a replacement or incremental asset bake
const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ?
EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets;
FHoudiniEngineUtils::FillInPackageParamsForBakingOutput(
PackageParams,
FHoudiniOutputObjectIdentifier(),
BakeFolderPath,
Entry.ActorBakeName.ToString() + "_BP",
InAssetName,
AssetPackageReplaceMode);
// If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment
// is consistent with the bake counter
int32 BakeCounter = 0;
UBlueprint* InPreviousBlueprint = nullptr;
FHoudiniBakedOutputObject* BakedOutputObject = nullptr;
FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr;
// Get the baked output object
if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs)
{
const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex);
WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key);
if (WorkResultObjectBakedOutput)
{
if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex))
{
BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier);
}
}
}
else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs)
{
if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex))
{
BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier);
}
}
if (BakedOutputObject)
{
InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid();
if (IsValid(InPreviousBlueprint))
{
if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint))
{
PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter);
}
}
}
UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter);
if (!Package || Package->IsPendingKill())
{
HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName()));
continue;
}
if (!Package->IsFullyLoaded())
Package->FullyLoad();
//Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false);
// Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a
// different base class than the incoming actor, we reparent the blueprint to the new base class before
// clearing the SCS graph and repopulating it from the temp actor.
Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName));
if (IsValid(Asset))
{
UBlueprint* Blueprint = Cast<UBlueprint>(Asset);
if (IsValid(Blueprint))
{
if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass())
{
// Close editors opened on existing asset if applicable
if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(Asset);
AssetsToReOpenEditors.Add(Asset);
}
Blueprint->ParentClass = Actor->GetClass();
FBlueprintEditorUtils::RefreshAllNodes(Blueprint);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
FKismetEditorUtilities::CompileBlueprint(Blueprint);
}
}
}
else if (Asset && Asset->IsPendingKill())
{
// Rename to pending kill so that we can use the desired name
const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL");
// Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString());
RenameAsset(Asset, AssetPendingKillName, true);
Asset = nullptr;
}
if (!Asset)
{
UBlueprintFactory* Factory = NewObject<UBlueprintFactory>();
Factory->ParentClass = Actor->GetClass();
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
Asset = AssetToolsModule.Get().CreateAsset(
BlueprintName, PackageParams.GetPackagePath(),
UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset"));
}
UBlueprint* Blueprint = Cast<UBlueprint>(Asset);
if (!Blueprint || Blueprint->IsPendingKill())
{
HOUDINI_LOG_WARNING(
TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."),
*(InBakeFolder.Path), *BlueprintName);
continue;
}
// Close editors opened on existing asset if applicable
if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr)
{
AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint);
AssetsToReOpenEditors.Add(Blueprint);
}
// Record the blueprint as the previous bake blueprint
if (BakedOutputObject)
BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString();
OutBlueprints.Add(Blueprint);
// Clear old Blueprint Node tree
{
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
int32 NodeSize = SCS->GetAllNodes().Num();
for (int32 n = NodeSize - 1; n >= 0; --n)
SCS->RemoveNode(SCS->GetAllNodes()[n]);
}
FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint);
UWorld* World = Actor->GetWorld();
if (!World)
World = GWorld;
World->EditorDestroyActor(Actor, true);
// Save the created BP package.
Package->MarkPackageDirty();
OutPackagesToSave.Add(Package);
}
// Re-open asset editors for updated blueprints that were open in editors
if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0)
{
for (UObject* Asset : AssetsToReOpenEditors)
{
if (IsValid(Asset))
{
AssetEditorSubsystem->OpenEditorForAsset(Asset);
}
}
}
return true;
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNode* InNode,
bool bInIsAutoBake,
const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode,
bool bInRecenterBakedActors,
TArray<UBlueprint*>& OutBlueprints,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats)
{
TArray<AActor*> BPActors;
if (!IsValid(InPDGAssetLink))
{
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null"));
return false;
}
if (!IsValid(InNode))
{
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null"));
return false;
}
const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets;
// Bake PDG output to new actors
// bInBakeForBlueprint == true will skip landscapes and instanced actor components
const bool bInBakeForBlueprint = true;
TArray<FHoudiniEngineBakedActor> BakedActors;
bool bSuccess = BakePDGTOPNodeOutputsKeepActors(
InPDGAssetLink,
InNode,
bInBakeForBlueprint,
bInIsAutoBake,
InPDGBakePackageReplaceMode,
BakedActors,
OutPackagesToSave,
OutBakeStats
);
if (bSuccess)
{
bSuccess = BakeBlueprintsFromBakedActors(
BakedActors,
bInRecenterBakedActors,
bReplaceAssets,
InPDGAssetLink->AssetName,
InPDGAssetLink->BakeFolder,
nullptr,
&InNode->GetBakedWorkResultObjectsOutputs(),
OutBlueprints,
OutPackagesToSave);
}
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors)
{
TArray<UBlueprint*> Blueprints;
TArray<UPackage*> PackagesToSave;
FHoudiniEngineOutputStats BakeStats;
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
const bool bSuccess = BakePDGTOPNodeBlueprints(
InPDGAssetLink,
InTOPNode,
bInIsAutoBake,
InPDGBakePackageReplaceMode,
bInRecenterBakedActors,
Blueprints,
PackagesToSave,
BakeStats);
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Sync the CB to the baked objects
if(GEditor && Blueprints.Num() > 0)
{
TArray<UObject*> Assets;
Assets.Reserve(Blueprints.Num());
for (UBlueprint* Blueprint : Blueprints)
{
Assets.Add(Blueprint);
}
GEditor->SyncBrowserToObjects(Assets);
}
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints(
UHoudiniPDGAssetLink* InPDGAssetLink,
UTOPNetwork* InNetwork,
const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode,
bool bInRecenterBakedActors,
TArray<UBlueprint*>& OutBlueprints,
TArray<UPackage*>& OutPackagesToSave,
FHoudiniEngineOutputStats& OutBakeStats)
{
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
if (!IsValid(InNetwork))
return false;
const bool bIsAutoBake = false;
bool bSuccess = true;
for (UTOPNode* Node : InNetwork->AllTOPNodes)
{
if (!IsValid(Node))
continue;
bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, OutBlueprints, OutPackagesToSave, OutBakeStats);
}
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors)
{
TArray<UBlueprint*> Blueprints;
TArray<UPackage*> PackagesToSave;
FHoudiniEngineOutputStats BakeStats;
if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill())
return false;
const bool bIsAutoBake = false;
bool bSuccess = true;
switch(InBakeSelectionOption)
{
case EPDGBakeSelectionOption::All:
for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks)
{
if (!IsValid(Network))
continue;
for (UTOPNode* Node : Network->AllTOPNodes)
{
if (!IsValid(Node))
continue;
bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats);
}
}
break;
case EPDGBakeSelectionOption::SelectedNetwork:
bSuccess &= BakePDGTOPNetworkBlueprints(
InPDGAssetLink,
InPDGAssetLink->GetSelectedTOPNetwork(),
InPDGBakePackageReplaceMode,
bInRecenterBakedActors,
Blueprints,
PackagesToSave,
BakeStats);
case EPDGBakeSelectionOption::SelectedNode:
bSuccess &= BakePDGTOPNodeBlueprints(
InPDGAssetLink,
InPDGAssetLink->GetSelectedTOPNode(),
bIsAutoBake,
InPDGBakePackageReplaceMode,
bInRecenterBakedActors,
Blueprints,
PackagesToSave,
BakeStats);
}
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
// Sync the CB to the baked objects
if(GEditor && Blueprints.Num() > 0)
{
TArray<UObject*> Assets;
Assets.Reserve(Blueprints.Num());
for (UBlueprint* Blueprint : Blueprints)
{
Assets.Add(Blueprint);
}
GEditor->SyncBrowserToObjects(Assets);
}
{
const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages.");
FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } );
FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) );
}
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
// Broadcast that the bake is complete
InPDGAssetLink->HandleOnPostBake(bSuccess);
return bSuccess;
}
bool
FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath(
const FString& InLevelPath,
ULevel*& OutDesiredLevel,
UWorld*& OutDesiredWorld,
bool& OutCreatedPackage)
{
OutDesiredLevel = nullptr;
OutDesiredWorld = nullptr;
if (InLevelPath.IsEmpty())
{
OutDesiredWorld = GWorld;
OutDesiredLevel = GWorld->GetCurrentLevel();
}
else
{
OutCreatedPackage = false;
UWorld* FoundWorld = nullptr;
ULevel* FoundLevel = nullptr;
bool bActorInWorld = false;
if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning(
GWorld,
InLevelPath,
true,
FoundWorld,
FoundLevel,
OutCreatedPackage,
bActorInWorld))
{
OutDesiredLevel = FoundLevel;
OutDesiredWorld = FoundWorld;
}
}
return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr));
}
bool
FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName(
const FString& InBakeActorName,
ULevel* InLevel,
AActor*& OutActor,
bool bInNoPendingKillActors,
bool bRenamePendingKillActor)
{
OutActor = nullptr;
if (!IsValid(InLevel))
return false;
UWorld* const World = InLevel->GetWorld();
if (!IsValid(World))
return false;
// Look for an actor with the given name in the world
const FName BakeActorFName(InBakeActorName);
AActor* FoundActor = Cast<AActor>(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName));
// for (TActorIterator<AActor> Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter)
// {
// AActor* const Actor = *Iter;
// if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel)
// {
// // Found the actor
// FoundActor = Actor;
// break;
// }
// }
// If we found an actor and it is pending kill, rename it and don't use it
if (FoundActor)
{
if (FoundActor->IsPendingKill())
{
if (bRenamePendingKillActor)
{
// FoundActor->Rename(
// *MakeUniqueObjectNameIfNeeded(
// FoundActor->GetOuter(),
// FoundActor->GetClass(),
// FName(FoundActor->GetName() + "_Pending_Kill")).ToString());
RenameAndRelabelActor(
FoundActor,
*MakeUniqueObjectNameIfNeeded(
FoundActor->GetOuter(),
FoundActor->GetClass(),
FoundActor->GetName() + "_Pending_Kill",
FoundActor),
false);
}
if (bInNoPendingKillActors)
FoundActor = nullptr;
else
OutActor = FoundActor;
}
else
{
OutActor = FoundActor;
}
}
return true;
}
bool FHoudiniEngineBakeUtils::FindUnrealBakeActor(
const FHoudiniOutputObject& InOutputObject,
const FHoudiniBakedOutputObject& InBakedOutputObject,
const TArray<FHoudiniEngineBakedActor>& InAllBakedActors,
ULevel* InLevel,
FName InDefaultActorName,
bool bInReplaceActorBakeMode,
AActor* InFallbackActor,
AActor*& OutFoundActor,
bool& bOutHasBakeActorName,
FName& OutBakeActorName)
{
// Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName
OutBakeActorName = NAME_None;
OutFoundActor = nullptr;
bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR);
if (bOutHasBakeActorName)
{
const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR];
if (BakeActorNameStr.IsEmpty())
{
OutBakeActorName = NAME_None;
bOutHasBakeActorName = false;
}
else
{
OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL);
// We have a bake actor name, look for the actor
AActor* BakeNameActor = nullptr;
if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor))
{
// Found an actor with that name, check that we "own" it (we created in during baking previously)
AActor* IncrementedBakedActor = nullptr;
for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors)
{
if (!IsValid(BakedActor.Actor))
continue;
if (BakedActor.Actor == BakeNameActor)
{
OutFoundActor = BakeNameActor;
break;
}
else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName)
{
// Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name)
IncrementedBakedActor = BakedActor.Actor;
}
}
if (!OutFoundActor && IncrementedBakedActor)
OutFoundActor = IncrementedBakedActor;
}
}
}
// If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName
if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty()))
OutBakeActorName = InDefaultActorName;
if (!OutFoundActor)
{
// If in replace mode, use previous bake actor if valid and in InLevel
if (bInReplaceActorBakeMode)
{
const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor);
const FString ActorPath = PrevActorPath.IsSubobject()
? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString()
: PrevActorPath.GetAssetPathString();
const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : "";
if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath)))
OutFoundActor = InBakedOutputObject.GetActorIfValid();
}
// Fallback to InFallbackActor if valid and in InLevel
if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel))
OutFoundActor = InFallbackActor;
}
return true;
}
AActor*
FHoudiniEngineBakeUtils::FindExistingActor_Bake(
UWorld* InWorld,
UHoudiniOutput* InOutput,
const FString& InActorName,
const FString& InPackagePath,
UWorld*& OutWorld,
ULevel*& OutLevel,
bool& bCreatedPackage)
{
bCreatedPackage = false;
// Try to Locate a previous actor
AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor<AActor>(InWorld, InActorName, FoundActor);
if (FoundActor)
FoundActor->Destroy(); // nuke it!
if (FoundActor)
{
// TODO: make sure that the found is actor is actually assigned to the level defined by package path.
// If the found actor is not from that level, it should be moved there.
OutWorld = FoundActor->GetWorld();
OutLevel = FoundActor->GetLevel();
}
else
{
// Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning.
bool bActorInWorld = false;
const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning(
InWorld,
InPackagePath,
true,
OutWorld,
OutLevel,
bCreatedPackage,
bActorInWorld);
if (!bResult)
{
return nullptr;
}
if (!bActorInWorld)
{
// The OutLevel is not present in the current world which means we might
// still find the tile actor in OutWorld.
FoundActor = FHoudiniEngineUtils::FindActorInWorld<AActor>(OutWorld, FName(InActorName));
}
}
return FoundActor;
}
bool
FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh(
UHoudiniAssetComponent* InHoudiniAssetComponent,
bool bInReplacePreviousBake,
EHoudiniEngineBakeOption InBakeOption,
bool bInRemoveHACOutputOnSuccess,
bool bInRecenterBakedActors,
bool& bOutNeedsReCook)
{
if (!IsValid(InHoudiniAssetComponent))
{
return false;
}
// Handle proxies: if the output has any current proxies, first refine them
bOutNeedsReCook = false;
if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput())
{
bool bNeedsRebuildOrDelete;
bool bInvalidState;
const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState);
if (bCookedDataAvailable)
{
// Cook data is available, refine the mesh
AHoudiniAssetActor* HoudiniActor = Cast<AHoudiniAssetActor>(InHoudiniAssetComponent->GetOwner());
if (IsValid(HoudiniActor))
{
FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor });
}
}
else if (!bNeedsRebuildOrDelete && !bInvalidState)
{
// A cook is needed: request the cook, but with no proxy and with a bake after cook
InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true);
// Only
if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound())
{
InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors](UHoudiniAssetComponent* InHAC) {
return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors);
});
}
InHoudiniAssetComponent->MarkAsNeedCook();
bOutNeedsReCook = true;
// The cook has to complete first (asynchronously) before the bake can happen
// The SetBakeAfterNextCookEnabled flag will result in a bake after cook
return false;
}
else
{
// The HAC is in an unsupported state
const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState();
HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState)));
return false;
}
}
return true;
}
void
FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor)
{
if (!IsValid(InActor))
return;
USceneComponent * const RootComponent = InActor->GetRootComponent();
if (!IsValid(RootComponent))
return;
// If the root component does not have any child components, then there is nothing to recenter
if (RootComponent->GetNumChildrenComponents() <= 0)
return;
const bool bOnlyCollidingComponents = false;
const bool bIncludeFromChildActors = true;
FVector Origin;
FVector BoxExtent;
// InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors);
FBox Box(ForceInit);
InActor->ForEachComponent<UPrimitiveComponent>(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp)
{
// Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components)
if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() &&
(!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled()))
{
Box += InPrimComp->Bounds.GetBox();
}
});
Box.GetCenterAndExtents(Origin, BoxExtent);
const FVector Delta = Origin - RootComponent->GetComponentLocation();
// Actor->SetActorLocation(Origin);
RootComponent->SetWorldLocation(Origin);
for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren())
{
if (!IsValid(SceneComponent))
continue;
SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta);
}
}
void
FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray<AActor*>& InActors)
{
for (AActor* Actor : InActors)
{
if (!IsValid(Actor))
continue;
CenterActorToBoundingBoxCenter(Actor);
}
}
USceneComponent*
FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated)
{
USceneComponent* RootComponent = InActor->GetRootComponent();
if (!IsValid(RootComponent))
{
RootComponent = NewObject<USceneComponent>(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional);
// Change the creation method so the component is listed in the details panels
InActor->SetRootComponent(RootComponent);
InActor->AddInstanceComponent(RootComponent);
RootComponent->RegisterComponent();
RootComponent->SetMobility(InMobilityIfCreated);
}
return RootComponent;
}
FString
FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed)
{
if (IsValid(InObjectThatWouldBeRenamed))
{
const FName CurrentName = InObjectThatWouldBeRenamed->GetFName();
if (CurrentName.ToString() == InName)
return InName;
// Check if the prefix matches (without counter suffix) the new name
// In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then
// don't we can just keep the current name
if (CurrentName.GetPlainNameString() == InName)
return CurrentName.ToString();
}
UObject* ExistingObject = nullptr;
FName CandidateName(InName);
bool bAppendedNumber = false;
// Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can
// revert to MakeUniqueObjectName.
// return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString();
do
{
if (InOuter == ANY_PACKAGE)
{
ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString()));
}
else
{
ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName);
}
if (ExistingObject)
{
if (!bAppendedNumber)
{
const bool bSplitName = false;
CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName);
bAppendedNumber = true;
}
else
{
CandidateName.SetNumber(CandidateName.GetNumber() + 1);
}
// CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter);
}
} while (ExistingObject);
return CandidateName.ToString();
}
FName
FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder)
{
const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER);
if (FolderPathPtr && !FolderPathPtr->IsEmpty())
return FName(*FolderPathPtr);
else
return InDefaultFolder;
}
bool
FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder)
{
if (!IsValid(InActor))
return false;
InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder));
return true;
}
uint32
FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput(
FHoudiniBakedOutputObject& InBakedOutputObject,
bool bInDestroyBakedComponent,
bool bInDestroyBakedInstancedActors,
bool bInDestroyBakedInstancedComponents)
{
uint32 NumDeleted = 0;
if (bInDestroyBakedComponent)
{
UActorComponent* Component = Cast<UActorComponent>(InBakedOutputObject.GetBakedComponentIfValid());
if (Component)
{
if (RemovePreviouslyBakedComponent(Component))
{
InBakedOutputObject.BakedComponent = nullptr;
NumDeleted++;
}
}
}
if (bInDestroyBakedInstancedActors)
{
for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors)
{
const FSoftObjectPath ActorPath(ActorPathStr);
if (!ActorPath.IsValid())
continue;
AActor* Actor = Cast<AActor>(ActorPath.TryLoad());
if (IsValid(Actor))
{
UWorld* World = Actor->GetWorld();
if (IsValid(World))
{
#if WITH_EDITOR
World->EditorDestroyActor(Actor, true);
#else
World->DestroyActor(Actor);
#endif
NumDeleted++;
}
}
}
InBakedOutputObject.InstancedActors.Empty();
}
if (bInDestroyBakedInstancedComponents)
{
for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents)
{
const FSoftObjectPath ComponentPath(ComponentPathStr);
if (!ComponentPath.IsValid())
continue;
UActorComponent* Component = Cast<UActorComponent>(ComponentPath.TryLoad());
if (IsValid(Component))
{
if (RemovePreviouslyBakedComponent(Component))
NumDeleted++;
}
}
InBakedOutputObject.InstancedComponents.Empty();
}
return NumDeleted;
}
UMaterialInterface* FHoudiniEngineBakeUtils::BakeSingleMaterialToPackage(UMaterialInterface* InOriginalMaterial,
const FHoudiniPackageParams & InPackageParams,
TArray<UPackage*>& OutPackagesToSave,
TMap<UMaterialInterface *, UMaterialInterface *>& InOutAlreadyBakedMaterialsMap)
{
if (!InOriginalMaterial || InOriginalMaterial->IsPendingKill())
{
return nullptr;
}
// We only deal with materials.
if (!InOriginalMaterial->IsA(UMaterial::StaticClass()) && !InOriginalMaterial->IsA(UMaterialInstance::StaticClass()))
{
return nullptr;
}
FString MaterialName = InOriginalMaterial->GetName();
// Duplicate material resource.
UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage(
InOriginalMaterial, nullptr, MaterialName, InPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap);
if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill())
return nullptr;
return DuplicatedMaterial;
}
#undef LOCTEXT_NAMESPACE