/* * 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 "HoudiniInput.h" #include "HoudiniEngineRuntime.h" #include "HoudiniAssetComponent.h" #include "HoudiniOutput.h" #include "HoudiniSplineComponent.h" #include "HoudiniAsset.h" #include "HoudiniGeoPartObject.h" #include "HoudiniAssetComponent.h" #include "HoudiniAssetBlueprintComponent.h" #include "EngineUtils.h" #include "Engine/Brush.h" #include "Engine/Engine.h" #include "Engine/DataTable.h" #include "Model.h" #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" #include "UObject/UObjectGlobals.h" #include "FoliageType_InstancedStaticMesh.h" #include "Components/SplineComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/InstancedStaticMeshComponent.h" #include "Landscape.h" #if WITH_EDITOR #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #endif // UHoudiniInput::UHoudiniInput() : Type(EHoudiniInputType::Invalid) , PreviousType(EHoudiniInputType::Invalid) , AssetNodeId(-1) , InputNodeId(-1) , InputIndex(0) , ParmId(-1) , bIsObjectPathParameter(false) , bHasChanged(false) , bPackBeforeMerge(false) , bExportLODs(false) , bExportSockets(false) , bExportColliders(false) , bCookOnCurveChanged(true) , bStaticMeshChanged(false) , bInputAssetConnectedInHoudini(false) , DefaultCurveOffset(0.f) , bAddRotAndScaleAttributesOnCurves(false) , bIsWorldInputBoundSelector(false) , bWorldInputBoundSelectorAutoUpdate(false) , UnrealSplineResolution(50.0f) , bUpdateInputLandscape(false) , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) , bLandscapeExportSelectionOnly(false) , bLandscapeAutoSelectComponent(false) , bLandscapeExportMaterials(false) , bLandscapeExportLighting(false) , bLandscapeExportNormalizedUVs(false) , bLandscapeExportTileUVs(false) { Name = TEXT(""); Label = TEXT(""); SetFlags(RF_Transactional); // Geometry inputs always have one null default object GeometryInputObjects.Add(nullptr); KeepWorldTransform = GetDefaultXTransformType(); const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; } void UHoudiniInput::BeginDestroy() { InvalidateData(); // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! // This messes up unreal's Garbage collection and would cause crashes on duplication // Mark all our input objects for destruction ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { ObjectArray.Empty(); }); Super::BeginDestroy(); } #if WITH_EDITOR void UHoudiniInput::PostEditUndo() { Super::PostEditUndo(); TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); if (!InputObjectsPtr) return; MarkChanged(true); bool bBlueprintStructureChanged = false; if (HasInputTypeChanged()) { // If the input type has changed on undo, previousType becomes new type /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... ) { EHoudiniInputType NewType = PreviousType; SetInputType(NewType); } */ EHoudiniInputType Temp = Type; Type = PreviousType; PreviousType = EHoudiniInputType::Invalid; // If the undo action caused input type changing, treat it as a regular type changing // after set up the new and prev types properly SetInputType(Temp, bBlueprintStructureChanged); } else { if (Type == EHoudiniInputType::Asset) { // Mark the input asset object as changed, since only undo changing asset will get into here. // The input array will be empty when undo adding asset (only support single asset input object in an input now) for (auto & NextAssetInputObj : *InputObjectsPtr) { if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) continue; NextAssetInputObj->MarkChanged(true); } } if (Type == EHoudiniInputType::World) { if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) { for (auto & NextNodeId : CreatedDataNodeIds) { if (bCanDeleteHoudiniNodes) FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); } CreatedDataNodeIds.Empty(); if (bCanDeleteHoudiniNodes) FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); InputNodeId = -1; } } if (Type == EHoudiniInputType::Curve) { if (PreviousType != EHoudiniInputType::Curve) { for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); if (!SplineInput || SplineInput->IsPendingKill()) continue; UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) continue; USceneComponent* OuterComponent = Cast(GetOuter()); // Attach the new Houdini spline component to it's owner HoudiniSplineComponent->RegisterComponent(); HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); HoudiniSplineComponent->SetVisibility(true, true); HoudiniSplineComponent->SetHoudiniSplineVisible(true); HoudiniSplineComponent->SetHiddenInGame(false, true); HoudiniSplineComponent->MarkChanged(true); } return; } bool bUndoDelete = false; bool bUndoInsert = false; bool bUndoDeletedObjArrayEmptied = false; TArray< USceneComponent* > childActor; UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); if (OuterHAC && !OuterHAC->IsPendingKill()) childActor = OuterHAC->GetAttachChildren(); // Undo delete input objects action for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) { UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; if (!InputObject || InputObject->IsPendingKill()) continue; UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) continue; UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); if (!SplineComponent || SplineComponent->IsPendingKill()) continue; // If the last change deleted this curve input, recreate this Houdini Spline input. if (!SplineComponent->GetAttachParent()) { bUndoDelete = true; if (!bUndoDeletedObjArrayEmptied) LastUndoDeletedInputs.Empty(); bUndoDeletedObjArrayEmptied = true; UHoudiniSplineComponent * ReconstructedSpline = NewObject( GetOuter(), UHoudiniSplineComponent::StaticClass()); if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) continue; ReconstructedSpline->SetFlags(RF_Transactional); ReconstructedSpline->CopyHoudiniData(SplineComponent); UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; ReconstructedSpline->RegisterComponent(); ReconstructedSpline->SetFlags(RF_Transactional); CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); // Reset the LastInsertedInputsArray for redoing this undo action. } } if (bUndoDelete) return; // Undo insert input objects action for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) { bUndoInsert = true; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) continue; HoudiniSplineComponent->DestroyComponent(); } if (bUndoInsert) return; for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) { UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) continue; FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); HoudiniSplineComponent->DetachFromComponent(DetachTransRules); HoudiniSplineComponent->DestroyComponent(); } } } if (bBlueprintStructureChanged) { UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); } } #endif FBox UHoudiniInput::GetBounds() const { FBox BoxBounds(ForceInitToZero); switch (Type) { case EHoudiniInputType::Curve: { for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) { const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); if (!CurInCurve || CurInCurve->IsPendingKill()) continue; UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); if (!CurCurve || CurCurve->IsPendingKill()) continue; FBox CurCurveBound(ForceInitToZero); for (auto & Trans : CurCurve->CurvePoints) { CurCurveBound += Trans.GetLocation(); } UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); if (OuterHAC && !OuterHAC->IsPendingKill()) BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); } } break; case EHoudiniInputType::Asset: { for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) { UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); if (!CurInAsset || CurInAsset->IsPendingKill()) continue; UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); if (!CurInHAC || CurInHAC->IsPendingKill()) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); } } break; case EHoudiniInputType::World: { for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) { UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); if (CurInActor && !CurInActor->IsPendingKill()) { AActor* Actor = CurInActor->GetActor(); if (!Actor || Actor->IsPendingKill()) continue; FVector Origin, Extent; Actor->GetActorBounds(false, Origin, Extent); BoxBounds += FBox::BuildAABB(Origin, Extent); } else { // World Input now also support HoudiniAssets UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); if (CurInAsset && !CurInAsset->IsPendingKill()) { UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); if (!CurInHAC || CurInHAC->IsPendingKill()) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); continue; } } } } break; case EHoudiniInputType::Landscape: { for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) { UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); if (!CurInLandscape || CurInLandscape->IsPendingKill()) continue; ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); if (!CurLandscape || CurLandscape->IsPendingKill()) continue; FVector Origin, Extent; CurLandscape->GetActorBounds(false, Origin, Extent); BoxBounds += FBox::BuildAABB(Origin, Extent); } } break; case EHoudiniInputType::Skeletal: case EHoudiniInputType::Invalid: default: break; } return BoxBounds; } FString UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) { FString InputTypeStr; switch (InInputType) { case EHoudiniInputType::Geometry: { InputTypeStr = TEXT("Geometry Input"); } break; case EHoudiniInputType::Asset: { InputTypeStr = TEXT("Asset Input"); } break; case EHoudiniInputType::Curve: { InputTypeStr = TEXT("Curve Input"); } break; case EHoudiniInputType::Landscape: { InputTypeStr = TEXT("Landscape Input"); } break; case EHoudiniInputType::World: { InputTypeStr = TEXT("World Outliner Input"); } break; case EHoudiniInputType::Skeletal: { InputTypeStr = TEXT("Skeletal Mesh Input"); } break; } return InputTypeStr; } EHoudiniInputType UHoudiniInput::StringToInputType(const FString& InInputTypeString) { if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::Geometry; } else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::Asset; } else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::Curve; } else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::Landscape; } else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::World; } else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) { return EHoudiniInputType::Skeletal; } return EHoudiniInputType::Invalid; } EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) { if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) { return EHoudiniCurveType::Polygon; } else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) { return EHoudiniCurveType::Nurbs; } else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) { return EHoudiniCurveType::Bezier; } else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) { return EHoudiniCurveType::Points; } return EHoudiniCurveType::Invalid; } EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) { if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) { return EHoudiniCurveMethod::CVs; } else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) { return EHoudiniCurveMethod::Breakpoints; } else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) { return EHoudiniCurveMethod::Freehand; } return EHoudiniCurveMethod::Invalid; } // void UHoudiniInput::SetSOPInput(const int32& InInputIndex) { // Set the input index InputIndex = InInputIndex; // Invalidate objpath parameter ParmId = -1; bIsObjectPathParameter = false; } void UHoudiniInput::SetObjectPathParameter(const int32& InParmId) { // Set as objpath parameter ParmId = InParmId; bIsObjectPathParameter = true; // Invalidate the geo input InputIndex = -1; } EHoudiniXformType UHoudiniInput::GetDefaultXTransformType() { switch (Type) { case EHoudiniInputType::Curve: case EHoudiniInputType::Geometry: case EHoudiniInputType::Skeletal: return EHoudiniXformType::None; case EHoudiniInputType::Asset: case EHoudiniInputType::Landscape: case EHoudiniInputType::World: return EHoudiniXformType::IntoThisObject; } return EHoudiniXformType::Auto; } bool UHoudiniInput::GetKeepWorldTransform() const { bool bReturn = false; switch (KeepWorldTransform) { case EHoudiniXformType::Auto: { // Return default values corresponding to the input type: if (Type == EHoudiniInputType::Curve || Type == EHoudiniInputType::Geometry || Type == EHoudiniInputType::Skeletal ) { // NONE for Geo, Curve and skeletal mesh IN bReturn = false; } else { // INTO THIS OBJECT for Asset, Landscape and World IN bReturn = true; } break; } case EHoudiniXformType::None: { bReturn = false; break; } case EHoudiniXformType::IntoThisObject: { bReturn = true; break; } } return bReturn; } void UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) { if (bInKeepWorldTransform) { KeepWorldTransform = EHoudiniXformType::IntoThisObject; } else { KeepWorldTransform = EHoudiniXformType::None; } } void UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) { if (InInputType == Type) return; SetPreviousInputType(Type); // Mark this input as changed MarkChanged(true); bOutBlueprintStructureModified = true; // Check previous input type switch (PreviousType) { case EHoudiniInputType::Asset: { break; } case EHoudiniInputType::Curve: { // detach the input curves from the asset component if (GetNumberOfInputObjects() > 0) { for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) continue; UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) continue; HoudiniSplineComponent->Modify(); const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); if (bIsArchetype) { #if WITH_EDITOR check(HoudiniSplineComponent->IsTemplate()); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); #endif } else { AActor* OwningActor = HoudiniSplineComponent->GetOwner(); check(OwningActor); FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); HoudiniSplineComponent->DetachFromComponent(DetachTransRules); HoudiniSplineComponent->SetVisibility(false, true); HoudiniSplineComponent->SetHoudiniSplineVisible(false); HoudiniSplineComponent->SetHiddenInGame(true, true); // This NodeId shouldn't be invalidated like this. If a spline component // or curve input is no longer valid, the input object should be removed from the HoudinInput // to get cleaned up properly. // HoudiniSplineComponent->SetNodeId(-1); HoudiniSplineComponent->MarkChanged(true); } bOutBlueprintStructureModified = true; } } break; } case EHoudiniInputType::Geometry: { break; } case EHoudiniInputType::Landscape: { TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); if (!InputObjectsArray) break; for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) { UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; if (!InputObj || InputObj->IsPendingKill()) continue; UHoudiniInputLandscape* InputLandscape = Cast(InputObj); if (!InputLandscape || InputLandscape->IsPendingKill()) continue; // do something? } break; } case EHoudiniInputType::Skeletal: { break; } case EHoudiniInputType::World: { break; } default: break; } Type = InInputType; // TODO: NOPE, not needed // Set keep world transform to default w.r.t to new input type. //KeepWorldTransform = GetDefaultXTransformType(); // Check current input type switch (InInputType) { case EHoudiniInputType::World: case EHoudiniInputType::Asset: { UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); if (OuterHAC && !bImportAsReference) { for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) continue; UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); if (!CurrentHAC || CurrentHAC->IsPendingKill()) continue; CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); } } } break; case EHoudiniInputType::Curve: { if (GetNumberOfInputObjects() == 0) { CreateNewCurveInputObject(bOutBlueprintStructureModified); MarkChanged(true); } else { for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); if (!IsValid(SplineInput)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); if (!IsValid(HoudiniSplineComponent)) continue; HoudiniSplineComponent->Modify(); const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); if (bIsArchetype) { #if WITH_EDITOR check(HoudiniSplineComponent->IsTemplate()); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); #endif } else { // Attach the new Houdini spline component to it's owner AActor* OwningActor = HoudiniSplineComponent->GetOwner(); check(OwningActor); USceneComponent* OuterComponent = Cast(GetOuter()); HoudiniSplineComponent->RegisterComponent(); HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); HoudiniSplineComponent->SetHoudiniSplineVisible(true); HoudiniSplineComponent->SetHiddenInGame(false, true); HoudiniSplineComponent->SetVisibility(true, true); HoudiniSplineComponent->MarkChanged(true); } bOutBlueprintStructureModified = true; } } } break; case EHoudiniInputType::Geometry: { } break; case EHoudiniInputType::Landscape: { // Need to do anything on select? } break; case EHoudiniInputType::Skeletal: { } break; default: { } break; } } UHoudiniInputObject* UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) { if (CurveInputObjects.Num() > 0) return nullptr; UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) return nullptr; UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) return nullptr; // Default Houdini spline component input should not be visible at initialization HoudiniSplineComponent->SetVisibility(true, true); HoudiniSplineComponent->SetHoudiniSplineVisible(true); HoudiniSplineComponent->SetHiddenInGame(false, true); CurveInputObjects.Add(NewCurveInputObject); SetInputObjectsNumber(EHoudiniInputType::Curve, 1); CurveInputObjects.SetNum(1); return NewCurveInputObject; } void UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) { MarkDataUploadNeeded(bInChanged); // Mark all the objects from this input has changed so they upload themselves TSet InputTypes; InputTypes.Add(Type); InputTypes.Add(EHoudiniInputType::Curve); TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); if (NewInputObjects) { for (auto CurInputObject : *NewInputObjects) { if (CurInputObject && !CurInputObject->IsPendingKill()) CurInputObject->MarkChanged(bInChanged); } } } UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) { UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); return NewInput; } void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) { // Preserve the current input objects before the copy to ensure we don't lose // access to input objects and have them end up in the garbage. TMap*> PrevInputObjectsMap; for(EHoudiniInputType InputType : HoudiniInputTypeList) { PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); } // TArray PrevInputObjects; // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); // if (OldToInputObjects) // PrevInputObjects = *OldToInputObjects; // Copy the state of this UHoudiniInput object. if (bCopyAllProperties) { UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; Params.bDoDelta = false; // Perform a deep copy Params.bClearReferences = false; // References will be replaced afterwards. UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); } AssetNodeId = InInput->AssetNodeId; InputNodeId = InInput->InputNodeId; ParmId = InInput->ParmId; bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; //if (bInCanDeleteHoudiniNodes) //{ // // Delete stale data nodes before they get overwritten. // TSet NewNodeIds(InInput->CreatedDataNodeIds); // for (int32 NodeId : CreatedDataNodeIds) // { // if (!NewNodeIds.Contains(NodeId)) // { // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); // } // } //} CreatedDataNodeIds = InInput->CreatedDataNodeIds; // Important note: At this point the new object may still share objects with InInput. // The CopyInputs() will properly duplicate inputs where necessary. // Copy states of Input Objects that correspond to the current type. for(auto& Entry : PrevInputObjectsMap) { EHoudiniInputType InputType = Entry.Key; TArray* PrevInputObjects = Entry.Value; TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); if (ToInputObjects && FromInputObjects) { *ToInputObjects = *PrevInputObjects; CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); } } } void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) { bCanDeleteHoudiniNodes = bInCanDeleteNodes; for(UHoudiniInputObject* InputObject : GeometryInputObjects) { if (!InputObject) continue; InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); } for(UHoudiniInputObject* InputObject : AssetInputObjects) { if (!InputObject) continue; InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); } for(UHoudiniInputObject* InputObject : CurveInputObjects) { if (!InputObject) continue; InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); } for(UHoudiniInputObject* InputObject : LandscapeInputObjects) { if (!InputObject) continue; InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); } for(UHoudiniInputObject* InputObject : WorldInputObjects) { if (!InputObject) continue; InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); } } void UHoudiniInput::InvalidateData() { // If valid, mark our input node for deletion if (InputNodeId >= 0) { // .. but if we're an asset input, don't delete the node as InputNodeId // is set to the input HDA's node ID! if (Type != EHoudiniInputType::Asset) { if (bCanDeleteHoudiniNodes) FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); } InputNodeId = -1; } for(UHoudiniInputObject* InputObject : GeometryInputObjects) { if (!InputObject) continue; InputObject->InvalidateData(); } for(UHoudiniInputObject* InputObject : AssetInputObjects) { if (!InputObject) continue; InputObject->InvalidateData(); } for(UHoudiniInputObject* InputObject : CurveInputObjects) { if (!InputObject) continue; InputObject->InvalidateData(); } for(UHoudiniInputObject* InputObject : LandscapeInputObjects) { if (!InputObject) continue; InputObject->InvalidateData(); } for(UHoudiniInputObject* InputObject : WorldInputObjects) { if (!InputObject) continue; if (InputObject->IsA()) { // When the input object is a HoudiniAssetComponent, // we need to be sure that this HDA node id is not in CreatedDataNodeIds // We dont want to delete the input HDA node! CreatedDataNodeIds.Remove(InputObject->InputNodeId); } InputObject->InvalidateData(); } if (bCanDeleteHoudiniNodes) { auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); for(int32 NodeId : CreatedDataNodeIds) { HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); } } CreatedDataNodeIds.Empty(); } void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) { TSet StaleObjects(ToInputObjects); const int32 NumInputs = FromInputObjects.Num(); UObject* TargetOuter = GetOuter(); ToInputObjects.SetNum(NumInputs); for (int i = 0; i < NumInputs; i++) { UHoudiniInputObject* FromObject = FromInputObjects[i]; UHoudiniInputObject* ToObject = ToInputObjects[i]; if (!FromObject) { ToInputObjects[i] = nullptr; continue; } if (ToObject) { bool IsValid = true; // Is ToInput and FromInput the same or do we have to create a input object? IsValid = IsValid && ToObject->Matches(*FromObject); IsValid = IsValid && ToObject->GetOuter() == TargetOuter; if (!IsValid) { ToObject = nullptr; } } if (ToObject) { // We have an existing (matching) object. Copy the // state from the incoming input. StaleObjects.Remove(ToObject); ToObject->CopyStateFrom(FromObject, true); } else { // We need to create a new input here. ToObject = FromObject->DuplicateAndCopyState(TargetOuter); ToInputObjects[i] = ToObject; } ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); } for (UHoudiniInputObject* StaleInputObject : StaleObjects) { if (!StaleInputObject) continue; if (StaleInputObject->GetOuter() == this) { StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); } } } UHoudiniInputHoudiniSplineComponent* UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) { UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; UObject* OuterObj = GetOuter(); USceneComponent* OuterComp = Cast(GetOuter()); bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); if (!FromHoudiniSplineInputComponent) { // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. check(OuterObj) // Create a default Houdini spline input if a null pointer is passed in. FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); // Create a Houdini Input Object. UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( nullptr, OuterObj, HoudiniSplineName.ToString()); if (!NewInputObject || NewInputObject->IsPendingKill()) return nullptr; HoudiniSplineInput = Cast(NewInputObject); if (!HoudiniSplineInput) return nullptr; HoudiniSplineComponent = NewObject( HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) return nullptr; HoudiniSplineInput->Update(HoudiniSplineComponent); HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); HoudiniSplineComponent->SetFlags(RF_Transactional); // Set the default position of curve to avoid overlapping. HoudiniSplineComponent->SetOffset(DefaultCurveOffset); DefaultCurveOffset += 100.f; if (!bOuterIsTemplate) { HoudiniSplineComponent->RegisterComponent(); // Attach the new Houdini spline component to it's owner. if (bAttachToparent) HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); } //push the new input object to the array for new type. if (bAppendToInputArray && Type == EHoudiniInputType::Curve) GetHoudiniInputObjectArray(Type)->Add(NewInputObject); #if WITH_EDITOR if (bOuterIsTemplate) { UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); if (HAB) { UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); if (Blueprint) { TArray Components; Components.Add(HoudiniSplineComponent); USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of // backwards compatibility, manually determine which SCSNode was added instead of // relying on Params.OutNodes. //FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; //Params.OptionalNewRootNode = HABNode; const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, HABNode, false); USCS_Node* NewNode = nullptr; const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); if (AddedNodes.Num() > 0) { // Record Input / SCS node mapping USCS_Node* SCSNode = AddedNodes.Array()[0]; HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); } Blueprint->Modify(); bOutBlueprintStructureModified = true; } } } #endif } else { // Otherwise, get the Houdini spline, and Houdini spline input from the argument. HoudiniSplineInput = FromHoudiniSplineInputComponent; HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) return nullptr; // Attach the new Houdini spline component to it's owner. HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); } // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. HoudiniSplineComponent->SetIsInputCurve(true); // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. HoudiniSplineComponent->MarkChanged(true); return HoudiniSplineInput; } void UHoudiniInput::RemoveSplineFromInputObject( UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, bool& bOutBlueprintStructureModified) const { if (!InHoudiniSplineInputObject) return; UObject* OuterObj = GetOuter(); const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); if (bOuterIsTemplate) { #if WITH_EDITOR // Find the SCS node that corresponds to this input and remove it. UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); if (HAB) { const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); if (Blueprint) { USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; check(SCS); FGuid SCSGuid; if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) { // TODO: Move this SCS variable removal code to a reusable utility function. We're // going to need to reuse this in a few other places too. USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); if (SCSNode) { SCS->RemoveNodeAndPromoteChildren(SCSNode); SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); bOutBlueprintStructureModified = true; HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); if (SCSNode->ComponentTemplate != nullptr) { const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); SCSNode->ComponentTemplate->Modify(); SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); TArray ArchetypeInstances; auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) { ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); for (UObject* ArchetypeInstance : ArchetypeInstances) { if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) { CastChecked(ArchetypeInstance)->DestroyComponent(); ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); } } }; DestroyArchetypeInstances(SCSNode->ComponentTemplate); if (Blueprint) { // Children need to have their inherited component template instance of the component renamed out of the way as well TArray ChildrenOfClass; GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); for (UClass* ChildClass : ChildrenOfClass) { UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) { Component->Modify(); Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); DestroyArchetypeInstances(Component); } } } } } } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) } // if (Blueprint) } #endif } // if (bIsOuterTemplate) else { UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); if (HoudiniSplineComponent) { // detach the input curves from the asset component //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); // Destroy the Houdini Spline Component //InputObjectsPtr->RemoveAt(AtIndex); HoudiniSplineComponent->DestroyComponent(); } } InHoudiniSplineInputObject->Update(nullptr); } TArray* UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) { switch (InType) { case EHoudiniInputType::Geometry: return &GeometryInputObjects; case EHoudiniInputType::Curve: return &CurveInputObjects; case EHoudiniInputType::Asset: return &AssetInputObjects; case EHoudiniInputType::Landscape: return &LandscapeInputObjects; case EHoudiniInputType::World: return &WorldInputObjects; case EHoudiniInputType::Skeletal: return &SkeletalInputObjects; default: case EHoudiniInputType::Invalid: return nullptr; } return nullptr; } TArray* UHoudiniInput::GetBoundSelectorObjectArray() { return &WorldInputBoundSelectorObjects; } const TArray* UHoudiniInput::GetBoundSelectorObjectArray() const { return &WorldInputBoundSelectorObjects; } const TArray* UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const { switch (InType) { case EHoudiniInputType::Geometry: return &GeometryInputObjects; case EHoudiniInputType::Curve: return &CurveInputObjects; case EHoudiniInputType::Asset: return &AssetInputObjects; case EHoudiniInputType::Landscape: return &LandscapeInputObjects; case EHoudiniInputType::World: return &WorldInputObjects; case EHoudiniInputType::Skeletal: return &SkeletalInputObjects; default: case EHoudiniInputType::Invalid: return nullptr; } return nullptr; } UHoudiniInputObject* UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) { return GetHoudiniInputObjectAt(Type, AtIndex); } const UHoudiniInputObject* UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const { const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) return nullptr; return (*InputObjectsArray)[AtIndex]; } UHoudiniInputObject* UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) return nullptr; return (*InputObjectsArray)[AtIndex]; } UObject* UHoudiniInput::GetInputObjectAt(const int32& AtIndex) { return GetInputObjectAt(Type, AtIndex); } AActor* UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) { if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) return nullptr; return WorldInputBoundSelectorObjects[AtIndex]; } UObject* UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) return nullptr; return HoudiniInputObject->GetObject(); } void UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) { InsertInputObjectAt(Type, AtIndex); } void UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return; InputObjectsPtr->InsertDefaulted(AtIndex, 1); MarkChanged(true); } void UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) { DeleteInputObjectAt(Type, AtIndex); } void UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return; if (!InputObjectsPtr->IsValidIndex(AtIndex)) return; bool bBlueprintStructureModified = false; if (Type == EHoudiniInputType::Asset) { // ... TODO operations for removing asset input type } else if (Type == EHoudiniInputType::Curve) { UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); if (HoudiniSplineInputObject) { RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); } } else if (Type == EHoudiniInputType::Geometry) { // ... TODO operations for removing geometry input type } else if (Type == EHoudiniInputType::Landscape) { // ... TODO operations for removing landscape input type } else if (Type == EHoudiniInputType::Skeletal) { // ... TODO operations for removing skeletal input type } else if (Type == EHoudiniInputType::World) { // ... TODO operations for removing world input type } else { // ... invalid input type } MarkChanged(true); UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) { // Mark the input object's nodes for deletion InputObjectToDelete->InvalidateData(); // If the deleted object wasnt null, trigger a re upload of the input data MarkDataUploadNeeded(true); } InputObjectsPtr->RemoveAt(AtIndex); // Delete the merge node when all the input objects are deleted. if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) { if (bCanDeleteHoudiniNodes) FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); InputNodeId = -1; } #if WITH_EDITOR if (bBlueprintStructureModified) { UActorComponent* Component = Cast(GetOuter()); FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); } #endif } void UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) { DuplicateInputObjectAt(Type, AtIndex); } void UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return; if (!InputObjectsPtr->IsValidIndex(AtIndex)) return; // If the duplicated object is not null, trigger a re upload of the input data bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; // TODO: Duplicate the UHoudiniInputObject!! UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; InputObjectsPtr->Insert(DuplicateInput, AtIndex); MarkChanged(true); if (bTriggerUpload) MarkDataUploadNeeded(true); } int32 UHoudiniInput::GetNumberOfInputObjects() { return GetNumberOfInputObjects(Type); } int32 UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return 0; return InputObjectsPtr->Num(); } int32 UHoudiniInput::GetNumberOfInputMeshes() { return GetNumberOfInputMeshes(Type); } int32 UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return 0; // TODO? // If geometry input, and we only have one null object, return 0 int32 Num = InputObjectsPtr->Num(); // TODO: Fix BP properly! // Special case for SM in BP: // we need to add extra input objects store in BlueprintStaticMeshes // Same thing for Actor InputObjects! for (auto InputObj : *InputObjectsPtr) { if (!InputObj || InputObj->IsPendingKill()) continue; UHoudiniInputStaticMesh* InputSM = Cast(InputObj); if (InputSM && !InputSM->IsPendingKill()) { if (InputSM->BlueprintStaticMeshes.Num() > 0) { Num += (InputSM->BlueprintStaticMeshes.Num() - 1); } } UHoudiniInputActor* InputActor = Cast(InputObj); if (InputActor && !InputActor->IsPendingKill()) { if (InputActor->GetActorComponents().Num() > 0) { Num += (InputActor->GetActorComponents().Num() - 1); } } } return Num; } int32 UHoudiniInput::GetNumberOfBoundSelectorObjects() const { return WorldInputBoundSelectorObjects.Num(); } void UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) { return SetInputObjectAt(Type, AtIndex, InObject); } void UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) { // Start by making sure we have the proper number of input objects int32 NumIntObject = GetNumberOfInputObjects(InType); if (NumIntObject <= AtIndex) { // We need to resize the array SetInputObjectsNumber(InType, AtIndex + 1); } UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); if (CurrentInputObject == InObject) { // Nothing to do return; } UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); if (!InObject) { // We want to set the input object to null TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) return; if (CurrentInputObjectWrapper) { // TODO: Check this case // Do not destroy the input object manually! this messes up GC //CurrentInputObjectWrapper->ConditionalBeginDestroy(); MarkDataUploadNeeded(true); } (*InputObjectsPtr)[AtIndex] = nullptr; return; } // Get the type of the previous and new input objects EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; // See if we can reuse the existing InputObject if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) { // The InputObjectTypes match, we can just update the existing object CurrentInputObjectWrapper->Update(InObject); CurrentInputObjectWrapper->MarkChanged(true); return; } // Destroy the existing input object TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!ensure(InputObjectsPtr)) return; UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); if (!ensure(NewInputObject)) return; // Mark that input object as changed so we know we need to update it NewInputObject->MarkChanged(true); if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) { // TODO: // For some input type, we may have to copy some of the previous object's property before deleting it // Delete the previous object CurrentInputObjectWrapper->MarkPendingKill(); (*InputObjectsPtr)[AtIndex] = nullptr; } // Update the input object array with the newly created input object (*InputObjectsPtr)[AtIndex] = NewInputObject; } void UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) return; if (InputObjectsPtr->Num() == InNewCount) { // Nothing to do return; } if (InNewCount > InputObjectsPtr->Num()) { // Simply add new default InputObjects InputObjectsPtr->SetNum(InNewCount); } else { // TODO: Check this case! // Do not destroy the input object themselves manually, // destroy the input object's nodes and reduce the array's size for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) continue; if (bCanDeleteHoudiniNodes) CurrentInputObject->InvalidateData(); /*/ //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); CurrentObject->ConditionalBeginDestroy(); (*InputObjectsPtr)[InObjIdx] = nullptr; */ } // Decrease the input object array size InputObjectsPtr->SetNum(InNewCount); } // Also delete the input's merge node when all the input objects are deleted. if (InNewCount == 0 && InputNodeId >= 0) { if (bCanDeleteHoudiniNodes) FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); InputNodeId = -1; } } void UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) { if (WorldInputBoundSelectorObjects.Num() == InNewCount) { // Nothing to do return; } if (InNewCount > WorldInputBoundSelectorObjects.Num()) { // Simply add new default InputObjects WorldInputBoundSelectorObjects.SetNum(InNewCount); } else { /* // TODO: Not Needed? // Do not destroy the input object themselves manually, // destroy the input object's nodes and reduce the array's size for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) { UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; if (!CurrentInputObject) continue; CurrentInputObject->MarkInputNodesForDeletion(); } */ // Decrease the input object array size WorldInputBoundSelectorObjects.SetNum(InNewCount); } } void UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) { // Start by making sure we have the proper number of objects int32 NumIntObject = GetNumberOfBoundSelectorObjects(); if (NumIntObject <= AtIndex) { // We need to resize the array SetBoundSelectorObjectsNumber(AtIndex + 1); } AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); if (CurrentActor == InActor) { // Nothing to do return; } // Update the array with the new object WorldInputBoundSelectorObjects[AtIndex] = InActor; } // Helper function indicating what classes are supported by an input type TArray UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) { TArray AllowedClasses; switch (InInputType) { case EHoudiniInputType::Geometry: AllowedClasses.Add(UStaticMesh::StaticClass()); AllowedClasses.Add(USkeletalMesh::StaticClass()); AllowedClasses.Add(UBlueprint::StaticClass()); AllowedClasses.Add(UDataTable::StaticClass()); AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); break; case EHoudiniInputType::Curve: AllowedClasses.Add(USplineComponent::StaticClass()); AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); break; case EHoudiniInputType::Asset: AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); break; case EHoudiniInputType::Landscape: AllowedClasses.Add(ALandscapeProxy::StaticClass()); break; case EHoudiniInputType::World: AllowedClasses.Add(AActor::StaticClass()); break; case EHoudiniInputType::Skeletal: AllowedClasses.Add(USkeletalMesh::StaticClass()); break; default: break; } return AllowedClasses; } // Helper function indicating if an object is supported by an input type bool UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) { TArray AllowedClasses = GetAllowedClasses(InInputType); for (auto CurClass : AllowedClasses) { if (InObject->IsA(CurClass)) return true; } return false; } bool UHoudiniInput::IsDataUploadNeeded() { if (bDataUploadNeeded) return true; return HasChanged(); } // Indicates if this input has changed and should be updated bool UHoudiniInput::HasChanged() { if (bHasChanged) return true; TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); if (!ensure(InputObjectsPtr)) return false; for (auto CurrentInputObject : (*InputObjectsPtr)) { if (CurrentInputObject && CurrentInputObject->HasChanged()) return true; } return false; } bool UHoudiniInput::IsTransformUploadNeeded() { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); if (!ensure(InputObjectsPtr)) return false; for (auto CurrentInputObject : (*InputObjectsPtr)) { if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) return true; } return false; } // Indicates if this input needs to trigger an update bool UHoudiniInput::NeedsToTriggerUpdate() { if (bNeedsToTriggerUpdate) return true; const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); if (!ensure(InputObjectsPtr)) return false; for (auto CurrentInputObject : (*InputObjectsPtr)) { if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) return true; } return false; } FString UHoudiniInput::GetNodeBaseName() const { UHoudiniAssetComponent* HAC = Cast(GetOuter()); FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); // Unfortunately CreateInputNode always prefix with input_... if (IsObjectPathParameter()) NodeBaseName += TEXT("_") + GetName(); else NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); return NodeBaseName; } void UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) { #if WITH_EDITORONLY_DATA if (TransformUIExpanded.IsValidIndex(AtIndex)) { TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; } else { // We need to append values to the expanded array for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) { TransformUIExpanded.Add(Index == AtIndex ? true : false); } } #endif } bool UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) { #if WITH_EDITORONLY_DATA return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; #else return false; #endif }; FTransform* UHoudiniInput::GetTransformOffset(const int32& AtIndex) { UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); if (InObject) return &(InObject->Transform); return nullptr; } const FTransform UHoudiniInput::GetTransformOffset(const int32& AtIndex) const { const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); if (InObject) return InObject->Transform; return FTransform::Identity; } TOptional UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetLocation().X; } TOptional UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetLocation().Y; } TOptional UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetLocation().Z; } TOptional UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const { return GetTransformOffset(AtIndex).Rotator().Roll; } TOptional UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const { return GetTransformOffset(AtIndex).Rotator().Pitch; } TOptional UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const { return GetTransformOffset(AtIndex).Rotator().Yaw; } TOptional UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetScale3D().X; } TOptional UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetScale3D().Y; } TOptional UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const { return GetTransformOffset(AtIndex).GetScale3D().Z; } bool UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) { FTransform* Transform = GetTransformOffset(AtIndex); if (!Transform) return false; if (PosRotScaleIndex == 0) { FVector Position = Transform->GetLocation(); if (Position[XYZIndex] == Value) return false; Position[XYZIndex] = Value; Transform->SetLocation(Position); } else if (PosRotScaleIndex == 1) { FRotator Rotator = Transform->Rotator(); switch (XYZIndex) { case 0: { if (Rotator.Roll == Value) return false; Rotator.Roll = Value; break; } case 1: { if (Rotator.Pitch == Value) return false; Rotator.Pitch = Value; break; } case 2: { if (Rotator.Yaw == Value) return false; Rotator.Yaw = Value; break; } } Transform->SetRotation(Rotator.Quaternion()); } else if (PosRotScaleIndex == 2) { FVector Scale = Transform->GetScale3D(); if (Scale[XYZIndex] == Value) return false; Scale[XYZIndex] = Value; Transform->SetScale3D(Scale); } MarkChanged(true); bStaticMeshChanged = true; return true; } void UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) { if (bAddRotAndScaleAttributesOnCurves == InValue) return; bAddRotAndScaleAttributesOnCurves = InValue; // Mark all input obj as changed MarkAllInputObjectsChanged(true); } #if WITH_EDITOR FText UHoudiniInput::GetCurrentSelectionText() const { FText CurrentSelectionText; switch (Type) { case EHoudiniInputType::Landscape : { if (LandscapeInputObjects.Num() > 0) { UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; UHoudiniInputLandscape* InputLandscape = Cast(InputObject); if (!InputLandscape || InputLandscape->IsPendingKill()) return CurrentSelectionText; ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) return CurrentSelectionText; CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); } } break; case EHoudiniInputType::Asset : { if (AssetInputObjects.Num() > 0) { UHoudiniInputObject* InputObject = AssetInputObjects[0]; UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) return CurrentSelectionText; UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); if (!HAC || HAC->IsPendingKill()) return CurrentSelectionText; UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) return CurrentSelectionText; CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); } } break; default: break; } return CurrentSelectionText; } #endif bool UHoudiniInput::HasLandscapeExportTypeChanged () const { if (Type != EHoudiniInputType::Landscape) return false; return bLandscapeHasExportTypeChanged; } void UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) { if (Type != EHoudiniInputType::Landscape) return; bLandscapeHasExportTypeChanged = InChanged; } bool UHoudiniInput::GetUpdateInputLandscape() const { if (Type != EHoudiniInputType::Landscape) return false; return bUpdateInputLandscape; } void UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) { if (Type != EHoudiniInputType::Landscape) return; bUpdateInputLandscape = bInUpdateInputLandcape; } bool UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() { // Dont do anything if we're not a World Input if (Type != EHoudiniInputType::World) return false; // Build an array of the current selection's bounds TArray AllBBox; for (auto CurrentActor : WorldInputBoundSelectorObjects) { if (!CurrentActor || CurrentActor->IsPendingKill()) continue; AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); } // // Select all actors in our bound selectors bounding boxes // // Get our parent component/actor USceneComponent* ParentComponent = Cast(GetOuter()); AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); UWorld* MyWorld = GetWorld(); TArray NewSelectedActors; for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) { AActor *CurrentActor = *ActorItr; if (!CurrentActor || CurrentActor->IsPendingKill()) continue; // Check that actor is currently not selected if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) continue; // Ignore the SkySpheres? FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); if (ClassName.Contains("BP_Sky_Sphere")) continue; // Don't allow selection of ourselves. Bad things happen if we do. if (ParentActor && (CurrentActor == ParentActor)) continue; // For BrushActors, both the actor and its brush must be valid ABrush* BrushActor = Cast(CurrentActor); if (BrushActor) { if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) continue; } FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); for (auto InBounds : AllBBox) { // Check if both actor's bounds intersects if (!ActorBounds.Intersect(InBounds)) continue; NewSelectedActors.Add(CurrentActor); break; } } return UpdateWorldSelection(NewSelectedActors); } bool UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) { TArray NewSelectedActors = InNewSelection; // Update our current selection with the new one // Keep actors that are still selected, remove the one that are not selected anymore bool bHasSelectionChanged = false; for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) { UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; if (CurActor && NewSelectedActors.Contains(CurActor)) { // The actor is still selected, remove it from the new selection NewSelectedActors.Remove(CurActor); } else { // That actor is no longer selected, remove itr from our current selection DeleteInputObjectAt(EHoudiniInputType::World, Idx); bHasSelectionChanged = true; } } if (NewSelectedActors.Num() > 0) bHasSelectionChanged = true; // Then add the newly selected Actors int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); for (const auto& CurActor : NewSelectedActors) { // Update the input objects from the valid selected actors array SetInputObjectAt(InputObjectIdx++, CurActor); } MarkChanged(bHasSelectionChanged); return bHasSelectionChanged; } bool UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const { if (!InObject || InObject->IsPendingKill()) return false; // Returns true if the object is one of our input object for the given type const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); if (!ObjectArray) return false; for (auto& CurrentInputObject : (*ObjectArray)) { if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) continue; if (CurrentInputObject->GetObject() == InObject) return true; } return false; } void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const { for(UHoudiniInputObject* InputObject : GeometryInputObjects) { Fn(InputObject); } for(UHoudiniInputObject* InputObject : AssetInputObjects) { Fn(InputObject); } for(UHoudiniInputObject* InputObject : CurveInputObjects) { Fn(InputObject); } for(UHoudiniInputObject* InputObject : LandscapeInputObjects) { Fn(InputObject); } for(UHoudiniInputObject* InputObject : WorldInputObjects) { Fn(InputObject); } for(UHoudiniInputObject* InputObject : SkeletalInputObjects) { Fn(InputObject); } } TArray*> UHoudiniInput::GetAllObjectArrays() const { return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; } TArray*> UHoudiniInput::GetAllObjectArrays() { return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; } void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const { TArray*> ObjectArrays = GetAllObjectArrays(); for (const TArray* ObjectArrayPtr : ObjectArrays) { if (!ObjectArrayPtr) continue; Fn(*ObjectArrayPtr); } } void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) { TArray*> ObjectArrays = GetAllObjectArrays(); for (TArray* ObjectArrayPtr : ObjectArrays) { if (!ObjectArrayPtr) continue; Fn(*ObjectArrayPtr); } } void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const { OutObjects.Empty(); auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) { if (InputObject) OutObjects.Add(InputObject); }; ForAllHoudiniInputObjects(AddInputObject); } void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const { auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) { if (!InputObject) return; UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); if (!SceneComponentInput) return; Fn(SceneComponentInput); }; ForAllHoudiniInputObjects(ProcessSceneComponent); } void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const { OutObjects.Empty(); auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) { if (!InputObject) return; UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); if (!SceneComponentInput) return; OutObjects.Add(SceneComponentInput); }; ForAllHoudiniInputObjects(AddSceneComponent); } void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const { OutObjects.Empty(); auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) { if (!InputObject) return; UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); if (!SceneComponentInput) return; OutObjects.Add(SceneComponentInput); }; ForAllHoudiniInputObjects(AddSceneComponent); } void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) { if (!InInputObject) return; ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { ObjectArray.Remove(InInputObject); }); return; }