/* * Copyright (c) <2017> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include "HoudiniEngineBakeUtils.h" #include "HoudiniApi.h" #include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniEngineUtils.h" #include "HoudiniRuntimeSettings.h" #include "HoudiniAssetActor.h" #include "HoudiniAssetComponent.h" #include "HoudiniEngine.h" #include "HoudiniAsset.h" #include "HoudiniEngineString.h" #include "HoudiniInstancedActorComponent.h" #include "HoudiniMeshSplitInstancerComponent.h" #include "HoudiniAssetInstanceInputField.h" #include "CoreMinimal.h" #include "Engine/StaticMesh.h" #include "Engine/StaticMeshActor.h" #include "Materials/Material.h" #if WITH_EDITOR #include "ActorFactories/ActorFactory.h" #include "Editor.h" #include "Factories/MaterialFactoryNew.h" #include "ActorFactories/ActorFactoryStaticMesh.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "FileHelpers.h" #include "Materials/Material.h" #include "Materials/MaterialInstance.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Materials/MaterialExpressionTextureCoordinate.h" #include "StaticMeshResources.h" #include "InstancedFoliage.h" #include "InstancedFoliageActor.h" #include "Layers/LayersSubsystem.h" #endif #include "EngineUtils.h" #include "UObject/MetaData.h" #include "PhysicsEngine/BodySetup.h" #include "Components/InstancedStaticMeshComponent.h" #if PLATFORM_WINDOWS #include "Windows/WindowsHWrapper.h" // Of course, Windows defines its own GetGeoInfo, // So we need to undefine that before including HoudiniApi.h to avoid collision... #ifdef GetGeoInfo #undef GetGeoInfo #endif #endif #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE UPackage * FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( UHoudiniAssetComponent * HoudiniAssetComponent, FString & BlueprintName ) { UPackage * Package = nullptr; #if WITH_EDITOR if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return nullptr; FString HoudiniAssetName; if ( HoudiniAssetComponent->HoudiniAsset ) HoudiniAssetName = HoudiniAssetComponent->HoudiniAsset->GetName(); else if ( HoudiniAssetComponent->GetOuter() ) HoudiniAssetName = HoudiniAssetComponent->GetOuter()->GetName(); else HoudiniAssetName = HoudiniAssetComponent->GetName(); 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. BlueprintName = HoudiniAssetName + TEXT( "_" ) + BakeGUIDString; // Generate unique package name. FString PackageName = HoudiniAssetComponent->GetBakeFolder().ToString() + TEXT( "/" ) + BlueprintName; PackageName = UPackageTools::SanitizePackageName( PackageName ); // See if package exists, if it does, we need to regenerate the name. 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 ); } #endif return Package; } UStaticMesh * FHoudiniEngineBakeUtils::BakeStaticMesh( UHoudiniAssetComponent * HoudiniAssetComponent, const FHoudiniGeoPartObject & HoudiniGeoPartObject, UStaticMesh * InStaticMesh ) { UStaticMesh * StaticMesh = nullptr; #if WITH_EDITOR if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return nullptr; UHoudiniAsset * HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) return nullptr; // We cannot bake curves. if( HoudiniGeoPartObject.IsCurve() ) return nullptr; if( HoudiniGeoPartObject.IsInstancer() ) { HOUDINI_LOG_MESSAGE( TEXT( "Baking of instanced static meshes is not supported at the moment." ) ); return nullptr; } // Get platform manager LOD specific information. ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); check( CurrentPlatform ); const FStaticMeshLODGroup & LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup( NAME_None ); int32 NumLODs = LODGroup.GetDefaultNumLODs(); // Get runtime settings. const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); check( HoudiniRuntimeSettings ); FHoudiniCookParams HoudiniCookParams( HoudiniAssetComponent ); HoudiniCookParams.StaticMeshBakeMode = EBakeMode::CreateNewAssets; FString MeshName; FGuid BakeGUID; UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( HoudiniCookParams, HoudiniGeoPartObject, MeshName, BakeGUID ); if( !Package || Package->IsPendingKill() ) return nullptr; // Create static mesh. StaticMesh = NewObject< UStaticMesh >( Package, FName( *MeshName ), RF_Public | RF_Transactional ); if ( !StaticMesh || StaticMesh->IsPendingKill() ) return nullptr; // Add meta information to this package. FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( Package, StaticMesh, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT( "true" ) ); FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( Package, StaticMesh, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MeshName ); // Notify registry that we created a new asset. FAssetRegistryModule::AssetCreated( StaticMesh ); // Copy materials. StaticMesh->StaticMaterials = InStaticMesh->StaticMaterials; // Create new source model for current static mesh. if (!StaticMesh->GetNumSourceModels()) StaticMesh->AddSourceModel(); FStaticMeshSourceModel * SrcModel = &StaticMesh->GetSourceModel(0); // Load raw data bytes. FRawMesh RawMesh; FStaticMeshSourceModel * InSrcModel = &InStaticMesh->GetSourceModel(0); InSrcModel->LoadRawMesh( RawMesh ); // Some mesh generation settings. HoudiniRuntimeSettings->SetMeshBuildSettings( SrcModel->BuildSettings, RawMesh ); // Setting the DistanceField resolution SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniAssetComponent->GeneratedDistanceFieldResolutionScale; // We need to check light map uv set for correctness. Unreal seems to have occasional issues with // zero UV sets when building lightmaps. if( SrcModel->BuildSettings.bGenerateLightmapUVs ) { // See if we need to disable lightmap generation because of bad UVs. if( FHoudiniEngineUtils::ContainsInvalidLightmapFaces( RawMesh, StaticMesh->LightMapCoordinateIndex ) ) { SrcModel->BuildSettings.bGenerateLightmapUVs = false; HOUDINI_LOG_MESSAGE( TEXT( "Skipping Lightmap Generation: Object %s " ) TEXT( "- skipping." ), *MeshName ); } } // Store the new raw mesh. SrcModel->StaticMeshOwner = StaticMesh; SrcModel->SaveRawMesh( RawMesh ); while (StaticMesh->GetNumSourceModels() < NumLODs) StaticMesh->AddSourceModel(); for( int32 ModelLODIndex = 0; ModelLODIndex < NumLODs; ++ModelLODIndex ) { StaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); for( int32 MaterialIndex = 0; MaterialIndex < StaticMesh->StaticMaterials.Num(); ++MaterialIndex ) { FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get( ModelLODIndex, MaterialIndex ); Info.MaterialIndex = MaterialIndex; Info.bEnableCollision = true; Info.bCastShadow = true; StaticMesh->GetSectionInfoMap().Set( ModelLODIndex, MaterialIndex, Info ); } } // Assign generation parameters for this static mesh. HoudiniAssetComponent->SetStaticMeshGenerationParameters( StaticMesh ); // Copy custom lightmap resolution if it is set. if( InStaticMesh->LightMapResolution != StaticMesh->LightMapResolution ) StaticMesh->LightMapResolution = InStaticMesh->LightMapResolution; if( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() ) { UBodySetup * BodySetup = StaticMesh->BodySetup; if (BodySetup && !BodySetup->IsPendingKill()) { // Enable collisions for this static mesh. BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; } } FHoudiniScopedGlobalSilence ScopedGlobalSilence; StaticMesh->Build( true ); StaticMesh->MarkPackageDirty(); #endif return StaticMesh; } UBlueprint * FHoudiniEngineBakeUtils::BakeBlueprint( UHoudiniAssetComponent * HoudiniAssetComponent ) { UBlueprint * Blueprint = nullptr; if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return nullptr; #if WITH_EDITOR // Create package for our Blueprint. FString BlueprintName = TEXT( "" ); UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( HoudiniAssetComponent, BlueprintName ); //Bake the asset's landscape BakeLandscape(HoudiniAssetComponent); if( Package && !Package->IsPendingKill() ) { AActor * Actor = HoudiniAssetComponent->CloneComponentsAndCreateActor(); if( Actor && !Actor->IsPendingKill() ) { Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor( *BlueprintName, Package, Actor, false ); // If actor is rooted, unroot it. We can also delete intermediate actor. Actor->RemoveFromRoot(); Actor->ConditionalBeginDestroy(); if( Blueprint && !Blueprint->IsPendingKill() ) FAssetRegistryModule::AssetCreated( Blueprint ); } } #endif return Blueprint; } AActor * FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint( UHoudiniAssetComponent * HoudiniAssetComponent ) { AActor * Actor = nullptr; if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return nullptr; #if WITH_EDITOR // Create package for our Blueprint. FString BlueprintName = TEXT( "" ); UPackage * Package = FHoudiniEngineBakeUtils::BakeCreateBlueprintPackageForComponent( HoudiniAssetComponent, BlueprintName ); if (!Package || Package->IsPendingKill()) return nullptr; //Bake the asset's landscape BakeLandscape( HoudiniAssetComponent ); AActor * ClonedActor = HoudiniAssetComponent->CloneComponentsAndCreateActor(); if (!ClonedActor || ClonedActor->IsPendingKill()) return nullptr; UBlueprint * Blueprint = FKismetEditorUtilities::CreateBlueprint( ClonedActor->GetClass(), Package, *BlueprintName, EBlueprintType::BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName( "CreateFromActor" ) ); if( Blueprint && !Blueprint->IsPendingKill() ) { Package->MarkPackageDirty(); if( ClonedActor->GetInstanceComponents().Num() > 0 ) FKismetEditorUtilities::AddComponentsToBlueprint( Blueprint, ClonedActor->GetInstanceComponents() ); if( Blueprint->GeneratedClass ) { AActor * CDO = Cast< AActor >( Blueprint->GeneratedClass->GetDefaultObject() ); if (!CDO || CDO->IsPendingKill()) return nullptr; const auto CopyOptions = ( EditorUtilities::ECopyOptions::Type ) ( EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ); EditorUtilities::CopyActorProperties( ClonedActor, 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->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); 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(); } } // Compile our blueprint and notify asset system about blueprint. FKismetEditorUtilities::CompileBlueprint( Blueprint ); FAssetRegistryModule::AssetCreated( Blueprint ); // Retrieve actor transform. FVector Location = ClonedActor->GetActorLocation(); FRotator Rotator = ClonedActor->GetActorRotation(); // Replace cloned actor with Blueprint instance. { TArray< AActor * > Actors; Actors.Add( ClonedActor ); ClonedActor->RemoveFromRoot(); Actor = FKismetEditorUtilities::CreateBlueprintInstanceFromSelection( Blueprint, Actors, Location, Rotator ); } // We can initiate Houdini actor deletion. DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); } else { ClonedActor->RemoveFromRoot(); ClonedActor->ConditionalBeginDestroy(); } #endif return Actor; } UStaticMesh * FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( const UStaticMesh * StaticMesh, UHoudiniAssetComponent * Component, const FHoudiniGeoPartObject & HoudiniGeoPartObject, EBakeMode BakeMode ) { UStaticMesh * DuplicatedStaticMesh = nullptr; #if WITH_EDITOR if( !HoudiniGeoPartObject.IsCurve() && !HoudiniGeoPartObject.IsInstancer() && !HoudiniGeoPartObject.IsPackedPrimitiveInstancer() && !HoudiniGeoPartObject.IsVolume() ) { // Create package for this duplicated mesh. FHoudiniCookParams HoudiniCookParams( Component ); // Transferring the CookMode to the materials // We're either using the default one, or a custom one HoudiniCookParams.StaticMeshBakeMode = BakeMode; if( BakeMode == FHoudiniCookParams::GetDefaultStaticMeshesCookMode() ) HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode(); else HoudiniCookParams.MaterialAndTextureBakeMode = BakeMode; FString MeshName; FGuid MeshGuid; UPackage * MeshPackage = FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( HoudiniCookParams, HoudiniGeoPartObject, MeshName, MeshGuid ); if( !MeshPackage || MeshPackage->IsPendingKill() ) return nullptr; // 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(); } } // Duplicate mesh for this new copied component. DuplicatedStaticMesh = DuplicateObject< UStaticMesh >( StaticMesh, MeshPackage, *MeshName ); if ( !DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill() ) return nullptr; if( BakeMode != EBakeMode::Intermediate ) DuplicatedStaticMesh->SetFlags( RF_Public | RF_Standalone ); // 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, *MeshName ); // 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 ) { UPackage * MaterialPackage = Cast< UPackage >( MaterialInterface->GetOuter() ); if( MaterialPackage && !MaterialPackage->IsPendingKill() ) { FString MaterialName; if( FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( MaterialPackage, MaterialInterface, MaterialName ) ) { // We only deal with materials. UMaterial * Material = Cast< UMaterial >( MaterialInterface ); if( Material && !Material->IsPendingKill() ) { // Duplicate material resource. UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( Material, HoudiniCookParams, MaterialName ); if( !DuplicatedMaterial || DuplicatedMaterial->IsPendingKill() ) continue; // Store duplicated material. FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; DuplicatedMaterials.Add( DupeStaticMaterial ); continue; } } } } DuplicatedMaterials.Add( Materials[MaterialIdx] ); } /* // 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(); } } */ // Assign duplicated materials. DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; // Notify registry that we have created a new duplicate mesh. FAssetRegistryModule::AssetCreated( DuplicatedStaticMesh ); // Dirty the static mesh package. DuplicatedStaticMesh->MarkPackageDirty(); } #endif return DuplicatedStaticMesh; } bool FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(UHoudiniAssetComponent * HoudiniAssetComponent, bool SelectNewActors) { bool bSuccess = false; #if WITH_EDITOR if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, SelectNewActors)) { bSuccess = FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); } #endif return bSuccess; } bool FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( UHoudiniAssetComponent * HoudiniAssetComponent, bool SelectNewActors ) { bool bSuccess = false; #if WITH_EDITOR if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return bSuccess; const FScopedTransaction Transaction( LOCTEXT( "BakeToActors", "Bake To Actors" ) ); auto SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); TArray< AActor* > NewActors = BakeHoudiniActorToActors_StaticMeshes( HoudiniAssetComponent, SMComponentToPart ); auto IAComponentToPart = HoudiniAssetComponent->CollectAllInstancedActorComponents(); NewActors.Append( BakeHoudiniActorToActors_InstancedActors( HoudiniAssetComponent, IAComponentToPart ) ); auto SplitMeshInstancerComponentToPart = HoudiniAssetComponent->CollectAllMeshSplitInstancerComponents(); NewActors.Append( BakeHoudiniActorToActors_SplitMeshInstancers( HoudiniAssetComponent, SplitMeshInstancerComponentToPart ) ); bSuccess = NewActors.Num() > 0; if( GEditor && SelectNewActors && bSuccess ) { GEditor->SelectNone( false, true ); for( AActor* NewActor : NewActors ) { if ( NewActor && !NewActor->IsPendingKill() ) GEditor->SelectActor( NewActor, true, false ); } GEditor->NoteSelectionChange(); } #endif return bSuccess; } TArray< AActor* > FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_InstancedActors( UHoudiniAssetComponent * HoudiniAssetComponent, TMap< const UHoudiniInstancedActorComponent*, FHoudiniGeoPartObject >& ComponentToPart ) { TArray< AActor* > NewActors; if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return NewActors; #if WITH_EDITOR ULevel* DesiredLevel = GWorld->GetCurrentLevel(); FName BaseName( *( HoudiniAssetComponent->GetOwner()->GetName() + TEXT( "_Baked" ) ) ); for( const auto& Iter : ComponentToPart ) { const UHoudiniInstancedActorComponent * OtherSMC = Iter.Key; if ( !OtherSMC || OtherSMC->IsPendingKill() ) continue; for( AActor* InstActor : OtherSMC->Instances ) { if ( !InstActor || InstActor->IsPendingKill() ) continue; FName NewName = MakeUniqueObjectName( DesiredLevel, OtherSMC->InstancedAsset->StaticClass(), BaseName ); FString NewNameStr = NewName.ToString(); FTransform CurrentTransform = InstActor->GetTransform(); AActor* NewActor = OtherSMC->SpawnInstancedActor(CurrentTransform); if( NewActor && !NewActor->IsPendingKill() ) { NewActor->SetActorLabel( NewNameStr ); NewActor->SetFolderPath( BaseName ); NewActor->SetActorTransform( CurrentTransform ); NewActors.Add(NewActor); } } } #endif return NewActors; } void FHoudiniEngineBakeUtils::CheckedBakeStaticMesh( UHoudiniAssetComponent* HoudiniAssetComponent, TMap< const UStaticMesh*, UStaticMesh* >& OriginalToBakedMesh, const FHoudiniGeoPartObject & HoudiniGeoPartObject, UStaticMesh* OriginalSM) { UStaticMesh* BakedSM = nullptr; if( UStaticMesh ** FoundMeshPtr = OriginalToBakedMesh.Find(OriginalSM) ) { // We've already baked this mesh, use it BakedSM = *FoundMeshPtr; } else { if( FHoudiniEngineBakeUtils::StaticMeshRequiresBake(OriginalSM) ) { // Bake the found mesh into the project BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( OriginalSM, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::CreateNewAssets); if( ensure(BakedSM) ) { FAssetRegistryModule::AssetCreated(BakedSM); } } else { // We didn't bake this mesh, but it's already baked so we will just use it as is BakedSM = OriginalSM; } OriginalToBakedMesh.Add(OriginalSM, BakedSM); } } TArray< AActor* > FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_StaticMeshes( UHoudiniAssetComponent * HoudiniAssetComponent, TMap< const UStaticMeshComponent*, FHoudiniGeoPartObject >& SMComponentToPart ) { TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; TArray< AActor* > NewActors; if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return NewActors; #if WITH_EDITOR // Loop over all comps, bake static mesh if not already baked, and create an actor for every one of them for( const auto& Iter : SMComponentToPart ) { const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; const UStaticMeshComponent * OtherSMC = Iter.Key; if ( !OtherSMC || OtherSMC->IsPendingKill() ) continue; UStaticMesh * OtherSM = OtherSMC->GetStaticMesh(); if( !OtherSM || OtherSM->IsPendingKill() ) continue; CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, HoudiniGeoPartObject, OtherSM); } // Finished baking, now spawn the actors for( const auto& Iter : SMComponentToPart ) { const UStaticMeshComponent * OtherSMC = Iter.Key; if ( !OtherSMC || OtherSMC->IsPendingKill() ) continue; const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; UStaticMesh* BakedSM = OriginalToBakedMesh[OtherSMC->GetStaticMesh()]; if( !BakedSM || BakedSM->IsPendingKill() ) continue; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); FName BaseName( *( HoudiniAssetComponent->GetOwner()->GetName() + TEXT( "_Baked" ) ) ); UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass( UActorFactoryStaticMesh::StaticClass() ) : nullptr; if (!Factory) continue; auto PrepNewStaticMeshActor = [&]( AActor* NewActor ) { if ( !NewActor || NewActor->IsPendingKill() ) return; // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset FName NewName = MakeUniqueObjectName( DesiredLevel, Factory->NewActorClass, BaseName ); FString NewNameStr = NewName.ToString(); NewActor->Rename( *NewNameStr ); NewActor->SetActorLabel( NewNameStr ); NewActor->SetFolderPath( BaseName ); // Copy properties to new actor AStaticMeshActor* SMActor = Cast< AStaticMeshActor>(NewActor); if ( !SMActor || SMActor->IsPendingKill() ) return; UStaticMeshComponent* SMC = SMActor->GetStaticMeshComponent(); if (!SMC || SMC->IsPendingKill()) return; UStaticMeshComponent* OtherSMC_NonConst = const_cast( OtherSMC ); SMC->SetCollisionProfileName( OtherSMC_NonConst->GetCollisionProfileName() ); SMC->SetCollisionEnabled( OtherSMC->GetCollisionEnabled() ); SMC->LightmassSettings = OtherSMC->LightmassSettings; SMC->CastShadow = OtherSMC->CastShadow; SMC->SetMobility( OtherSMC->Mobility ); if (OtherSMC_NonConst->GetBodySetup() && SMC->GetBodySetup()) { // Copy the BodySetup SMC->GetBodySetup()->CopyBodyPropertiesFrom(OtherSMC_NonConst->GetBodySetup()); // We need to recreate the physics mesh for the new body setup SMC->GetBodySetup()->InvalidatePhysicsData(); SMC->GetBodySetup()->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 && OtherSMC_NonConst->GetBodySetup()->GetPhysMaterial() != GEngine->DefaultPhysMaterial) SMC->SetPhysMaterialOverride(OtherSMC_NonConst->GetBodySetup()->GetPhysMaterial()); } SMActor->SetActorHiddenInGame( OtherSMC->bHiddenInGame ); SMC->SetVisibility( OtherSMC->IsVisible() ); // Reapply the uproperties modified by attributes on the new component FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( SMC, HoudiniGeoPartObject ); SMC->PostEditChange(); }; const UInstancedStaticMeshComponent* OtherISMC = Cast< const UInstancedStaticMeshComponent>(OtherSMC); if( OtherISMC && !OtherISMC->IsPendingKill() ) { #ifdef BAKE_TO_INSTANCEDSTATICMESHCOMPONENT_ACTORS // This is an instanced static mesh component - we will create a generic AActor with a UInstancedStaticMeshComponent root FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = DesiredLevel; SpawnInfo.ObjectFlags = RF_Transactional; SpawnInfo.Name = MakeUniqueObjectName( DesiredLevel, AActor::StaticClass(), BaseName ); SpawnInfo.bDeferConstruction = true; if( AActor* NewActor = DesiredLevel->OwningWorld->SpawnActor( SpawnInfo ) ) { NewActor->SetActorLabel( NewActor->GetName() ); NewActor->SetActorHiddenInGame( OtherISMC->bHiddenInGame ); // Do we need to create a HISMC? const UHierarchicalInstancedStaticMeshComponent* OtherHISMC = Cast< const UHierarchicalInstancedStaticMeshComponent>( OtherSMC ); UInstancedStaticMeshComponent* NewISMC = nullptr; if ( OtherHISMC ) NewISMC = DuplicateObject< UHierarchicalInstancedStaticMeshComponent >(OtherHISMC, NewActor, *OtherHISMC->GetName() ) ); else NewISMC = DuplicateObject< UInstancedStaticMeshComponent >( OtherISMC, NewActor, *OtherISMC->GetName() ) ); if( NewISMC ) { NewISMC->SetupAttachment( nullptr ); NewISMC->SetStaticMesh( BakedSM ); NewActor->AddInstanceComponent( NewISMC ); NewActor->SetRootComponent( NewISMC ); NewISMC->SetWorldTransform( OtherISMC->GetComponentTransform() ); NewISMC->RegisterComponent(); NewActor->SetFolderPath( BaseName ); NewActor->FinishSpawning( OtherISMC->GetComponentTransform() ); NewActors.Add( NewActor ); NewActor->InvalidateLightingCache(); NewActor->PostEditMove( true ); NewActor->MarkPackageDirty(); } } #else // This is an instanced static mesh component - we will split it up into StaticMeshActors for( int32 InstanceIx = 0; InstanceIx < OtherISMC->GetInstanceCount(); ++InstanceIx ) { FTransform InstanceTransform; OtherISMC->GetInstanceTransform( InstanceIx, InstanceTransform, true ); AActor* NewActor = Factory->CreateActor(BakedSM, DesiredLevel, InstanceTransform, RF_Transactional); if (!NewActor || NewActor->IsPendingKill()) continue; PrepNewStaticMeshActor( NewActor ); // We need to set the modified uproperty on the created actor AStaticMeshActor* SMActor = Cast< AStaticMeshActor>(NewActor); if ( !SMActor || SMActor->IsPendingKill() ) continue; UStaticMeshComponent* SMC = SMActor->GetStaticMeshComponent(); if ( !SMC || SMC->IsPendingKill() ) continue; FHoudiniGeoPartObject GeoPartObject = HoudiniAssetComponent->LocateGeoPartObject( OtherSMC->GetStaticMesh() ); // Set the part id to 0 so we can access the instancer GeoPartObject.PartId = 0; FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( SMC, GeoPartObject ); NewActors.Add(NewActor); } #endif } else { AActor* NewActor = Factory->CreateActor(BakedSM, DesiredLevel, OtherSMC->GetComponentTransform(), RF_Transactional); if( NewActor && !NewActor->IsPendingKill() ) { PrepNewStaticMeshActor( NewActor ); NewActors.Add(NewActor); } } } #endif return NewActors; } TArray FHoudiniEngineBakeUtils::BakeHoudiniActorToActors_SplitMeshInstancers(UHoudiniAssetComponent * HoudiniAssetComponent, TMap SplitMeshInstancerComponentToPart) { TArray< AActor* > NewActors; if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return NewActors; #if WITH_EDITOR TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; for( auto&& Iter : SplitMeshInstancerComponentToPart ) { auto&& HoudiniGeoPartObject = Iter.Value; auto&& OtherMSIC = Iter.Key; if ( !OtherMSIC || OtherMSIC->IsPendingKill() ) continue; UStaticMesh * OtherSM = OtherMSIC->GetStaticMesh(); if( !OtherSM || OtherSM->IsPendingKill() ) continue; CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, HoudiniGeoPartObject, OtherSM); } // Done baking, now spawn the actors for( auto&& Iter : SplitMeshInstancerComponentToPart ) { auto&& OtherMSIC = Iter.Key; UStaticMesh* BakedSM = OriginalToBakedMesh[OtherMSIC->GetStaticMesh()]; if( !BakedSM || BakedSM->IsPendingKill() ) continue; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); FName BaseName(*( HoudiniAssetComponent->GetOwner()->GetName() + TEXT("_Baked") )); // 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 = MakeUniqueObjectName(DesiredLevel, AActor::StaticClass(), BaseName); SpawnInfo.bDeferConstruction = true; AActor* NewActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); if (!NewActor || NewActor->IsPendingKill()) continue; NewActor->SetActorLabel(NewActor->GetName()); NewActor->SetActorHiddenInGame(OtherMSIC->bHiddenInGame); for( UStaticMeshComponent* OtherSMC : OtherMSIC->GetInstances() ) { if ( !OtherSMC || OtherSMC->IsPendingKill() ) continue; UStaticMeshComponent* NewSMC = DuplicateObject< UStaticMeshComponent >(OtherSMC, NewActor, *OtherSMC->GetName()); if (!NewSMC || NewSMC->IsPendingKill()) continue; NewSMC->SetupAttachment(nullptr); NewSMC->SetStaticMesh(BakedSM); NewActor->AddInstanceComponent(NewSMC); NewSMC->SetWorldTransform(OtherSMC->GetComponentTransform()); NewSMC->RegisterComponent(); } NewActor->SetFolderPath(BaseName); NewActor->FinishSpawning(OtherMSIC->GetComponentTransform()); NewActors.Add(NewActor); NewActor->InvalidateLightingCache(); NewActor->PostEditMove(true); NewActor->MarkPackageDirty(); } #endif return NewActors; } UHoudiniAssetInput* FHoudiniEngineBakeUtils::GetInputForBakeHoudiniActorToOutlinerInput( const UHoudiniAssetComponent * HoudiniAssetComponent ) { if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return nullptr; UHoudiniAssetInput *FirstInput = nullptr, *Result = nullptr; #if WITH_EDITOR if( HoudiniAssetComponent->GetInputs().Num() && HoudiniAssetComponent->GetInputs()[0] ) { FirstInput = HoudiniAssetComponent->GetInputs()[0]; } else { TMap< FString, UHoudiniAssetParameter* > InputParams; HoudiniAssetComponent->CollectAllParametersOfType( UHoudiniAssetInput::StaticClass(), InputParams ); TArray< UHoudiniAssetParameter* > InputParamsArray; InputParams.GenerateValueArray( InputParamsArray ); if( InputParamsArray.Num() > 0 ) { FirstInput = Cast( InputParamsArray[0] ); } } if( FirstInput && !FirstInput->IsPendingKill() ) { if( FirstInput->GetChoiceIndex() == EHoudiniAssetInputType::WorldInput && FirstInput->GetWorldOutlinerInputs().Num() == 1 ) { const FHoudiniAssetInputOutlinerMesh& InputOutlinerMesh = FirstInput->GetWorldOutlinerInputs()[0]; if( InputOutlinerMesh.ActorPtr.IsValid() && InputOutlinerMesh.StaticMeshComponent ) { Result = FirstInput; } } } #endif return Result; } bool FHoudiniEngineBakeUtils::CanComponentBakeToOutlinerInput( const UHoudiniAssetComponent * HoudiniAssetComponent ) { if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return false; #if WITH_EDITOR // TODO: Cache this auto SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); if( SMComponentToPart.Num() == 1 ) { return nullptr != GetInputForBakeHoudiniActorToOutlinerInput( HoudiniAssetComponent ); } #endif return false; } bool FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(const UHoudiniAssetComponent * HoudiniAssetComponent) { if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return false; const TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields = HoudiniAssetComponent->GetAllInstanceInputFields(); return InstanceInputFields.Num() > 0; } void FHoudiniEngineBakeUtils::BakeHoudiniActorToOutlinerInput( UHoudiniAssetComponent * HoudiniAssetComponent ) { if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) return; #if WITH_EDITOR TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; TMap< const UStaticMeshComponent*, FHoudiniGeoPartObject > SMComponentToPart = HoudiniAssetComponent->CollectAllStaticMeshComponents(); for( const auto& Iter : SMComponentToPart ) { const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Value; const UStaticMeshComponent * OtherSMC = Iter.Key; if ( !OtherSMC || OtherSMC->IsPendingKill() ) continue; UStaticMesh* OtherSM = OtherSMC->GetStaticMesh(); if( !OtherSM || OtherSM->IsPendingKill() ) continue; UStaticMesh* BakedSM = nullptr; if( UStaticMesh ** FoundMeshPtr = OriginalToBakedMesh.Find( OtherSM ) ) { // We've already baked this mesh, use it BakedSM = *FoundMeshPtr; } else { // Bake the found mesh into the project BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( OtherSM, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::CreateNewAssets ); if( BakedSM ) { OriginalToBakedMesh.Add(OtherSM, BakedSM ); FAssetRegistryModule::AssetCreated( BakedSM ); } } } { const FScopedTransaction Transaction( LOCTEXT( "BakeToInput", "Bake To Input" ) ); for( auto Iter : OriginalToBakedMesh ) { // Get the first outliner input UHoudiniAssetInput* FirstInput = GetInputForBakeHoudiniActorToOutlinerInput(HoudiniAssetComponent); if ( !FirstInput || FirstInput->IsPendingKill() ) continue; const FHoudiniAssetInputOutlinerMesh& InputOutlinerMesh = FirstInput->GetWorldOutlinerInputs()[0]; if( InputOutlinerMesh.ActorPtr.IsValid() && InputOutlinerMesh.StaticMeshComponent ) { UStaticMeshComponent* InOutSMC = InputOutlinerMesh.StaticMeshComponent; if ( InOutSMC && !InOutSMC->IsPendingKill() ) { InputOutlinerMesh.ActorPtr->Modify(); InOutSMC->SetStaticMesh(Iter.Value); InOutSMC->InvalidateLightingCache(); InOutSMC->MarkPackageDirty(); } // Disconnect the input from the asset - InputOutlinerMesh now garbage FirstInput->RemoveWorldOutlinerInput( 0 ); } // Only handle the first Baked Mesh break; } } #endif } // Bakes output instanced meshes to the level's foliage actor and removes the Houdini actor bool FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage( UHoudiniAssetComponent * HoudiniAssetComponent ) { bool bSuccess = false; #if WITH_EDITOR if ( FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(HoudiniAssetComponent) ) { bSuccess = FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); } #endif return bSuccess; } bool FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent * HoudiniAssetComponent ) { #if WITH_EDITOR if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return false; const FScopedTransaction Transaction(LOCTEXT("BakeToFoliage", "Bake To Foliage")); ULevel* DesiredLevel = GWorld->GetCurrentLevel(); AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) return false; // Map storing original and baked Static Meshes TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; int32 BakedCount = 0; const TArray< UHoudiniAssetInstanceInputField * > InstanceInputFields = HoudiniAssetComponent->GetAllInstanceInputFields(); for ( int32 Idx = 0; Idx < InstanceInputFields.Num(); ++Idx ) { const UHoudiniAssetInstanceInputField * HoudiniAssetInstanceInputField = InstanceInputFields[ Idx ]; if ( !HoudiniAssetInstanceInputField || HoudiniAssetInstanceInputField->IsPendingKill() ) continue; for ( int32 VariationIdx = 0; VariationIdx < HoudiniAssetInstanceInputField->InstanceVariationCount(); VariationIdx++ ) { UInstancedStaticMeshComponent * ISMC = Cast( HoudiniAssetInstanceInputField->GetInstancedComponent( VariationIdx ) ); if (!ISMC || ISMC->IsPendingKill()) continue; // If the original static mesh is used (the cooked mesh for HAPI), then we need to bake it to a file // If not, we don't need to bake a new mesh as we're using a SM override, so can use the existing unreal asset UStaticMesh* OutStaticMesh = Cast( HoudiniAssetInstanceInputField->GetInstanceVariation( VariationIdx ) ); if ( HoudiniAssetInstanceInputField->IsOriginalObjectUsed( VariationIdx ) ) { UStaticMesh* OriginalSM = Cast(HoudiniAssetInstanceInputField->GetOriginalObject()); if (!OriginalSM || OriginalSM->IsPendingKill()) continue; // We're instancing a mesh created by the plugin, we need to bake it first auto&& ItemGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(OutStaticMesh); FHoudiniEngineBakeUtils::CheckedBakeStaticMesh(HoudiniAssetComponent, OriginalToBakedMesh, ItemGeoPartObject, OriginalSM); OutStaticMesh = OriginalToBakedMesh[ OutStaticMesh ]; } // See if we already have a FoliageType for that mesh UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); if ( !FoliageType || FoliageType->IsPendingKill() ) { // We need to create a new FoliageType for this Static Mesh InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType, HoudiniAssetComponent->GeneratedFoliageDefaultSettings); } // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); if ( !FoliageInfo ) continue; // Get the transforms for this instance TArray< FTransform > ProcessedTransforms; HoudiniAssetInstanceInputField->GetProcessedTransforms(ProcessedTransforms, VariationIdx); FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); FFoliageInstance FoliageInstance; for (auto CurrentTransform : ProcessedTransforms) { FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); } // 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(ProcessedTransforms.Num()) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); FHoudiniEngineUtils::CreateSlateNotification(Notification); BakedCount += ProcessedTransforms.Num(); } } if (BakedCount > 0) return true; #endif return false; } bool FHoudiniEngineBakeUtils::StaticMeshRequiresBake( const UStaticMesh * StaticMesh ) { #if WITH_EDITOR if( !StaticMesh || StaticMesh->IsPendingKill() ) return false; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); FAssetData BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath( *StaticMesh->GetPathName() ); if( !BackingAssetData.IsUAsset() ) return true; for( const auto& StaticMaterial : StaticMesh->StaticMaterials ) { if( !StaticMaterial.MaterialInterface || StaticMaterial.MaterialInterface->IsPendingKill() ) continue; BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*StaticMaterial.MaterialInterface->GetPathName()); if (!BackingAssetData.IsUAsset()) return true; } #endif return false; } UMaterial * FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( UMaterial * Material, FHoudiniCookParams& HoudiniCookParams, const FString & SubMaterialName ) { UMaterial * DuplicatedMaterial = nullptr; #if WITH_EDITOR // Create material package. FString MaterialName; UPackage * MaterialPackage = FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( HoudiniCookParams, SubMaterialName, MaterialName ); if( !MaterialPackage || MaterialPackage->IsPendingKill() ) return nullptr; // Clone material. DuplicatedMaterial = DuplicateObject< UMaterial >( Material, MaterialPackage, *MaterialName ); if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) return nullptr; if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) DuplicatedMaterial->SetFlags( RF_Public | RF_Standalone ); // 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, *MaterialName ); // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. for( auto& Expression : DuplicatedMaterial->Expressions ) { FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( Expression, HoudiniCookParams ); } // Notify registry that we have created a new duplicate material. FAssetRegistryModule::AssetCreated( DuplicatedMaterial ); // Dirty the material package. DuplicatedMaterial->MarkPackageDirty(); // Reset any derived state DuplicatedMaterial->ForceRecompileForRendering(); #endif return DuplicatedMaterial; } void FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, FHoudiniCookParams& HoudiniCookParams ) { #if WITH_EDITOR 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; FString GeneratedTextureName; if( FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( TexturePackage, Texture, GeneratedTextureName ) ) { // Duplicate texture. UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( Texture, HoudiniCookParams, GeneratedTextureName ); // Re-assign generated texture. TextureSample->Texture = DuplicatedTexture; } #endif } UTexture2D * FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( UTexture2D * Texture, FHoudiniCookParams& HoudiniCookParams, const FString & SubTextureName ) { 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 ); // Create texture package. FString TextureName; UPackage * NewTexturePackage = FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( HoudiniCookParams, SubTextureName, TextureName ); if( !NewTexturePackage || NewTexturePackage->IsPendingKill() ) return nullptr; // Clone texture. DuplicatedTexture = DuplicateObject< UTexture2D >( Texture, NewTexturePackage, *TextureName ); if ( !DuplicatedTexture || DuplicatedTexture->IsPendingKill() ) return nullptr; if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) DuplicatedTexture->SetFlags( RF_Public | RF_Standalone ); // 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, *TextureName ); 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(); } #endif return DuplicatedTexture; } FHoudiniCookParams::FHoudiniCookParams( UHoudiniAssetComponent* HoudiniAssetComponent ) { #if WITH_EDITOR HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); HoudiniCookManager = HoudiniAssetComponent; PackageGUID = HoudiniAssetComponent->GetComponentGuid(); BakedStaticMeshPackagesForParts = &HoudiniAssetComponent->BakedStaticMeshPackagesForParts; BakedMaterialPackagesForIds = &HoudiniAssetComponent->BakedMaterialPackagesForIds; CookedTemporaryStaticMeshPackages = &HoudiniAssetComponent->CookedTemporaryStaticMeshPackages; CookedTemporaryPackages = &HoudiniAssetComponent->CookedTemporaryPackages; CookedTemporaryLandscapeLayers = &HoudiniAssetComponent->CookedTemporaryLandscapeLayers; TempCookFolder = HoudiniAssetComponent->GetTempCookFolder(); BakeFolder = HoudiniAssetComponent->GetBakeFolder(); BakeNameOverrides = &HoudiniAssetComponent->BakeNameOverrides; IntermediateOuter = HoudiniAssetComponent->GetComponentLevel(); GeneratedDistanceFieldResolutionScale = HoudiniAssetComponent->GeneratedDistanceFieldResolutionScale; #endif } bool FHoudiniEngineBakeUtils::BakeLandscape( UHoudiniAssetComponent* HoudiniAssetComponent, ALandscapeProxy * OnlyBakeThisLandscape ) { #if WITH_EDITOR if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() ) return false; if ( !HoudiniAssetComponent->HasLandscape() ) return false; TMap< FHoudiniGeoPartObject, TWeakObjectPtr> * LandscapeComponentsPtr = HoudiniAssetComponent->GetLandscapeComponents(); if ( !LandscapeComponentsPtr ) return false; TArray LayerPackages; bool bNeedToUpdateProperties = false; for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr >::TIterator Iter(* LandscapeComponentsPtr ); Iter; ++Iter) { ALandscapeProxy * CurrentLandscape = Iter.Value().Get(); if ( !CurrentLandscape || CurrentLandscape->IsPendingKill() || !CurrentLandscape->IsValidLowLevel() ) continue; // If we only want to bake a single landscape if ( OnlyBakeThisLandscape && CurrentLandscape != OnlyBakeThisLandscape ) continue; // Simply remove the landscape from the map FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key(); LandscapeComponentsPtr->Remove( HoudiniGeoPartObject ); CurrentLandscape->DetachFromActor( FDetachmentTransformRules::KeepWorldTransform ); // And save its layers to prevent them from being removed for ( TMap< TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject > ::TIterator IterPackage( HoudiniAssetComponent->CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage ) { if ( !( HoudiniGeoPartObject == IterPackage.Value() ) ) continue; UPackage * Package = IterPackage.Key().Get(); if ( Package && !Package->IsPendingKill() ) LayerPackages.Add( Package ); } bNeedToUpdateProperties = true; // If we only wanted to bake a single landscape, we're done if ( OnlyBakeThisLandscape ) break; } if ( LayerPackages.Num() > 0 ) { // Save the layer info's package FEditorFileUtils::PromptForCheckoutAndSave( LayerPackages, true, false ); // Remove the packages from the asset component, or the asset component might // destroy them when it is being destroyed for ( int32 n = 0; n < LayerPackages.Num(); n++ ) HoudiniAssetComponent->CookedTemporaryLandscapeLayers.Remove( LayerPackages[n] ); } return bNeedToUpdateProperties; #else return false; #endif } UPackage * FHoudiniEngineBakeUtils::BakeCreateMaterialPackageForComponent( FHoudiniCookParams& HoudiniCookParams, const HAPI_MaterialInfo & MaterialInfo, FString & MaterialName ) { UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; if ( !HoudiniAsset || HoudiniAsset->IsPendingKill() ) return nullptr; FString MaterialDescriptor; if( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) MaterialDescriptor = HoudiniAsset->GetName() + TEXT( "_material_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ); else MaterialDescriptor = HoudiniAsset->GetName() + TEXT( "_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ); return FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( HoudiniCookParams, MaterialDescriptor, MaterialName ); } UPackage * FHoudiniEngineBakeUtils::BakeCreateTexturePackageForComponent( FHoudiniCookParams& HoudiniCookParams, const HAPI_MaterialInfo & MaterialInfo, const FString & TextureType, FString & TextureName ) { UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) return nullptr; FString TextureInfoDescriptor; if ( HoudiniCookParams.MaterialAndTextureBakeMode != EBakeMode::Intermediate ) { TextureInfoDescriptor = HoudiniAsset->GetName() + TEXT( "_texture_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ) + TextureType + TEXT( "_" ); } else { TextureInfoDescriptor = HoudiniAsset->GetName() + TEXT( "_" ) + FString::FromInt( MaterialInfo.nodeId ) + TEXT( "_" ) + TextureType + TEXT( "_" ); } return FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( HoudiniCookParams, TextureInfoDescriptor, TextureName ); } UPackage * FHoudiniEngineBakeUtils::BakeCreateTextureOrMaterialPackageForComponent( FHoudiniCookParams& HoudiniCookParams, const FString & MaterialInfoDescriptor, FString & MaterialName ) { UPackage * PackageNew = nullptr; #if WITH_EDITOR EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; UHoudiniAsset * HoudiniAsset = HoudiniCookParams.HoudiniAsset; FGuid BakeGUID; FString PackageName; const FGuid & ComponentGUID = HoudiniCookParams.PackageGUID; FString ComponentGUIDString = ComponentGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDComponentNameLength ); if ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) { bool bRemovePackageFromCache = false; UPackage* FoundPackage = nullptr; if (BakeMode == EBakeMode::ReplaceExisitingAssets) { TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.BakedMaterialPackagesForIds->Find(MaterialInfoDescriptor); if ( FoundPointer ) { if ( (*FoundPointer).IsValid() ) FoundPackage = (*FoundPointer).Get(); } else { bRemovePackageFromCache = true; } } else { TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.CookedTemporaryPackages->Find(MaterialInfoDescriptor); if (FoundPointer) { if ( (*FoundPointer).IsValid() ) FoundPackage = (*FoundPointer).Get(); } else { bRemovePackageFromCache = true; } } // Find a previously baked / cooked asset if ( FoundPackage && !FoundPackage->IsPendingKill() ) { if ( UPackage::IsEmptyPackage( FoundPackage ) ) { // This happens when the prior baked output gets renamed, we can delete this // orphaned package so that we can re-use the name FoundPackage->ClearFlags( RF_Standalone ); FoundPackage->ConditionalBeginDestroy(); bRemovePackageFromCache = true; } else { if ( CheckPackageSafeForBake( FoundPackage, MaterialName ) && !MaterialName.IsEmpty() ) { return FoundPackage; } else { // Found the package but we can't update it. We already issued an error, but should popup the standard reference error dialog //::ErrorPopup( TEXT( "Baking Failed: Could not overwrite %s, because it is being referenced" ), *(*FoundPackage)->GetPathName() ); // If we're cooking, we'll create a new package, if baking, fail if ( BakeMode != EBakeMode::CookToTemp ) return nullptr; } } bRemovePackageFromCache = true; } if ( bRemovePackageFromCache ) { // Package is either invalid / not found so we need to remove it from the cache if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) HoudiniCookParams.BakedMaterialPackagesForIds->Remove( MaterialInfoDescriptor ); else HoudiniCookParams.CookedTemporaryPackages->Remove( MaterialInfoDescriptor ); } } while ( true ) { if ( !BakeGUID.IsValid() ) BakeGUID = FGuid::NewGuid(); // We only want half of generated guid string. FString BakeGUIDString = BakeGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDItemNameLength ); // Generate material name. MaterialName = MaterialInfoDescriptor; MaterialName += BakeGUIDString; switch (BakeMode) { case EBakeMode::Intermediate: { // Generate unique package name. PackageName = FPackageName::GetLongPackagePath( HoudiniAsset->GetOuter()->GetName() ) + TEXT("/") + HoudiniAsset->GetName() + TEXT("_") + ComponentGUIDString + TEXT("/") + MaterialName; } break; case EBakeMode::CookToTemp: { PackageName = HoudiniCookParams.TempCookFolder.ToString() + TEXT("/") + MaterialName; } break; default: { // Generate unique package name. PackageName = HoudiniCookParams.BakeFolder.ToString() + TEXT("/") + MaterialName; } break; } // Sanitize package name. PackageName = UPackageTools::SanitizePackageName( PackageName ); UObject * OuterPackage = nullptr; if ( BakeMode == EBakeMode::Intermediate ) { // If we are not baking, then use outermost package, since objects within our package need to be visible // to external operations, such as copy paste. OuterPackage = HoudiniCookParams.IntermediateOuter; } // See if package exists, if it does, we need to regenerate the name. UPackage * Package = FindPackage( OuterPackage, *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. PackageNew = CreatePackage( OuterPackage, *PackageName ); PackageNew->SetPackageFlags(RF_Standalone); break; } } if( PackageNew && !PackageNew->IsPendingKill() && ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) ) { // Add the new package to the cache if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) HoudiniCookParams.BakedMaterialPackagesForIds->Add( MaterialInfoDescriptor, PackageNew ); else HoudiniCookParams.CookedTemporaryPackages->Add( MaterialInfoDescriptor, PackageNew ); } #endif return PackageNew; } bool FHoudiniEngineBakeUtils::CheckPackageSafeForBake( UPackage* Package, FString& FoundAssetName ) { if (!Package || Package->IsPendingKill()) return false; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); TArray AssetsInPackage; AssetRegistryModule.Get().GetAssetsByPackageName( Package->GetFName(), AssetsInPackage ); for( const auto& AssetInfo : AssetsInPackage ) { UObject* AssetInPackage = AssetInfo.GetAsset(); if (!AssetInPackage || AssetInPackage->IsPendingKill()) continue; // Check and see whether we are referenced by any objects that won't be garbage collected (*including* the undo buffer) FReferencerInformationList ReferencesIncludingUndo; bool bReferencedInMemoryOrUndoStack = IsReferenced( AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo ); if( bReferencedInMemoryOrUndoStack ) { // warn HOUDINI_LOG_ERROR( TEXT( "Could not bake to %s because it is being referenced" ), *Package->GetPathName() ); return false; } FoundAssetName = AssetInfo.AssetName.ToString(); } return true; } UPackage * FHoudiniEngineBakeUtils::BakeCreateStaticMeshPackageForComponent( FHoudiniCookParams& HoudiniCookParams, const FHoudiniGeoPartObject & HoudiniGeoPartObject, FString & MeshName, FGuid & BakeGUID ) { UPackage * PackageNew = nullptr; #if WITH_EDITOR EBakeMode BakeMode = HoudiniCookParams.StaticMeshBakeMode; FString PackageName; int32 BakeCount = 0; const FGuid & ComponentGUID = HoudiniCookParams.PackageGUID; FString ComponentGUIDString = ComponentGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDComponentNameLength ); while ( true ) { if( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) { bool bRemovePackageFromCache = false; UPackage* FoundPackage = nullptr; if (BakeMode == EBakeMode::ReplaceExisitingAssets) { TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.BakedStaticMeshPackagesForParts->Find( HoudiniGeoPartObject ); if ( FoundPointer ) { if ( ( *FoundPointer ).IsValid() ) FoundPackage = ( *FoundPointer ).Get(); } else { bRemovePackageFromCache = true; } } else { TWeakObjectPtr< UPackage > * FoundPointer = HoudiniCookParams.CookedTemporaryStaticMeshPackages->Find( HoudiniGeoPartObject ); if ( FoundPointer ) { if ( ( *FoundPointer ).IsValid() ) FoundPackage = ( *FoundPointer ).Get(); } else { bRemovePackageFromCache = true; } } // Find a previously baked / cooked asset if ( FoundPackage && !FoundPackage->IsPendingKill() ) { if ( UPackage::IsEmptyPackage( FoundPackage ) ) { // This happens when the prior baked output gets renamed, we can delete this // orphaned package so that we can re-use the name FoundPackage->ClearFlags( RF_Standalone ); FoundPackage->ConditionalBeginDestroy(); bRemovePackageFromCache = true; } else { if ( FHoudiniEngineBakeUtils::CheckPackageSafeForBake( FoundPackage, MeshName ) && !MeshName.IsEmpty() ) { return FoundPackage; } else { // Found the package but we can't update it. We already issued an error, but should popup the standard reference error dialog //::ErrorPopup( TEXT( "Baking Failed: Could not overwrite %s, because it is being referenced" ), *(*FoundPackage)->GetPathName() ); // If we're cooking, we'll create a new package, if baking, fail if ( BakeMode != EBakeMode::CookToTemp ) return nullptr; } } bRemovePackageFromCache = true; } if ( bRemovePackageFromCache ) { // Package is either invalid / not found so we need to remove it from the cache if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) HoudiniCookParams.BakedStaticMeshPackagesForParts->Remove( HoudiniGeoPartObject ); else HoudiniCookParams.CookedTemporaryStaticMeshPackages->Remove( HoudiniGeoPartObject ); } } if ( !BakeGUID.IsValid() ) BakeGUID = FGuid::NewGuid(); MeshName = HoudiniCookParams.HoudiniCookManager->GetBakingBaseName( HoudiniGeoPartObject ); if( BakeCount > 0 ) { MeshName += FString::Printf( TEXT( "_%02d" ), BakeCount ); } switch ( BakeMode ) { case EBakeMode::Intermediate: { // We only want half of generated guid string. FString BakeGUIDString = BakeGUID.ToString().Left( FHoudiniEngineUtils::PackageGUIDItemNameLength ); MeshName += TEXT("_") + FString::FromInt(HoudiniGeoPartObject.ObjectId) + TEXT("_") + FString::FromInt(HoudiniGeoPartObject.GeoId) + TEXT("_") + FString::FromInt(HoudiniGeoPartObject.PartId) + TEXT("_") + FString::FromInt(HoudiniGeoPartObject.SplitId) + TEXT("_") + HoudiniGeoPartObject.SplitName + TEXT("_") + BakeGUIDString; PackageName = FPackageName::GetLongPackagePath( HoudiniCookParams.HoudiniAsset->GetOuter()->GetName() ) + TEXT("/") + HoudiniCookParams.HoudiniAsset->GetName() + TEXT("_") + ComponentGUIDString + TEXT("/") + MeshName; } break; case EBakeMode::CookToTemp: { PackageName = HoudiniCookParams.TempCookFolder.ToString() + TEXT("/") + MeshName; } break; default: { PackageName = HoudiniCookParams.BakeFolder.ToString() + TEXT("/") + MeshName; } break; } // Santize package name. PackageName = UPackageTools::SanitizePackageName( PackageName ); UObject * OuterPackage = nullptr; if ( BakeMode == EBakeMode::Intermediate ) { // If we are not baking, then use outermost package, since objects within our package need to be visible // to external operations, such as copy paste. OuterPackage = HoudiniCookParams.IntermediateOuter; } // See if package exists, if it does, we need to regenerate the name. UPackage * Package = FindPackage( OuterPackage, *PackageName ); if ( Package && !Package->IsPendingKill() ) { if ( BakeMode != EBakeMode::Intermediate ) { // Increment bake counter BakeCount++; } else { // Package does exist, there's a collision, we need to generate a new name. BakeGUID.Invalidate(); } } else { // Create actual package. PackageNew = CreatePackage( OuterPackage, *PackageName ); break; } } if ( PackageNew && !PackageNew->IsPendingKill() && ( ( BakeMode == EBakeMode::ReplaceExisitingAssets ) || ( BakeMode == EBakeMode::CookToTemp ) ) ) { // Add the new package to the cache if ( BakeMode == EBakeMode::ReplaceExisitingAssets ) HoudiniCookParams.BakedStaticMeshPackagesForParts->Add( HoudiniGeoPartObject, PackageNew ); else HoudiniCookParams.CookedTemporaryStaticMeshPackages->Add( HoudiniGeoPartObject, PackageNew ); } #endif return PackageNew; } 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( NameFull.Len() - FHoudiniEngineUtils::PackageGUIDItemNameLength ); return true; } return false; } bool FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent * HoudiniAssetComponent) { // Helper function used by the replace function to delete the Houdini Asset Actor after it's been baked if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return false; bool bSuccess = false; #if WITH_EDITOR // We can initiate Houdini actor deletion. AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); if (!ActorOwner || ActorOwner->IsPendingKill()) return bSuccess; // Remove Houdini actor from active selection in editor and delete it. if (GEditor) { GEditor->SelectActor(ActorOwner, false, false); ULayersSubsystem* LayerSubSystem = GEditor->GetEditorSubsystem(); if (LayerSubSystem) LayerSubSystem->DisassociateActorFromLayers(ActorOwner); } UWorld * World = ActorOwner->GetWorld(); if (!World) World = GWorld; bSuccess = World->EditorDestroyActor(ActorOwner, false); #endif return bSuccess; }