661 lines
22 KiB
C++
661 lines
22 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 "HoudiniEngineRuntimeUtils.h"
|
|
#include "HoudiniEngineRuntimePrivatePCH.h"
|
|
#include "HoudiniRuntimeSettings.h"
|
|
|
|
#include "EngineUtils.h"
|
|
#include "Engine/EngineTypes.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "Editor.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#endif
|
|
|
|
|
|
FString
|
|
FHoudiniEngineRuntimeUtils::GetLibHAPIName()
|
|
{
|
|
static const FString LibHAPIName =
|
|
|
|
#if PLATFORM_WINDOWS
|
|
HAPI_LIB_OBJECT_WINDOWS;
|
|
#elif PLATFORM_MAC
|
|
HAPI_LIB_OBJECT_MAC;
|
|
#elif PLATFORM_LINUX
|
|
HAPI_LIB_OBJECT_LINUX;
|
|
#else
|
|
TEXT("");
|
|
#endif
|
|
|
|
return LibHAPIName;
|
|
}
|
|
|
|
|
|
void
|
|
FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray<AActor*> InActors, TArray<FBox>& OutBBoxes)
|
|
{
|
|
OutBBoxes.Empty();
|
|
|
|
for (auto CurrentActor : InActors)
|
|
{
|
|
if (!CurrentActor || CurrentActor->IsPendingKill())
|
|
continue;
|
|
|
|
OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true));
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf<AActor> ActorType, const TArray<FBox>& BBoxes, const TArray<AActor*>* ExcludeActors, TArray<AActor*>& OutActors)
|
|
{
|
|
if (!IsValid(World))
|
|
return false;
|
|
|
|
OutActors.Empty();
|
|
for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
|
|
{
|
|
AActor* CurrentActor = *ActorItr;
|
|
if (!IsValid(CurrentActor))
|
|
continue;
|
|
|
|
if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get()))
|
|
continue;
|
|
|
|
if (ExcludeActors && ExcludeActors->Contains(CurrentActor))
|
|
continue;
|
|
|
|
// Special case
|
|
// Ignore the SkySpheres?
|
|
FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString();
|
|
if (ClassName.Contains("BP_Sky_Sphere"))
|
|
continue;
|
|
|
|
FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true);
|
|
for (auto InBounds : BBoxes)
|
|
{
|
|
// Check if both actor's bounds intersects
|
|
if (!ActorBounds.Intersect(InBounds))
|
|
continue;
|
|
|
|
OutActors.Add(CurrentActor);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly)
|
|
{
|
|
bool bDeleted = false;
|
|
OutPackage = nullptr;
|
|
bOutPackageIsInMemoryOnly = false;
|
|
|
|
if (!IsValid(InObjectToDelete))
|
|
return false;
|
|
|
|
// Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject
|
|
bool bIsReferenced = false;
|
|
bool bIsReferencedByUndo = false;
|
|
if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo))
|
|
return false;
|
|
|
|
if (bIsReferenced)
|
|
{
|
|
HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName());
|
|
}
|
|
else
|
|
{
|
|
// Even though we already checked for references, we still let DeleteSingleObject check for references, since
|
|
// we want that code path where it'll clean up in-memory references (undo buffer/transactions)
|
|
const bool bCheckForReferences = true;
|
|
if (DeleteSingleObject(InObjectToDelete, bCheckForReferences))
|
|
{
|
|
bDeleted = true;
|
|
|
|
OutPackage = InObjectToDelete->GetOutermost();
|
|
|
|
FString PackageFilename;
|
|
if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename))
|
|
{
|
|
// Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage
|
|
// collection to pick up the stale package
|
|
bOutPackageIsInMemoryOnly = true;
|
|
}
|
|
else
|
|
{
|
|
// There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be
|
|
// called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package
|
|
// as part of this function so that the caller can collect all Packages and do one call to
|
|
// CleanUpAfterSuccessfulDelete with an array
|
|
}
|
|
}
|
|
}
|
|
|
|
return bDeleted;
|
|
}
|
|
|
|
int32
|
|
FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray<UObject*>& InObjectsToDelete, TArray<UObject*>* OutObjectsNotDeleted)
|
|
{
|
|
int32 NumDeleted = 0;
|
|
bool bGarbageCollectionRequired = false;
|
|
TSet<UPackage*> PackagesToCleanUp;
|
|
TSet<UObject*> ProcessedObjects;
|
|
while (InObjectsToDelete.Num() > 0)
|
|
{
|
|
UObject* const ObjectToDelete = InObjectsToDelete.Pop();
|
|
|
|
if (ProcessedObjects.Contains(ObjectToDelete))
|
|
continue;
|
|
|
|
ProcessedObjects.Add(ObjectToDelete);
|
|
|
|
if (!IsValid(ObjectToDelete))
|
|
continue;
|
|
|
|
UPackage* Package = nullptr;
|
|
bool bInMemoryPackageOnly = false;
|
|
if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly))
|
|
{
|
|
NumDeleted++;
|
|
if (bInMemoryPackageOnly)
|
|
{
|
|
// Packages that are in-memory only are cleaned up by garbage collection
|
|
if (!bGarbageCollectionRequired)
|
|
bGarbageCollectionRequired = true;
|
|
}
|
|
else
|
|
{
|
|
// Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end
|
|
PackagesToCleanUp.Add(Package);
|
|
}
|
|
}
|
|
else if (OutObjectsNotDeleted)
|
|
{
|
|
OutObjectsNotDeleted->Add(ObjectToDelete);
|
|
}
|
|
}
|
|
|
|
// CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp
|
|
if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0)
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
|
|
if (PackagesToCleanUp.Num() > 0)
|
|
CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array());
|
|
|
|
return NumDeleted;
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
int32
|
|
FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options)
|
|
{
|
|
UClass* ComponentClass = SourceComponent->GetClass();
|
|
check( ComponentClass == TargetComponent->GetClass() );
|
|
|
|
const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0;
|
|
int32 CopiedPropertyCount = 0;
|
|
bool bTransformChanged = false;
|
|
|
|
// Build a list of matching component archetype instances for propagation (if requested)
|
|
TArray<UActorComponent*> ComponentArchetypeInstances;
|
|
if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances )
|
|
{
|
|
TArray<UObject*> Instances;
|
|
TargetComponent->GetArchetypeInstances(Instances);
|
|
for(UObject* ObjInstance : Instances)
|
|
{
|
|
UActorComponent* ComponentInstance = Cast<UActorComponent>(ObjInstance);
|
|
if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent)
|
|
{
|
|
ComponentArchetypeInstances.Add(ComponentInstance);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSet<const FProperty*> SourceUCSModifiedProperties;
|
|
SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties);
|
|
|
|
TArray<UActorComponent*> ComponentInstancesToReregister;
|
|
|
|
// 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( SourceComponent, TargetComponent );
|
|
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 (SourceComponent == RootComponent)
|
|
// {
|
|
// return true;
|
|
// }
|
|
// else if (RootComponent == nullptr && bSourceActorIsBPCDO)
|
|
// {
|
|
// // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component
|
|
// return (TargetComponent == TargetActor->GetRootComponent());
|
|
// }
|
|
// return false;
|
|
// };
|
|
|
|
TSet<UObject*> ModifiedObjects;
|
|
// if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property)
|
|
// && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !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)));
|
|
if( bIsSafeToCopy )
|
|
{
|
|
// if (!Options.CanCopyProperty(*Property, *SourceActor))
|
|
// {
|
|
// continue;
|
|
// }
|
|
if (!Options.CanCopyProperty(*Property, *SourceComponent))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( !bIsPreviewing )
|
|
{
|
|
if( !ModifiedObjects.Contains(TargetComponent) )
|
|
{
|
|
TargetComponent->SetFlags(RF_Transactional);
|
|
TargetComponent->Modify();
|
|
ModifiedObjects.Add(TargetComponent);
|
|
}
|
|
|
|
if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty )
|
|
{
|
|
TargetComponent->PreEditChange( Property );
|
|
}
|
|
|
|
// Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances.
|
|
TArray<UActorComponent*> ComponentArchetypeInstancesToChange;
|
|
if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances )
|
|
{
|
|
for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances)
|
|
{
|
|
if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) )
|
|
{
|
|
bool bAdd = true;
|
|
// We also need to double check that either the direct archetype of the target is also identical
|
|
if (ComponentArchetypeInstance->GetArchetype() != TargetComponent)
|
|
{
|
|
UActorComponent* CheckComponent = CastChecked<UActorComponent>(ComponentArchetypeInstance->GetArchetype());
|
|
while (CheckComponent != ComponentArchetypeInstance)
|
|
{
|
|
if (!Property->Identical_InContainer( CheckComponent, TargetComponent ))
|
|
{
|
|
bAdd = false;
|
|
break;
|
|
}
|
|
CheckComponent = CastChecked<UActorComponent>(CheckComponent->GetArchetype());
|
|
}
|
|
}
|
|
|
|
if (bAdd)
|
|
{
|
|
ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property);
|
|
|
|
if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty )
|
|
{
|
|
FPropertyChangedEvent PropertyChangedEvent( Property );
|
|
TargetComponent->PostEditChangeProperty( PropertyChangedEvent );
|
|
}
|
|
|
|
if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances )
|
|
{
|
|
for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex )
|
|
{
|
|
UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex];
|
|
if( ComponentArchetypeInstance != nullptr )
|
|
{
|
|
if( !ModifiedObjects.Contains(ComponentArchetypeInstance) )
|
|
{
|
|
// Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer.
|
|
// Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them.
|
|
if (!ComponentArchetypeInstance->IsCreatedByConstructionScript())
|
|
{
|
|
ComponentArchetypeInstance->SetFlags(RF_Transactional);
|
|
ComponentArchetypeInstance->Modify();
|
|
ModifiedObjects.Add(ComponentArchetypeInstance);
|
|
}
|
|
|
|
// We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation.
|
|
AActor* Owner = ComponentArchetypeInstance->GetOwner();
|
|
if( Owner != nullptr && !ModifiedObjects.Contains(Owner))
|
|
{
|
|
Owner->Modify();
|
|
ModifiedObjects.Add(Owner);
|
|
}
|
|
}
|
|
|
|
if (ComponentArchetypeInstance->IsRegistered())
|
|
{
|
|
ComponentArchetypeInstance->UnregisterComponent();
|
|
ComponentInstancesToReregister.Add(ComponentArchetypeInstance);
|
|
}
|
|
|
|
EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
++CopiedPropertyCount;
|
|
|
|
if( bIsTransform )
|
|
{
|
|
bTransformChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister)
|
|
{
|
|
ModifiedComponentInstance->RegisterComponent();
|
|
}
|
|
|
|
return CopiedPropertyCount;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if WITH_EDITOR
|
|
FBlueprintEditor*
|
|
FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject)
|
|
{
|
|
if (!IsValid(InObject))
|
|
return nullptr;
|
|
|
|
UObject* Outer = InObject->GetOuter();
|
|
if (!IsValid(Outer))
|
|
return nullptr;
|
|
|
|
UBlueprintGeneratedClass* OuterBPClass = Cast<UBlueprintGeneratedClass>(Outer->GetClass());
|
|
|
|
if (!OuterBPClass)
|
|
return nullptr;
|
|
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
return static_cast<FBlueprintEditor*>(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false));
|
|
}
|
|
#endif
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate)
|
|
{
|
|
if (!ComponentTemplate)
|
|
return;
|
|
|
|
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(ComponentTemplate->GetOuter());
|
|
if (!BPGC)
|
|
return;
|
|
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(BPGC->ClassGeneratedBy);
|
|
if (!Blueprint)
|
|
return;
|
|
|
|
Blueprint->Modify();
|
|
|
|
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
FBlueprintEditor* BlueprintEditor = static_cast<FBlueprintEditor*>(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false));
|
|
check(BlueprintEditor);
|
|
|
|
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
|
|
TSharedPtr<SSCSEditor> SCSEditor = nullptr;
|
|
|
|
SCSEditor = BlueprintEditor->GetSCSEditor();
|
|
check(SCSEditor);
|
|
SCSEditor->SaveSCSCurrentState(SCS);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
|
|
|
|
SCSEditor->UpdateTree(true);
|
|
}
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate)
|
|
{
|
|
if (!ComponentTemplate)
|
|
return;
|
|
|
|
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(ComponentTemplate->GetOuter());
|
|
if (!BPGC)
|
|
return;
|
|
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(BPGC->ClassGeneratedBy);
|
|
if (!Blueprint)
|
|
return;
|
|
|
|
Blueprint->Modify();
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
#endif
|
|
|
|
|
|
#if WITH_EDITOR
|
|
|
|
// Centralized call to set actor label (changing Actor's implementation was too risky)
|
|
bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel)
|
|
{
|
|
// Clean up the incoming string a bit
|
|
FString NewActorLabel = ActorLabel.TrimStartAndEnd();
|
|
if (NewActorLabel == Actor->GetActorLabel())
|
|
{
|
|
return false;
|
|
}
|
|
Actor->SetActorLabel(NewActorLabel);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName)
|
|
{
|
|
FPropertyChangedEvent Evt(FindFieldChecked<FProperty>(Obj->GetClass(), PropertyName));
|
|
Obj->PostEditChangeProperty(Evt);
|
|
}
|
|
|
|
void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property)
|
|
{
|
|
FPropertyChangedEvent Evt(Property);
|
|
Obj->PostEditChangeProperty(Evt);
|
|
}
|
|
|
|
void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange)
|
|
{
|
|
if (!InObject)
|
|
return;
|
|
if (!InObject->HasAnyFlags(RF_ArchetypeObject))
|
|
return;
|
|
|
|
// Iterate over the modified properties and propagate value changed to all archetype instances
|
|
TArray<UObject*> ArchetypeInstances;
|
|
InObject->GetArchetypeInstances(ArchetypeInstances);
|
|
for (UObject* Instance : ArchetypeInstances)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName()));
|
|
for (FName PropertyName : DeltaChange.ChangedProperties)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString()));
|
|
// FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef<void(UObject* Obj)> Operation)
|
|
{
|
|
if (!InTemplateObj)
|
|
return;
|
|
if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject))
|
|
return;
|
|
|
|
TArray<UObject*> Instances;
|
|
InTemplateObj->GetArchetypeInstances(Instances);
|
|
|
|
for(UObject* Instance : Instances)
|
|
{
|
|
Operation(Instance);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FHoudiniStaticMeshGenerationProperties
|
|
FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties()
|
|
{
|
|
FHoudiniStaticMeshGenerationProperties SMGP;
|
|
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
if (HoudiniRuntimeSettings)
|
|
{
|
|
SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry;
|
|
SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial;
|
|
SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance;
|
|
SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag;
|
|
//SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier;
|
|
SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution;
|
|
SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex;
|
|
SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio;
|
|
SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier;
|
|
SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride;
|
|
SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings;
|
|
SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData;
|
|
}
|
|
|
|
return SMGP;
|
|
}
|
|
|
|
FMeshBuildSettings
|
|
FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings()
|
|
{
|
|
FMeshBuildSettings DefaultBuildSettings;
|
|
|
|
const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
if(HoudiniRuntimeSettings)
|
|
{
|
|
DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates;
|
|
DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace;
|
|
DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer;
|
|
DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution;
|
|
DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs;
|
|
DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex;
|
|
DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex;
|
|
|
|
DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals;
|
|
DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer;
|
|
DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis;
|
|
DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided;
|
|
DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap;
|
|
DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale;
|
|
|
|
// Recomputing normals.
|
|
EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag;
|
|
switch (RecomputeNormalFlag)
|
|
{
|
|
case HRSRF_Never:
|
|
{
|
|
DefaultBuildSettings.bRecomputeNormals = false;
|
|
break;
|
|
}
|
|
|
|
case HRSRF_Always:
|
|
case HRSRF_OnlyIfMissing:
|
|
default:
|
|
{
|
|
DefaultBuildSettings.bRecomputeNormals = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Recomputing tangents.
|
|
EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag;
|
|
switch (RecomputeTangentFlag)
|
|
{
|
|
case HRSRF_Never:
|
|
{
|
|
DefaultBuildSettings.bRecomputeTangents = false;
|
|
break;
|
|
}
|
|
|
|
case HRSRF_Always:
|
|
case HRSRF_OnlyIfMissing:
|
|
default:
|
|
{
|
|
DefaultBuildSettings.bRecomputeTangents = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Lightmap UV generation.
|
|
EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag;
|
|
switch (GenerateLightmapUVFlag)
|
|
{
|
|
case HRSRF_Never:
|
|
{
|
|
DefaultBuildSettings.bGenerateLightmapUVs = false;
|
|
break;
|
|
}
|
|
|
|
case HRSRF_Always:
|
|
case HRSRF_OnlyIfMissing:
|
|
default:
|
|
{
|
|
DefaultBuildSettings.bGenerateLightmapUVs = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return DefaultBuildSettings;
|
|
}
|
|
|