6701 lines
242 KiB
C++
6701 lines
242 KiB
C++
/*
|
|
* 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 "HoudiniAssetComponent.h"
|
|
|
|
#include "HoudiniApi.h"
|
|
#include "HoudiniEngineRuntimePrivatePCH.h"
|
|
#include "HoudiniEngineUtils.h"
|
|
#include "HoudiniEngineBakeUtils.h"
|
|
#include "HoudiniEngineMaterialUtils.h"
|
|
#include "HoudiniEngine.h"
|
|
#include "HoudiniAsset.h"
|
|
#include "HoudiniAssetActor.h"
|
|
#include "HoudiniAssetComponentMaterials.h"
|
|
#include "HoudiniAssetInstanceInput.h"
|
|
#include "HoudiniAssetInput.h"
|
|
#include "HoudiniAssetParameter.h"
|
|
#include "HoudiniAssetParameterButton.h"
|
|
#include "HoudiniAssetParameterChoice.h"
|
|
#include "HoudiniAssetParameterColor.h"
|
|
#include "HoudiniAssetParameterFloat.h"
|
|
#include "HoudiniAssetParameterFolder.h"
|
|
#include "HoudiniAssetParameterFolderList.h"
|
|
#include "HoudiniAssetParameterInt.h"
|
|
#include "HoudiniAssetParameterLabel.h"
|
|
#include "HoudiniAssetParameterMultiparm.h"
|
|
#include "HoudiniAssetParameterSeparator.h"
|
|
#include "HoudiniAssetParameterString.h"
|
|
#include "HoudiniAssetParameterFile.h"
|
|
#include "HoudiniAssetParameterToggle.h"
|
|
#include "HoudiniAssetParameterRamp.h"
|
|
#include "HoudiniHandleComponent.h"
|
|
#include "HoudiniSplineComponent.h"
|
|
#include "HoudiniEngineTask.h"
|
|
#include "HoudiniEngineTaskInfo.h"
|
|
#include "HoudiniAssetComponentMaterials.h"
|
|
#include "HoudiniPluginSerializationVersion.h"
|
|
#include "HoudiniEngineString.h"
|
|
#include "HoudiniAssetInstanceInputField.h"
|
|
#include "HoudiniInstancedActorComponent.h"
|
|
#include "HoudiniMeshSplitInstancerComponent.h"
|
|
#include "HoudiniParamUtils.h"
|
|
#include "HoudiniLandscapeUtils.h"
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
#include "Landscape.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Misc/UObjectToken.h"
|
|
#include "LandscapeInfo.h"
|
|
#include "LandscapeLayerInfoObject.h"
|
|
#include "Materials/MaterialInstance.h"
|
|
#include "Engine/StaticMeshSocket.h"
|
|
#include "HoudiniCookHandler.h"
|
|
#include "UObject/MetaData.h"
|
|
#if WITH_EDITOR
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Editor.h"
|
|
#include "EdMode.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorModes.h"
|
|
#include "Editor/PropertyEditor/Public/IDetailsView.h"
|
|
#include "Editor/UnrealEd/Private/GeomFitUtils.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Editor/UnrealEd/Public/BusyCursor.h"
|
|
#include "Editor/UnrealEd/Public/AssetThumbnail.h"
|
|
#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h"
|
|
#include "Editor/PropertyEditor/Private/PropertyNode.h"
|
|
#include "Editor/PropertyEditor/Private/SDetailsViewBase.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "NavigationSystem.h"
|
|
#include "FileHelpers.h"
|
|
#endif
|
|
#include "Internationalization/Internationalization.h"
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
|
|
|
|
#if WITH_EDITOR
|
|
|
|
/** Slate widget used to pick an asset to instantiate from an HDA with multiple assets inside. **/
|
|
class SAssetSelectionWidget : public SCompoundWidget
|
|
{
|
|
public:
|
|
SLATE_BEGIN_ARGS( SAssetSelectionWidget )
|
|
: _WidgetWindow(), _AvailableAssetNames()
|
|
{}
|
|
|
|
SLATE_ARGUMENT(TSharedPtr<SWindow>, WidgetWindow )
|
|
SLATE_ARGUMENT( TArray< HAPI_StringHandle >, AvailableAssetNames )
|
|
SLATE_END_ARGS()
|
|
|
|
public:
|
|
|
|
SAssetSelectionWidget();
|
|
|
|
public:
|
|
|
|
/** Widget construct. **/
|
|
void Construct( const FArguments & InArgs );
|
|
|
|
/** Return true if cancel button has been pressed. **/
|
|
bool IsCancelled() const;
|
|
|
|
/** Return true if constructed widget is valid. **/
|
|
bool IsValidWidget() const;
|
|
|
|
/** Return selected asset name. **/
|
|
int32 GetSelectedAssetName() const;
|
|
|
|
protected:
|
|
|
|
/** Called when Ok button is pressed. **/
|
|
FReply OnButtonOk();
|
|
|
|
/** Called when Cancel button is pressed. **/
|
|
FReply OnButtonCancel();
|
|
|
|
/** Called when user picks an asset. **/
|
|
FReply OnButtonAssetPick( int32 AssetName );
|
|
|
|
protected:
|
|
|
|
/** Parent widget window. **/
|
|
TWeakPtr< SWindow > WidgetWindow;
|
|
|
|
/** List of available Houdini Engine asset names. **/
|
|
TArray< HAPI_StringHandle > AvailableAssetNames;
|
|
|
|
/** Selected asset name. **/
|
|
int32 SelectedAssetName;
|
|
|
|
/** Is set to true if constructed widget is valid. **/
|
|
bool bIsValidWidget;
|
|
|
|
/** Is set to true if selection process has been cancelled. **/
|
|
bool bIsCancelled;
|
|
};
|
|
|
|
SAssetSelectionWidget::SAssetSelectionWidget()
|
|
: SelectedAssetName( -1 )
|
|
, bIsValidWidget( false )
|
|
, bIsCancelled( false )
|
|
{}
|
|
|
|
bool
|
|
SAssetSelectionWidget::IsCancelled() const
|
|
{
|
|
return bIsCancelled;
|
|
}
|
|
|
|
bool
|
|
SAssetSelectionWidget::IsValidWidget() const
|
|
{
|
|
return bIsValidWidget;
|
|
}
|
|
|
|
int32
|
|
SAssetSelectionWidget::GetSelectedAssetName() const
|
|
{
|
|
return SelectedAssetName;
|
|
}
|
|
|
|
void
|
|
SAssetSelectionWidget::Construct( const FArguments & InArgs )
|
|
{
|
|
WidgetWindow = InArgs._WidgetWindow;
|
|
AvailableAssetNames = InArgs._AvailableAssetNames;
|
|
|
|
TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox );
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew( SBorder )
|
|
.BorderImage( FEditorStyle::GetBrush( TEXT( "Menu.Background" ) ) )
|
|
.Content()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.HAlign( HAlign_Center )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SAssignNew( VerticalBox, SVerticalBox )
|
|
]
|
|
]
|
|
];
|
|
|
|
for ( int32 AssetNameIdx = 0, AssetNameNum = AvailableAssetNames.Num(); AssetNameIdx < AssetNameNum; ++AssetNameIdx )
|
|
{
|
|
FString AssetNameString = TEXT( "" );
|
|
HAPI_StringHandle AssetName = AvailableAssetNames[ AssetNameIdx ];
|
|
|
|
FHoudiniEngineString HoudiniEngineString( AssetName );
|
|
if ( HoudiniEngineString.ToFString( AssetNameString ) )
|
|
{
|
|
bIsValidWidget = true;
|
|
FText AssetNameStringText = FText::FromString( AssetNameString );
|
|
|
|
VerticalBox->AddSlot()
|
|
.HAlign( HAlign_Center )
|
|
.AutoHeight()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.HAlign( HAlign_Center )
|
|
.VAlign( VAlign_Center )
|
|
.Padding( 2.0f, 4.0f )
|
|
[
|
|
SNew( SButton )
|
|
.VAlign( VAlign_Center )
|
|
.HAlign( HAlign_Center )
|
|
.OnClicked( this, &SAssetSelectionWidget::OnButtonAssetPick, AssetName )
|
|
.Text( AssetNameStringText )
|
|
.ToolTipText( AssetNameStringText )
|
|
]
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply
|
|
SAssetSelectionWidget::OnButtonAssetPick( int32 AssetName )
|
|
{
|
|
SelectedAssetName = AssetName;
|
|
|
|
if (TSharedPtr<SWindow> WindowPtr = WidgetWindow.Pin())
|
|
{
|
|
WindowPtr->HideWindow();
|
|
WindowPtr->RequestDestroyWindow();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
SAssetSelectionWidget::OnButtonOk()
|
|
{
|
|
if (TSharedPtr<SWindow> WindowPtr = WidgetWindow.Pin())
|
|
{
|
|
WindowPtr->HideWindow();
|
|
WindowPtr->RequestDestroyWindow();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
SAssetSelectionWidget::OnButtonCancel()
|
|
{
|
|
bIsCancelled = true;
|
|
|
|
if (TSharedPtr<SWindow> WindowPtr = WidgetWindow.Pin())
|
|
{
|
|
WindowPtr->HideWindow();
|
|
WindowPtr->RequestDestroyWindow();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// Macro to update given property on all components.
|
|
#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \
|
|
do \
|
|
{ \
|
|
TArray< UActorComponent * > ReregisterComponents; \
|
|
TArray< USceneComponent * > LocalAttachChildren;\
|
|
GetChildrenComponents(true, LocalAttachChildren); \
|
|
for ( TArray< USceneComponent * >::TConstIterator Iter( LocalAttachChildren ); Iter; ++Iter ) \
|
|
{ \
|
|
COMPONENT_CLASS * Component = Cast< COMPONENT_CLASS >( *Iter ); \
|
|
if ( Component ) \
|
|
{ \
|
|
Component->PROPERTY = PROPERTY; \
|
|
ReregisterComponents.Add( Component ); \
|
|
} \
|
|
} \
|
|
\
|
|
if ( ReregisterComponents.Num() > 0 ) \
|
|
{ \
|
|
FMultiComponentReregisterContext MultiComponentReregisterContext( ReregisterComponents ); \
|
|
} \
|
|
} \
|
|
while( 0 )
|
|
|
|
bool
|
|
UHoudiniAssetComponent::bDisplayEngineNotInitialized = true;
|
|
|
|
bool
|
|
UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch = true;
|
|
|
|
UHoudiniAssetComponent::UHoudiniAssetComponent( const FObjectInitializer & ObjectInitializer )
|
|
: Super( ObjectInitializer )
|
|
{
|
|
HoudiniAsset = nullptr;
|
|
bManualRecookRequested = false;
|
|
PreviousTransactionHoudiniAsset = nullptr;
|
|
HoudiniAssetComponentMaterials = nullptr;
|
|
#if WITH_EDITOR
|
|
CopiedHoudiniComponent = nullptr;
|
|
#endif
|
|
AssetId = -1;
|
|
GeneratedGeometryScaleFactor = HAPI_UNREAL_SCALE_FACTOR_POSITION;
|
|
TransformScaleFactor = HAPI_UNREAL_SCALE_FACTOR_TRANSLATION;
|
|
ImportAxis = HRSAI_Unreal;
|
|
HapiNotificationStarted = 0.0;
|
|
AssetCookCount = 0;
|
|
HoudiniAssetComponentTransientFlagsPacked = 0u;
|
|
|
|
/** Component flags. **/
|
|
HoudiniAssetComponentFlagsPacked = 0u;
|
|
bEnableCooking = true;
|
|
bUploadTransformsToHoudiniEngine = true;
|
|
bUseHoudiniMaterials = true;
|
|
bCookingTriggersDownstreamCooks = true;
|
|
|
|
UObject * Object = ObjectInitializer.GetObj();
|
|
UObject * ObjectOuter = Object->GetOuter();
|
|
|
|
if ( ObjectOuter->IsA( AHoudiniAssetActor::StaticClass() ) )
|
|
bIsNativeComponent = true;
|
|
|
|
// Set scaling information.
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if ( HoudiniRuntimeSettings )
|
|
{
|
|
GeneratedGeometryScaleFactor = HoudiniRuntimeSettings->GeneratedGeometryScaleFactor;
|
|
TransformScaleFactor = HoudiniRuntimeSettings->TransformScaleFactor;
|
|
ImportAxis = HoudiniRuntimeSettings->ImportAxis;
|
|
}
|
|
|
|
// Set component properties.
|
|
Mobility = EComponentMobility::Static;
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
bTickInEditor = true;
|
|
SetGenerateOverlapEvents(false);
|
|
|
|
// Similar to UMeshComponent.
|
|
CastShadow = true;
|
|
bUseAsOccluder = true;
|
|
bCanEverAffectNavigation = true;
|
|
|
|
// This component requires render update.
|
|
bNeverNeedsRenderUpdate = false;
|
|
|
|
// Initialize static mesh generation parameters.
|
|
bGeneratedDoubleSidedGeometry = false;
|
|
GeneratedPhysMaterial = nullptr;
|
|
DefaultBodyInstance.SetCollisionProfileName("BlockAll");
|
|
GeneratedCollisionTraceFlag = CTF_UseDefault;
|
|
GeneratedLpvBiasMultiplier = 1.0f;
|
|
GeneratedLightMapResolution = 32;
|
|
GeneratedLightMapCoordinateIndex = 1;
|
|
bGeneratedUseMaximumStreamingTexelRatio = false;
|
|
GeneratedStreamingDistanceMultiplier = 1.0f;
|
|
GeneratedDistanceFieldResolutionScale = 0.0f;
|
|
|
|
bNeedToUpdateNavigationSystem = false;
|
|
|
|
// Make an invalid GUID, since we do not have any cooking requests.
|
|
HapiGUID.Invalidate();
|
|
|
|
// Create unique component GUID.
|
|
ComponentGUID = FGuid::NewGuid();
|
|
|
|
bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
bFullyLoaded = false;
|
|
|
|
Bounds = FBox( ForceInitToZero );
|
|
|
|
DefaultPresetBuffer.Empty();
|
|
PresetBuffer.Empty();
|
|
Parameters.Empty();
|
|
ParameterByName.Empty();
|
|
BakeNameOverrides.Empty();
|
|
DownstreamAssetConnections.Empty();
|
|
}
|
|
|
|
UHoudiniAssetComponent::~UHoudiniAssetComponent()
|
|
{}
|
|
|
|
void
|
|
UHoudiniAssetComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( InThis );
|
|
|
|
if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() )
|
|
{
|
|
// Add references for all parameters.
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator
|
|
IterParams( HoudiniAssetComponent->Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() )
|
|
Collector.AddReferencedObject( HoudiniAssetParameter, InThis );
|
|
}
|
|
|
|
// Add references to all inputs.
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator
|
|
IterInputs( HoudiniAssetComponent->Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() )
|
|
Collector.AddReferencedObject( HoudiniAssetInput, InThis );
|
|
}
|
|
|
|
// Add references to all instance inputs.
|
|
for ( auto& InstanceInput : HoudiniAssetComponent->InstanceInputs)
|
|
{
|
|
if ( !InstanceInput || InstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
Collector.AddReferencedObject( InstanceInput, InThis );
|
|
}
|
|
|
|
// Add references to all handles.
|
|
for ( TMap< FString, UHoudiniHandleComponent * >::TIterator
|
|
IterHandles( HoudiniAssetComponent->HandleComponents ); IterHandles; ++IterHandles )
|
|
{
|
|
UHoudiniHandleComponent * HandleComponent = IterHandles.Value();
|
|
if ( HandleComponent && !HandleComponent->IsPendingKill() )
|
|
Collector.AddReferencedObject( HandleComponent, InThis );
|
|
}
|
|
|
|
// Add references to all static meshes and corresponding geo parts.
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator
|
|
Iter( HoudiniAssetComponent->StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( StaticMesh && StaticMesh->IsValidLowLevel() && !StaticMesh->IsPendingKill() )
|
|
Collector.AddReferencedObject( StaticMesh, InThis );
|
|
}
|
|
|
|
// Add references to all static meshes and their static mesh components.
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator
|
|
Iter( HoudiniAssetComponent->StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Key();
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
|
|
if (!StaticMesh || !StaticMeshComponent->IsValidLowLevel() || StaticMesh->IsPendingKill())
|
|
continue;
|
|
|
|
Collector.AddReferencedObject( StaticMesh, InThis );
|
|
|
|
if (!StaticMeshComponent || !StaticMeshComponent->IsValidLowLevel() || StaticMeshComponent->IsPendingKill())
|
|
continue;
|
|
|
|
Collector.AddReferencedObject( StaticMeshComponent, InThis );
|
|
}
|
|
|
|
// Add references to all temporary cooked mesh packages
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<class UPackage> > ::TIterator
|
|
Iter(HoudiniAssetComponent->CookedTemporaryStaticMeshPackages); Iter; ++Iter)
|
|
{
|
|
UPackage * MeshPackage = Iter.Value().Get();
|
|
if (MeshPackage && !MeshPackage->IsPendingKill())
|
|
Collector.AddReferencedObject(MeshPackage, InThis);
|
|
}
|
|
|
|
// Add references to all spline components.
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator
|
|
Iter( HoudiniAssetComponent->SplineComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value();
|
|
if ( !HoudiniSplineComponent || !HoudiniSplineComponent->IsValidLowLevel() || HoudiniSplineComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
Collector.AddReferencedObject( HoudiniSplineComponent, InThis );
|
|
}
|
|
|
|
// Add references to all Landscape
|
|
for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr <ALandscapeProxy> >::TIterator
|
|
Iter( HoudiniAssetComponent->LandscapeComponents ); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value().Get();
|
|
if ( !HoudiniLandscape || HoudiniLandscape->IsPendingKill() || !HoudiniLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
Collector.AddReferencedObject( HoudiniLandscape, InThis );
|
|
}
|
|
|
|
// Add references to all generated Landscape layer objects
|
|
for ( TMap< TWeakObjectPtr<class UPackage>, FHoudiniGeoPartObject > ::TIterator
|
|
Iter( HoudiniAssetComponent->CookedTemporaryLandscapeLayers ); Iter; ++Iter )
|
|
{
|
|
UPackage * LayerPackage = Iter.Key().Get();
|
|
if ( LayerPackage && !LayerPackage->IsPendingKill() )
|
|
Collector.AddReferencedObject( LayerPackage, InThis );
|
|
}
|
|
|
|
// Retrieve asset associated with this component and add reference to it.
|
|
// Also do not add reference if it is being referenced by preview component.
|
|
UHoudiniAsset * HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset();
|
|
if ( HoudiniAsset && !HoudiniAsset->IsPendingKill() && !HoudiniAssetComponent->bIsPreviewComponent )
|
|
{
|
|
// Manually add a reference to Houdini asset from this component.
|
|
Collector.AddReferencedObject( HoudiniAsset, InThis );
|
|
}
|
|
|
|
// Add reference to material replacements.
|
|
UHoudiniAssetComponentMaterials* HoudiniAssetComponentMaterials = HoudiniAssetComponent->HoudiniAssetComponentMaterials;
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
Collector.AddReferencedObject( HoudiniAssetComponent->HoudiniAssetComponentMaterials, InThis );
|
|
|
|
// Add references to all temporary cooked material packages
|
|
for (TMap< FString, TWeakObjectPtr<class UPackage> > ::TIterator
|
|
Iter(HoudiniAssetComponent->CookedTemporaryPackages); Iter; ++Iter)
|
|
{
|
|
UPackage * MaterialPackage = Iter.Value().Get();
|
|
if (MaterialPackage && !MaterialPackage->IsPendingKill())
|
|
Collector.AddReferencedObject(MaterialPackage, InThis);
|
|
}
|
|
|
|
}
|
|
|
|
// Call base implementation.
|
|
Super::AddReferencedObjects( InThis, Collector );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetNative( bool InbIsNativeComponent )
|
|
{
|
|
bIsNativeComponent = InbIsNativeComponent;
|
|
}
|
|
|
|
HAPI_NodeId
|
|
UHoudiniAssetComponent::GetAssetId() const
|
|
{
|
|
return AssetId;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetAssetId( HAPI_NodeId InAssetId )
|
|
{
|
|
AssetId = InAssetId;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasValidAssetId() const
|
|
{
|
|
return FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId );
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsComponentValid() const
|
|
{
|
|
if( !IsValidLowLevel() )
|
|
return false;
|
|
|
|
if ( IsTemplate() )
|
|
return false;
|
|
|
|
if ( IsPendingKillOrUnreachable() )
|
|
return false;
|
|
|
|
if ( !GetOuter() ) //|| !GetOuter()->GetLevel() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
UHoudiniAsset *
|
|
UHoudiniAssetComponent::GetHoudiniAsset() const
|
|
{
|
|
return HoudiniAsset;
|
|
}
|
|
|
|
|
|
AHoudiniAssetActor*
|
|
UHoudiniAssetComponent::GetHoudiniAssetActorOwner() const
|
|
{
|
|
return Cast< AHoudiniAssetActor >( GetOwner() );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetHoudiniAsset( UHoudiniAsset * InHoudiniAsset )
|
|
{
|
|
// If it is the same asset, do nothing.
|
|
if ( InHoudiniAsset == HoudiniAsset )
|
|
return;
|
|
|
|
UHoudiniAsset * Asset = nullptr;
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast< AHoudiniAssetActor >( GetOwner() );
|
|
|
|
HoudiniAsset = InHoudiniAsset;
|
|
|
|
// Reset material tracking.
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
HoudiniAssetComponentMaterials->ResetMaterialInfo();
|
|
|
|
if ( !bIsNativeComponent )
|
|
return;
|
|
|
|
#if WITH_EDITOR
|
|
|
|
// Houdini Asset has been changed, we need to reset corresponding HDA and relevant resources.
|
|
ResetHoudiniResources();
|
|
|
|
#endif
|
|
|
|
// Clear all created parameters.
|
|
ClearParameters();
|
|
|
|
// Clear all inputs.
|
|
ClearInputs();
|
|
|
|
// Clear all instance inputs.
|
|
ClearInstanceInputs();
|
|
|
|
// Release all curve related resources.
|
|
ClearCurves();
|
|
|
|
// Clear all handles.
|
|
ClearHandles();
|
|
|
|
// Clear all landscapes.
|
|
ClearLandscapes();
|
|
|
|
// Set Houdini logo to be default geometry.
|
|
ReleaseObjectGeoPartResources( StaticMeshes );
|
|
StaticMeshes.Empty();
|
|
StaticMeshComponents.Empty();
|
|
CreateStaticMeshHoudiniLogoResource( StaticMeshes );
|
|
|
|
bIsPreviewComponent = false;
|
|
if ( !InHoudiniAsset )
|
|
{
|
|
#if WITH_EDITOR
|
|
UpdateEditorProperties( false );
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if ( HoudiniAssetActor )
|
|
bIsPreviewComponent = HoudiniAssetActor->IsUsedForPreview();
|
|
|
|
if( !bIsNativeComponent )
|
|
bLoadedComponent = false;
|
|
|
|
// Get instance of Houdini Engine.
|
|
FHoudiniEngine & HoudiniEngine = FHoudiniEngine::Get();
|
|
|
|
#if WITH_EDITOR
|
|
|
|
if ( !bIsPreviewComponent )
|
|
{
|
|
// If the fist session on the engine was never initialized, do it now
|
|
if (!FHoudiniEngine::Get().GetFirstSessionCreated())
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("First initialization of the Houdini Engine session"));
|
|
FHoudiniEngine::Get().RestartSession();
|
|
}
|
|
|
|
if ( HoudiniEngine.IsInitialized() )
|
|
{
|
|
if ( !bLoadedComponent )
|
|
{
|
|
// If component is not a loaded component, instantiate and start ticking.
|
|
StartTaskAssetInstantiation( false, true );
|
|
}
|
|
else if ( bTransactionAssetChange )
|
|
{
|
|
// If assigned asset is transacted asset, instantiate and start ticking. Also treat as loaded component.
|
|
StartTaskAssetInstantiation( true, true );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch && HoudiniEngine.CheckHapiVersionMismatch() )
|
|
{
|
|
// We have mismatch in defined and running versions.
|
|
int32 RunningEngineMajor = 0;
|
|
int32 RunningEngineMinor = 0;
|
|
int32 RunningEngineApi = 0;
|
|
|
|
const HAPI_Session * Session = FHoudiniEngine::Get().GetSession();
|
|
|
|
// Retrieve version numbers for running Houdini Engine.
|
|
FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR, &RunningEngineMajor );
|
|
FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR, &RunningEngineMinor );
|
|
FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API, &RunningEngineApi );
|
|
|
|
// Get platform specific name of libHAPI.
|
|
FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName();
|
|
|
|
// Some sanity checks.
|
|
int32 BuiltEngineMajor = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_MAJOR );
|
|
int32 BuiltEngineMinor = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_MINOR );
|
|
int32 BuiltEngineApi = FMath::Max( 0, HAPI_VERSION_HOUDINI_ENGINE_API );
|
|
|
|
FString WarningMessage = FString::Printf(
|
|
TEXT( "Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d mismatch. " )
|
|
TEXT( "%s was loaded, but has wrong version. " )
|
|
TEXT( "No cooking / instantiation will take place." ),
|
|
BuiltEngineMajor,
|
|
BuiltEngineMinor,
|
|
BuiltEngineApi,
|
|
RunningEngineMajor,
|
|
RunningEngineMinor,
|
|
RunningEngineApi,
|
|
*LibHAPIName );
|
|
|
|
FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" );
|
|
FText WarningTitleText = FText::FromString( WarningTitle );
|
|
FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText );
|
|
|
|
UHoudiniAssetComponent::bDisplayEngineHapiVersionMismatch = false;
|
|
}
|
|
else if ( UHoudiniAssetComponent::bDisplayEngineNotInitialized )
|
|
{
|
|
// Get platform specific name of libHAPI.
|
|
FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName();
|
|
|
|
// If this is first time component is instantiated and we do not have Houdini Engine initialized.
|
|
FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" );
|
|
FText WarningTitleText = FText::FromString( WarningTitle );
|
|
FString WarningMessage = FString::Printf(
|
|
TEXT( "Houdini Installation was not detected." )
|
|
TEXT( "Failed to locate or load %s. " )
|
|
TEXT( "No cooking / instantiation will take place." ),
|
|
*LibHAPIName );
|
|
|
|
FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText );
|
|
|
|
UHoudiniAssetComponent::bDisplayEngineNotInitialized = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::AddDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex )
|
|
{
|
|
if ( InDownstreamAssetComponent && !InDownstreamAssetComponent->IsPendingKill() )
|
|
{
|
|
if ( DownstreamAssetConnections.Contains( InDownstreamAssetComponent ) )
|
|
{
|
|
TSet< int32 > & InputIndicesSet = DownstreamAssetConnections[ InDownstreamAssetComponent ];
|
|
InputIndicesSet.Add( InInputIndex );
|
|
}
|
|
else
|
|
{
|
|
TSet< int32 > InputIndicesSet;
|
|
InputIndicesSet.Add( InInputIndex );
|
|
DownstreamAssetConnections.Add( InDownstreamAssetComponent, InputIndicesSet );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::RemoveDownstreamAsset( UHoudiniAssetComponent * InDownstreamAssetComponent, int32 InInputIndex )
|
|
{
|
|
if ( !InDownstreamAssetComponent || InDownstreamAssetComponent->IsPendingKill() )
|
|
return;
|
|
|
|
if ( DownstreamAssetConnections.Contains( InDownstreamAssetComponent ) )
|
|
{
|
|
TSet< int32 > & InputIndicesSet = DownstreamAssetConnections[ InDownstreamAssetComponent ];
|
|
if ( InputIndicesSet.Contains( InInputIndex ) )
|
|
InputIndicesSet.Remove( InInputIndex );
|
|
|
|
// Remove the downstream asset if we're not link to at least one of its input
|
|
if (InputIndicesSet.Num() <= 0)
|
|
DownstreamAssetConnections.Remove(InDownstreamAssetComponent);
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateObjectGeoPartResources(
|
|
TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap )
|
|
{
|
|
// Reset Houdini logo flag.
|
|
bContainsHoudiniLogoGeometry = false;
|
|
|
|
// We need to store instancers as they need to be processed after all other meshes.
|
|
TArray< FHoudiniGeoPartObject > FoundInstancers;
|
|
TArray< FHoudiniGeoPartObject > FoundCurves;
|
|
TArray< FHoudiniGeoPartObject > FoundVolumes;
|
|
TMap< FHoudiniGeoPartObject, UStaticMesh* > StaleParts;
|
|
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshMap ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
|
|
if ( HoudiniGeoPartObject.IsInstancer() )
|
|
{
|
|
FoundInstancers.Add( HoudiniGeoPartObject );
|
|
}
|
|
else if ( HoudiniGeoPartObject.IsPackedPrimitiveInstancer() )
|
|
{
|
|
// Packed Primitives should be processed before other instancer in case they are instanced by the same
|
|
FoundInstancers.Insert( HoudiniGeoPartObject, 0 );
|
|
}
|
|
else if ( HoudiniGeoPartObject.IsCurve() )
|
|
{
|
|
// This geo part is a curve and has no mesh assigned.
|
|
check( !StaticMesh );
|
|
FoundCurves.Add( HoudiniGeoPartObject );
|
|
}
|
|
else if ( HoudiniGeoPartObject.IsVolume() )
|
|
{
|
|
FoundVolumes.Add( HoudiniGeoPartObject );
|
|
}
|
|
else
|
|
{
|
|
// This geo part is visible and not an instancer and must have static mesh assigned.
|
|
if ( HoudiniGeoPartObject.IsVisible() && ( !StaticMesh || StaticMesh->IsPendingKill() ) )
|
|
{
|
|
HOUDINI_LOG_WARNING( TEXT( "No static mesh generated for visible part %d,%d,%d" ), HoudiniGeoPartObject.AssetId, HoudiniGeoPartObject.ObjectId, HoudiniGeoPartObject.PartId );
|
|
continue;
|
|
}
|
|
|
|
UStaticMeshComponent * StaticMeshComponent = nullptr;
|
|
UStaticMeshComponent * FoundStaticMeshComponent = LocateStaticMeshComponent( StaticMesh );
|
|
|
|
if ( FoundStaticMeshComponent && !FoundStaticMeshComponent->IsPendingKill() )
|
|
{
|
|
StaticMeshComponent = FoundStaticMeshComponent;
|
|
if ( !HoudiniGeoPartObject.IsVisible() && !HoudiniGeoPartObject.IsCollidable())
|
|
{
|
|
// We have a mesh and component for a part which is invisible.
|
|
// Visibility may have changed since last cook
|
|
StaleParts.Add( HoudiniGeoPartObject, StaticMesh );
|
|
continue;
|
|
}
|
|
}
|
|
else if ( HoudiniGeoPartObject.IsVisible() || HoudiniGeoPartObject.IsCollidable())
|
|
{
|
|
// Create necessary component.
|
|
StaticMeshComponent = NewObject< UStaticMeshComponent >(
|
|
GetOwner() ? GetOwner() : GetOuter(), UStaticMeshComponent::StaticClass(),
|
|
NAME_None, RF_Transactional );
|
|
|
|
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
|
|
{
|
|
StaticMeshComponent->SetStaticMesh(StaticMesh);
|
|
StaticMeshComponent->SetVisibility(HoudiniGeoPartObject.IsVisible());
|
|
StaticMeshComponent->SetMobility(Mobility);
|
|
|
|
// Property propagation: set the new SMC's properties to the HAC's current settings
|
|
CopyComponentPropertiesTo(StaticMeshComponent);
|
|
|
|
// Register the component AFTER copying the properties!
|
|
StaticMeshComponent->RegisterComponent();
|
|
|
|
// Attach created static mesh component to our Houdini component.
|
|
StaticMeshComponent->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
|
|
|
|
// Add to the map of components.
|
|
StaticMeshComponents.Add(StaticMesh, StaticMeshComponent);
|
|
}
|
|
}
|
|
|
|
// No need to change visible/collision/uproperties if we're not fully loaded,
|
|
// as this likely means that we're in the process of being deserialized...
|
|
if (bFullyLoaded && StaticMeshComponent && !StaticMeshComponent->IsPendingKill())
|
|
{
|
|
// If this is a collision geo, we need to make it invisible.
|
|
if (HoudiniGeoPartObject.IsCollidable())
|
|
{
|
|
StaticMeshComponent->SetVisibility( false );
|
|
StaticMeshComponent->SetHiddenInGame( true );
|
|
StaticMeshComponent->SetCollisionProfileName( FName( TEXT( "InvisibleWall" ) ) );
|
|
StaticMeshComponent->SetCastShadow(false);
|
|
}
|
|
else
|
|
{
|
|
// Visibility may have changed so we still need to update it
|
|
StaticMeshComponent->SetVisibility( HoudiniGeoPartObject.IsVisible() );
|
|
StaticMeshComponent->SetHiddenInGame( !HoudiniGeoPartObject.IsVisible() );
|
|
}
|
|
|
|
// And we will need to update the navmesh later
|
|
if( HoudiniGeoPartObject.IsCollidable() || HoudiniGeoPartObject.IsRenderCollidable() )
|
|
bNeedToUpdateNavigationSystem = true;
|
|
|
|
// Transform the component by transformation provided by HAPI.
|
|
StaticMeshComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix );
|
|
|
|
// If the static mesh had sockets, we can assign the desired actor to them now
|
|
int32 NumberOfSockets = StaticMesh == nullptr ? 0 : StaticMesh->Sockets.Num();
|
|
for( int32 nSocket = 0; nSocket < NumberOfSockets; nSocket++ )
|
|
{
|
|
UStaticMeshSocket* MeshSocket = StaticMesh->Sockets[ nSocket ];
|
|
if ( MeshSocket && !MeshSocket->IsPendingKill() && ( MeshSocket->Tag.IsEmpty() ) )
|
|
continue;
|
|
|
|
FHoudiniEngineUtils::AddActorsToMeshSocket( StaticMesh->Sockets[ nSocket ], StaticMeshComponent );
|
|
}
|
|
|
|
// Try to update uproperty atributes
|
|
FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( StaticMeshComponent, HoudiniGeoPartObject );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( StaleParts.Num() )
|
|
{
|
|
for ( auto Iter : StaleParts )
|
|
{
|
|
StaticMeshMap.Remove( Iter.Key );
|
|
}
|
|
ReleaseObjectGeoPartResources( StaleParts, true );
|
|
}
|
|
|
|
// Skip self assignment.
|
|
if ( &StaticMeshes != &StaticMeshMap )
|
|
StaticMeshes = StaticMeshMap;
|
|
|
|
#if WITH_EDITOR
|
|
if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) )
|
|
{
|
|
// Create necessary instance inputs.
|
|
CreateInstanceInputs( FoundInstancers );
|
|
|
|
// Create necessary curves.
|
|
CreateCurves( FoundCurves );
|
|
|
|
// Create necessary landscapes
|
|
CreateAllLandscapes( FoundVolumes );
|
|
}
|
|
#endif
|
|
|
|
CleanUpAttachedStaticMeshComponents();
|
|
|
|
// Now that all the Meshes/Landscapes are created, see if we need to create material instances from attributes
|
|
CreateOrUpdateMaterialInstances();
|
|
|
|
// Mobility will be set to static if we generated a Landscape,
|
|
// Or to movable if any of our children is movable
|
|
UpdateMobility();
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::ReleaseObjectGeoPartResources( bool bDeletePackages )
|
|
{
|
|
ReleaseObjectGeoPartResources( StaticMeshes, bDeletePackages );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ReleaseObjectGeoPartResources(
|
|
TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap,
|
|
bool bDeletePackages,
|
|
TMap< FHoudiniGeoPartObject, UStaticMesh * > * pKeepIfContainedIn )
|
|
{
|
|
// Record generated static meshes which we need to delete.
|
|
TArray< UStaticMesh * > StaticMeshesToDelete;
|
|
|
|
// Get Houdini logo.
|
|
UStaticMesh * HoudiniLogoMesh = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get();
|
|
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshMap ); Iter; ++Iter )
|
|
{
|
|
// If we find the new mesh in the existing list, don't destroy the existing static mesh slot
|
|
if ( pKeepIfContainedIn )
|
|
{
|
|
const FHoudiniGeoPartObject& GeoPart = Iter.Key();
|
|
if (LocateStaticMeshByNames(GeoPart, *pKeepIfContainedIn))
|
|
{
|
|
// The old static mesh was found in the new list, no need to destroy it
|
|
continue;
|
|
}
|
|
}
|
|
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
// Removes the static mesh component from the map, detaches and destroys it.
|
|
RemoveStaticMeshComponent( StaticMesh );
|
|
|
|
if ( bDeletePackages && ( StaticMesh != HoudiniLogoMesh ) )
|
|
{
|
|
// Make sure this static mesh is not referenced.
|
|
UObject * ObjectMesh = (UObject *) StaticMesh;
|
|
FReferencerInformationList Referencers;
|
|
|
|
// Check if object is referenced and get its referencers, if it is.
|
|
bool bReferenced = IsReferenced(
|
|
ObjectMesh, GARBAGE_COLLECTION_KEEPFLAGS,
|
|
EInternalObjectFlags::GarbageCollectionKeepFlags, true, &Referencers );
|
|
|
|
if ( !bReferenced || IsObjectReferencedLocally( StaticMesh, Referencers ) )
|
|
{
|
|
// Only delete generated mesh if it has not been saved manually.
|
|
UPackage * Package = Cast< UPackage >( StaticMesh->GetOuter() );
|
|
if ( !Package || !Package->bHasBeenFullyLoaded )
|
|
StaticMeshesToDelete.Add(StaticMesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
// CleanUpAttachedStaticMeshComponents();
|
|
|
|
// Remove unused meshes.
|
|
StaticMeshMap.Empty();
|
|
|
|
#if WITH_EDITOR // Delete no longer used generated static meshes.
|
|
int32 MeshNum = StaticMeshesToDelete.Num();
|
|
|
|
if ( bDeletePackages && MeshNum > 0 )
|
|
{
|
|
for ( int32 MeshIdx = 0; MeshIdx < MeshNum; ++MeshIdx )
|
|
{
|
|
UStaticMesh * StaticMesh = StaticMeshesToDelete[ MeshIdx ];
|
|
|
|
// Free any RHI resources.
|
|
StaticMesh->PreEditChange( nullptr );
|
|
StaticMesh->MarkPendingKill();
|
|
|
|
ObjectTools::DeleteSingleObject( StaticMesh, false );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CleanUpAttachedStaticMeshComponents()
|
|
{
|
|
// Record generated static meshes which we need to delete.
|
|
TArray< UStaticMesh * > StaticMeshesToDelete;
|
|
|
|
// Collect all the static mesh component for this asset
|
|
TMap<const UStaticMeshComponent *, FHoudiniGeoPartObject> AllSMC = CollectAllStaticMeshComponents();
|
|
|
|
// We'll check all the children static mesh components for junk
|
|
const auto & LocalAttachChildren = GetAttachChildren();
|
|
for (TArray< USceneComponent * >::TConstIterator Iter( LocalAttachChildren ); Iter; ++Iter)
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Cast< UStaticMeshComponent >(*Iter);
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
bool bNeedToCleanMeshComponent = false;
|
|
UStaticMesh * StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
|
|
if (AllSMC.Find(StaticMeshComponent) == nullptr)
|
|
bNeedToCleanMeshComponent = true;
|
|
|
|
// Do not clean up component attached to a socket
|
|
if ( StaticMeshComponent->GetAttachSocketName() != NAME_None )
|
|
bNeedToCleanMeshComponent = false;
|
|
|
|
if ( bNeedToCleanMeshComponent )
|
|
{
|
|
// This StaticMeshComponent is attached to the asset but not in the map, and not an instance.
|
|
// It may be a leftover from previous cook/undo/redo and needs to be properly destroyed
|
|
StaticMeshComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform );
|
|
StaticMeshComponent->UnregisterComponent();
|
|
StaticMeshComponent->DestroyComponent();
|
|
|
|
StaticMeshesToDelete.Add( StaticMesh );
|
|
|
|
//HOUDINI_LOG_WARNING( TEXT("CLEANUP: Deleted extra Static Mesh Component for %s"), *(StaticMesh->GetName()) );
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// We'll try to delete the undesirable static meshes too
|
|
for (int32 MeshIdx = 0; MeshIdx < StaticMeshesToDelete.Num(); ++MeshIdx)
|
|
{
|
|
UStaticMesh * StaticMesh = StaticMeshesToDelete[MeshIdx];
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
UObject * ObjectMesh = (UObject *)StaticMesh;
|
|
if ( ObjectMesh->IsUnreachable() )
|
|
continue;
|
|
|
|
// Check if object is referenced and get its referencers, if it is.
|
|
FReferencerInformationList Referencers;
|
|
bool bReferenced = IsReferenced(
|
|
ObjectMesh, GARBAGE_COLLECTION_KEEPFLAGS,
|
|
EInternalObjectFlags::GarbageCollectionKeepFlags, true, &Referencers);
|
|
|
|
if ( !bReferenced || IsObjectReferencedLocally( StaticMesh, Referencers ) )
|
|
{
|
|
// Only delete generated mesh if it has not been saved manually.
|
|
UPackage * Package = Cast< UPackage >( StaticMesh->GetOuter() );
|
|
if ( !Package || !Package->bHasBeenFullyLoaded )
|
|
{
|
|
StaticMesh->PreEditChange( nullptr );
|
|
ObjectTools::DeleteSingleObject( StaticMesh, false );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsObjectReferencedLocally(
|
|
UStaticMesh * StaticMesh,
|
|
FReferencerInformationList & Referencers ) const
|
|
{
|
|
if ( Referencers.InternalReferences.Num() == 0 && Referencers.ExternalReferences.Num() == 1 )
|
|
{
|
|
const FReferencerInformation & ExternalReferencer = Referencers.ExternalReferences[ 0 ];
|
|
if ( ExternalReferencer.Referencer == this )
|
|
{
|
|
// Only referencer is this component.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CollectSubstanceParameters( TMap< FString, UHoudiniAssetParameter * > & SubstanceParameters ) const
|
|
{
|
|
SubstanceParameters.Empty();
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( HoudiniAssetParameter->IsSubstanceParameter() )
|
|
SubstanceParameters.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CollectAllParametersOfType(
|
|
UClass * ParameterClass,
|
|
TMap< FString, UHoudiniAssetParameter * > & ClassParameters ) const
|
|
{
|
|
ClassParameters.Empty();
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill())
|
|
continue;
|
|
|
|
if ( HoudiniAssetParameter->IsA( ParameterClass ) )
|
|
ClassParameters.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter );
|
|
}
|
|
}
|
|
|
|
UHoudiniAssetParameter *
|
|
UHoudiniAssetComponent::FindParameter( const FString & ParameterName ) const
|
|
{
|
|
UHoudiniAssetParameter * const * FoundHoudiniAssetParameter = ParameterByName.Find( ParameterName );
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = nullptr;
|
|
|
|
if ( FoundHoudiniAssetParameter )
|
|
HoudiniAssetParameter = *FoundHoudiniAssetParameter;
|
|
|
|
if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() )
|
|
return nullptr;
|
|
|
|
return HoudiniAssetParameter;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::GetInputs( TArray< UHoudiniAssetInput* >& AllInputs, bool IncludeObjectPathParameter )
|
|
{
|
|
AllInputs.Empty();
|
|
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
AllInputs.Add( HoudiniAssetInput );
|
|
}
|
|
|
|
if ( !IncludeObjectPathParameter )
|
|
return;
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TConstIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetInput* ObjectPathInput = Cast< UHoudiniAssetInput >( IterParams.Value() );
|
|
if ( !ObjectPathInput || ObjectPathInput->IsPendingKill() )
|
|
continue;
|
|
|
|
AllInputs.Add ( ObjectPathInput );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::GetAllUsedStaticMeshes( TArray< UStaticMesh * > & UsedStaticMeshes )
|
|
{
|
|
UsedStaticMeshes.Empty();
|
|
|
|
// Add all static meshes.
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( StaticMesh && !StaticMesh->IsPendingKill() )
|
|
UsedStaticMeshes.Add( StaticMesh );
|
|
}
|
|
}
|
|
|
|
TMap<const UStaticMeshComponent *, FHoudiniGeoPartObject>
|
|
UHoudiniAssetComponent::CollectAllStaticMeshComponents() const
|
|
{
|
|
TMap<const UStaticMeshComponent *, FHoudiniGeoPartObject> OutSMComponentToPart;
|
|
|
|
// Add all the instance meshes, including the variations
|
|
for ( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs )
|
|
{
|
|
if ( !InstanceInput || InstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() )
|
|
{
|
|
if ( !InputField || InputField->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex )
|
|
{
|
|
UObject* Comp = InputField->GetInstancedComponent( VarIndex );
|
|
if ( Comp && InputField->GetInstanceVariation( VarIndex ) && Comp->IsA<UInstancedStaticMeshComponent>() )
|
|
{
|
|
OutSMComponentToPart.Add( Cast<UInstancedStaticMeshComponent>( Comp ), InputField->GetHoudiniGeoPartObject() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add all the plain UStaticMeshComponent
|
|
for ( const auto& MeshPart : GetStaticMeshes() )
|
|
{
|
|
if ( UStaticMeshComponent * SMC = LocateStaticMeshComponent( MeshPart.Value ) )
|
|
{
|
|
OutSMComponentToPart.Add( SMC, MeshPart.Key );
|
|
}
|
|
}
|
|
|
|
return OutSMComponentToPart;
|
|
}
|
|
|
|
TMap<const UHoudiniInstancedActorComponent *, FHoudiniGeoPartObject>
|
|
UHoudiniAssetComponent::CollectAllInstancedActorComponents() const
|
|
{
|
|
TMap<const UHoudiniInstancedActorComponent *, FHoudiniGeoPartObject> OutSMComponentToPart;
|
|
for ( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs )
|
|
{
|
|
if ( !InstanceInput || InstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() )
|
|
{
|
|
if ( !InputField || InputField->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex )
|
|
{
|
|
UObject* Comp = InputField->GetInstancedComponent( VarIndex );
|
|
if (!Comp || Comp->IsPendingKill())
|
|
continue;
|
|
|
|
if ( InputField->GetInstanceVariation( VarIndex ) && Comp->IsA<UHoudiniInstancedActorComponent>() )
|
|
{
|
|
OutSMComponentToPart.Add( Cast<UHoudiniInstancedActorComponent>( Comp ), InputField->GetHoudiniGeoPartObject() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return OutSMComponentToPart;
|
|
}
|
|
|
|
TMap<const UHoudiniMeshSplitInstancerComponent *, FHoudiniGeoPartObject>
|
|
UHoudiniAssetComponent::CollectAllMeshSplitInstancerComponents() const
|
|
{
|
|
TMap<const UHoudiniMeshSplitInstancerComponent *, FHoudiniGeoPartObject> OutSMComponentToPart;
|
|
for( const UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs )
|
|
{
|
|
if (!InstanceInput || InstanceInput->IsPendingKill())
|
|
continue;
|
|
|
|
for( const UHoudiniAssetInstanceInputField* InputField : InstanceInput->GetInstanceInputFields() )
|
|
{
|
|
if ( !InputField || InputField->IsPendingKill() )
|
|
continue;
|
|
|
|
for( int32 VarIndex = 0; VarIndex < InputField->InstanceVariationCount(); ++VarIndex )
|
|
{
|
|
UObject* Comp = InputField->GetInstancedComponent(VarIndex);
|
|
if (!Comp || Comp->IsPendingKill())
|
|
continue;
|
|
|
|
if( InputField->GetInstanceVariation(VarIndex) && Comp->IsA<UHoudiniMeshSplitInstancerComponent>() )
|
|
{
|
|
OutSMComponentToPart.Add(Cast<UHoudiniMeshSplitInstancerComponent>(Comp), InputField->GetHoudiniGeoPartObject());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return OutSMComponentToPart;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::CheckGlobalSettingScaleFactors() const
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if ( HoudiniRuntimeSettings )
|
|
{
|
|
return ( GeneratedGeometryScaleFactor == HoudiniRuntimeSettings->GeneratedGeometryScaleFactor &&
|
|
TransformScaleFactor == HoudiniRuntimeSettings->TransformScaleFactor );
|
|
}
|
|
|
|
return ( GeneratedGeometryScaleFactor == HAPI_UNREAL_SCALE_FACTOR_POSITION &&
|
|
TransformScaleFactor == HAPI_UNREAL_SCALE_FACTOR_TRANSLATION );
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsInstantiatingOrCooking() const
|
|
{
|
|
return HapiGUID.IsValid();
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasBeenInstantiatedButNotCooked() const
|
|
{
|
|
return ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) && ( 0 == AssetCookCount ) );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::AssignUniqueActorLabel()
|
|
{
|
|
if ( GEditor && FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner();
|
|
if ( HoudiniAssetActor )
|
|
{
|
|
FString UniqueName;
|
|
if ( FHoudiniEngineUtils::GetHoudiniAssetName( AssetId, UniqueName ) )
|
|
FActorLabelUtilities::SetActorLabelUnique( HoudiniAssetActor, UniqueName );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartHoudiniUIUpdateTicking()
|
|
{
|
|
// If we have no timer delegate spawned for ui update, spawn one.
|
|
if ( !TimerDelegateUIUpdate.IsBound() && GEditor )
|
|
{
|
|
TimerDelegateUIUpdate = FTimerDelegate::CreateUObject( this, &UHoudiniAssetComponent::TickHoudiniUIUpdate );
|
|
|
|
// We need to register delegate with the timer system.
|
|
static const float TickTimerDelay = 0.25f;
|
|
GEditor->GetTimerManager()->SetTimer( TimerHandleUIUpdate, TimerDelegateUIUpdate, TickTimerDelay, true );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StopHoudiniUIUpdateTicking()
|
|
{
|
|
if ( TimerDelegateUIUpdate.IsBound() && GEditor )
|
|
{
|
|
GEditor->GetTimerManager()->ClearTimer( TimerHandleUIUpdate );
|
|
TimerDelegateUIUpdate.Unbind();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::TickHoudiniUIUpdate()
|
|
{
|
|
UpdateEditorProperties( true );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartHoudiniTicking()
|
|
{
|
|
// If we have no timer delegate spawned for this component, spawn one.
|
|
if ( !TimerDelegateCooking.IsBound() && GEditor )
|
|
{
|
|
TimerDelegateCooking = FTimerDelegate::CreateUObject( this, &UHoudiniAssetComponent::TickHoudiniComponent );
|
|
|
|
// We need to register delegate with the timer system.
|
|
static const float TickTimerDelay = 0.25f;
|
|
GEditor->GetTimerManager()->SetTimer( TimerHandleCooking, TimerDelegateCooking, TickTimerDelay, true );
|
|
|
|
// Grab current time for delayed notification.
|
|
HapiNotificationStarted = FPlatformTime::Seconds();
|
|
}
|
|
|
|
// If the fist session of houdini engine hasnt been initialized yet, do it now
|
|
if (!FHoudiniEngine::Get().GetFirstSessionCreated())
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("First initialization of the Houdini Engine session"));
|
|
FHoudiniEngine::Get().RestartSession();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StopHoudiniTicking()
|
|
{
|
|
if ( TimerDelegateCooking.IsBound() && GEditor )
|
|
{
|
|
GEditor->GetTimerManager()->ClearTimer( TimerHandleCooking );
|
|
TimerDelegateCooking.Unbind();
|
|
|
|
// Reset time for delayed notification.
|
|
HapiNotificationStarted = 0.0;
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostCook( bool bCookError )
|
|
{
|
|
// Show busy cursor.
|
|
FScopedBusyCursor ScopedBusyCursor;
|
|
|
|
// Create parameters and inputs.
|
|
CreateParameters();
|
|
CreateInputs();
|
|
CreateHandles();
|
|
|
|
if ( bCookError )
|
|
{
|
|
// We need to reset the manual recook flag here to avoid endless cooking
|
|
bManualRecookRequested = false;
|
|
return;
|
|
}
|
|
|
|
FTransform ComponentTransform;
|
|
TMap< FHoudiniGeoPartObject, UStaticMesh * > NewStaticMeshes;
|
|
|
|
FHoudiniCookParams HoudiniCookParams( this );
|
|
HoudiniCookParams.StaticMeshBakeMode = FHoudiniCookParams::GetDefaultStaticMeshesCookMode();
|
|
HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode();
|
|
|
|
if ( FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset(
|
|
GetAssetId(),
|
|
HoudiniCookParams,
|
|
!CheckGlobalSettingScaleFactors(),
|
|
bManualRecookRequested,
|
|
StaticMeshes,
|
|
NewStaticMeshes,
|
|
ComponentTransform ) )
|
|
{
|
|
// Remove all duplicates. After this operation, old map will have meshes which we need to deallocate.
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator
|
|
Iter( NewStaticMeshes ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
|
|
// Removes the mesh from previous map of meshes
|
|
UStaticMesh * FoundOldStaticMesh = LocateStaticMesh( HoudiniGeoPartObject );
|
|
if (!FoundOldStaticMesh)
|
|
FoundOldStaticMesh = LocateStaticMeshByNames(HoudiniGeoPartObject, StaticMeshes);
|
|
|
|
if ( ( FoundOldStaticMesh ) && ( FoundOldStaticMesh == StaticMesh ) )
|
|
{
|
|
// Mesh has not changed, we need to remove it from the old map to avoid deallocation.
|
|
StaticMeshes.Remove( HoudiniGeoPartObject );
|
|
}
|
|
|
|
// See if we need to update the HoudiniAssetComponent uproperties
|
|
FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( this, HoudiniGeoPartObject );
|
|
}
|
|
|
|
// Make sure rendering is done
|
|
FlushRenderingCommands();
|
|
|
|
// Update the bake folder as it might have been updated by an override
|
|
BakeFolder = HoudiniCookParams.BakeFolder;
|
|
|
|
if ( NewStaticMeshes.Num() == 0 && bContainsHoudiniLogoGeometry )
|
|
{
|
|
// We're not creating anything new and are already using the houdini logo
|
|
// No need to destroy then recreate the Houdini Logo
|
|
if ( StaticMeshes.Num() != 1)
|
|
CreateStaticMeshHoudiniLogoResource( StaticMeshes );
|
|
}
|
|
else
|
|
{
|
|
// Free meshes and components that are no longer used.
|
|
// compare with first array, keep if contained in NewMeshes
|
|
ReleaseObjectGeoPartResources(StaticMeshes, true, &NewStaticMeshes);
|
|
|
|
// Set meshes and create new components for those meshes that do not have them.
|
|
if (NewStaticMeshes.Num() > 0)
|
|
CreateObjectGeoPartResources(NewStaticMeshes);
|
|
else
|
|
CreateStaticMeshHoudiniLogoResource(NewStaticMeshes);
|
|
}
|
|
}
|
|
|
|
// Invoke cooks of downstream assets.
|
|
if ( bCookingTriggersDownstreamCooks && DownstreamAssetConnections.Num() > 0)
|
|
{
|
|
// First, make sure our downstream assets are valid
|
|
// (check that the component is valid and we are properly set as one of its asset input)
|
|
ValidateDownstreamAssets();
|
|
|
|
for ( TMap<UHoudiniAssetComponent *, TSet< int32 > >::TIterator IterAssets( DownstreamAssetConnections );
|
|
IterAssets;
|
|
++IterAssets )
|
|
{
|
|
UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key();
|
|
if ( !DownstreamAsset || DownstreamAsset->IsPendingKill() )
|
|
continue;
|
|
|
|
DownstreamAsset->bManualRecookRequested = bManualRecookRequested;
|
|
DownstreamAsset->NotifyParameterChanged( nullptr );
|
|
}
|
|
}
|
|
|
|
// We can reset the manual recook flag now that the static meshes have been created
|
|
bManualRecookRequested = false;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ValidateDownstreamAssets()
|
|
{
|
|
TArray<UHoudiniAssetComponent*> InvalidDowmnstreamAssets;
|
|
for (TMap<UHoudiniAssetComponent *, TSet< int32 > >::TIterator IterAssets(DownstreamAssetConnections); IterAssets; ++IterAssets)
|
|
{
|
|
UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key();
|
|
if (!DownstreamAsset || DownstreamAsset->IsPendingKill())
|
|
{
|
|
InvalidDowmnstreamAssets.Add(DownstreamAsset);
|
|
continue;
|
|
}
|
|
|
|
// Check that the downstream asset is valid (that we are indeed set as asset input)
|
|
bool bInvalidDownstreamAsset = false;
|
|
TSet<int32> InputIndexes = IterAssets.Value();
|
|
if (InputIndexes.Num() <= 0)
|
|
{
|
|
InvalidDowmnstreamAssets.Add(DownstreamAsset);
|
|
continue;
|
|
}
|
|
|
|
// Get the downstream assets inputs (including object path parameters!)
|
|
TArray<UHoudiniAssetInput*> DownstreamInputs;
|
|
DownstreamAsset->GetInputs(DownstreamInputs, true);
|
|
|
|
// Check that asset component's input
|
|
for (auto DownstreamInputIdx : InputIndexes)
|
|
{
|
|
if (!DownstreamInputs.IsValidIndex(DownstreamInputIdx))
|
|
{
|
|
RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx);
|
|
continue;
|
|
}
|
|
|
|
UHoudiniAssetInput* DownInput = DownstreamInputs[DownstreamInputIdx];
|
|
if (!DownInput || DownInput->IsPendingKill())
|
|
{
|
|
RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx);
|
|
continue;
|
|
}
|
|
|
|
if (DownInput->GetChoiceIndex() != EHoudiniAssetInputType::AssetInput
|
|
|| DownInput->GetConnectedInputAssetComponent() != this)
|
|
{
|
|
RemoveDownstreamAsset(DownstreamAsset, DownstreamInputIdx);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the fully invalid HAC
|
|
for (auto InvalidHAC : InvalidDowmnstreamAssets)
|
|
{
|
|
DownstreamAssetConnections.Remove(InvalidHAC);
|
|
}
|
|
|
|
}
|
|
void
|
|
UHoudiniAssetComponent::TickHoudiniComponent()
|
|
{
|
|
// Only tick/cook when in Editor
|
|
// This prevents PIE cooks or runtime cooks due to inputs moving
|
|
if (!GetWorld() || (GetWorld()->WorldType != EWorldType::Editor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get settings.
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
|
|
FHoudiniEngineTaskInfo TaskInfo;
|
|
bool bStopTicking = false;
|
|
bool bFinishedLoadedInstantiation = false;
|
|
|
|
static float NotificationFadeOutDuration = 2.0f;
|
|
static float NotificationExpireDuration = 2.0f;
|
|
static double NotificationUpdateFrequency = 2.0f;
|
|
|
|
// Check whether we want to display Slate cooking and instantiation notifications.
|
|
bool bDisplaySlateCookingNotifications = false;
|
|
if ( HoudiniRuntimeSettings )
|
|
bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications;
|
|
|
|
if ( HapiGUID.IsValid() )
|
|
{
|
|
// If we have a valid task GUID.
|
|
if ( FHoudiniEngine::Get().RetrieveTaskInfo( HapiGUID, TaskInfo ) )
|
|
{
|
|
if ( EHoudiniEngineTaskState::None != TaskInfo.TaskState )
|
|
{
|
|
if ( !NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
FNotificationInfo Info( TaskInfo.StatusText );
|
|
|
|
Info.bFireAndForget = false;
|
|
Info.FadeOutDuration = NotificationFadeOutDuration;
|
|
Info.ExpireDuration = NotificationExpireDuration;
|
|
|
|
TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniLogoBrush();
|
|
if ( HoudiniBrush.IsValid() )
|
|
Info.Image = HoudiniBrush.Get();
|
|
|
|
if ( ( FPlatformTime::Seconds() - HapiNotificationStarted) >= NotificationUpdateFrequency )
|
|
{
|
|
if ( !IsPIEActive() )
|
|
NotificationPtr = FSlateNotificationManager::Get().AddNotification( Info );
|
|
}
|
|
}
|
|
}
|
|
|
|
FString DisplayName;
|
|
if (GetOwner())
|
|
DisplayName = (GetOwner()->GetName());
|
|
else
|
|
DisplayName = GetName();
|
|
|
|
switch( TaskInfo.TaskState )
|
|
{
|
|
case EHoudiniEngineTaskState::FinishedInstantiation:
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT(" %s FinishedInstantiation." ), *DisplayName );
|
|
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) )
|
|
{
|
|
// Set new asset id.
|
|
SetAssetId( TaskInfo.AssetId );
|
|
|
|
AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner();
|
|
if( HoudiniAssetActor && HoudiniAssetActor->GetName().StartsWith( AHoudiniAssetActor::StaticClass()->GetName() ) )
|
|
{
|
|
// Assign unique actor label based on asset name if it seems to have not been renamed already
|
|
AssignUniqueActorLabel();
|
|
}
|
|
|
|
// Create default preset buffer.
|
|
CreateDefaultPreset();
|
|
|
|
// If necessary, set asset transform.
|
|
if ( bUploadTransformsToHoudiniEngine )
|
|
{
|
|
// Retrieve the current component-to-world transform for this component.
|
|
if ( !FHoudiniEngineUtils::HapiSetAssetTransform( AssetId, GetComponentTransform() ) )
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Initial Transformation back to HAPI." ) );
|
|
}
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
{
|
|
NotificationItem->SetText( TaskInfo.StatusText );
|
|
NotificationItem->ExpireAndFadeout();
|
|
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID );
|
|
HapiGUID.Invalidate();
|
|
|
|
// We just finished instantiation, we need to reset cook counter.
|
|
AssetCookCount = 0;
|
|
|
|
if ( TaskInfo.bLoadedComponent )
|
|
bFinishedLoadedInstantiation = true;
|
|
|
|
FHoudiniEngine::Get().SetHapiState( HAPI_RESULT_SUCCESS );
|
|
}
|
|
else
|
|
{
|
|
bStopTicking = true;
|
|
HOUDINI_LOG_MESSAGE( TEXT( " %s Received invalid asset id." ), *DisplayName );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case EHoudiniEngineTaskState::FinishedCooking:
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( " %s FinishedCooking." ), *DisplayName );
|
|
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) )
|
|
{
|
|
// Set new asset id.
|
|
SetAssetId( TaskInfo.AssetId );
|
|
|
|
// Call post cook event.
|
|
PostCook();
|
|
|
|
// Need to update rendering information.
|
|
UpdateRenderingInformation();
|
|
|
|
#if WITH_EDITOR
|
|
// Force editor to redraw viewports.
|
|
if ( GEditor )
|
|
GEditor->RedrawAllViewports();
|
|
|
|
// Update properties panel after instantiation.
|
|
UpdateEditorProperties( true );
|
|
|
|
// We may have toolshelf input presets to apply (may cause a recook)
|
|
if ( HoudiniToolInputPreset.Num() > 0 )
|
|
ApplyHoudiniToolInputPreset();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( " %s Received invalid asset id." ), *DisplayName );
|
|
}
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
{
|
|
NotificationItem->SetText( TaskInfo.StatusText );
|
|
NotificationItem->ExpireAndFadeout();
|
|
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID );
|
|
HapiGUID.Invalidate();
|
|
|
|
bStopTicking = true;
|
|
AssetCookCount++;
|
|
|
|
break;
|
|
}
|
|
|
|
case EHoudiniEngineTaskState::FinishedCookingWithErrors:
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( " %s FinishedCookingWithErrors." ), *DisplayName );
|
|
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( TaskInfo.AssetId ) )
|
|
{
|
|
// Call post cook event with error parameter. This will create parameters, inputs and handles.
|
|
PostCook( true );
|
|
|
|
// Create default preset buffer.
|
|
CreateDefaultPreset();
|
|
#if WITH_EDITOR
|
|
// Apply the Input presets for Houdini tools if we have any...
|
|
ApplyHoudiniToolInputPreset();
|
|
|
|
// Update properties panel.
|
|
UpdateEditorProperties( true );
|
|
#endif
|
|
// If necessary, set asset transform.
|
|
if ( bUploadTransformsToHoudiniEngine )
|
|
{
|
|
// Retrieve the current component-to-world transform for this component.
|
|
if ( !FHoudiniEngineUtils::HapiSetAssetTransform(AssetId, GetComponentTransform() ) )
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Initial Transformation back to HAPI." ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Make sure to reset the manual cook flag here to avoid endless cooking/error spam
|
|
bManualRecookRequested = false;
|
|
}
|
|
|
|
// Try to detect possible crash/failures of HARS
|
|
if ( TaskInfo.Result == HAPI_RESULT_NOT_INITIALIZED
|
|
|| TaskInfo.Result == HAPI_RESULT_INVALID_SESSION )
|
|
{
|
|
HOUDINI_LOG_ERROR( TEXT("Failed to cook due to an invalid session.\n\
|
|
This could be due to a crash in the Houdini Engine session / HARS.\n\
|
|
Try restarting it via \"File > Restart Houdini Engine session\"\n\
|
|
then rebuild your assets." ) );
|
|
}
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
{
|
|
NotificationItem->SetText( TaskInfo.StatusText );
|
|
NotificationItem->ExpireAndFadeout();
|
|
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID );
|
|
HapiGUID.Invalidate();
|
|
|
|
bStopTicking = true;
|
|
AssetCookCount++;
|
|
|
|
break;
|
|
}
|
|
|
|
case EHoudiniEngineTaskState::Aborted:
|
|
case EHoudiniEngineTaskState::FinishedInstantiationWithErrors:
|
|
{
|
|
HOUDINI_LOG_ERROR( TEXT( " %s FinishedInstantiationWithErrors." ), *DisplayName );
|
|
|
|
bool bLicensingIssue = false;
|
|
switch( TaskInfo.Result )
|
|
{
|
|
case HAPI_RESULT_NO_LICENSE_FOUND:
|
|
{
|
|
FHoudiniEngine::Get().SetHapiState( HAPI_RESULT_NO_LICENSE_FOUND );
|
|
|
|
bLicensingIssue = true;
|
|
break;
|
|
}
|
|
|
|
case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND:
|
|
case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE:
|
|
case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE:
|
|
case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE:
|
|
{
|
|
bLicensingIssue = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bLicensingIssue )
|
|
{
|
|
const FString & StatusMessage = TaskInfo.StatusText.ToString() ;
|
|
HOUDINI_LOG_MESSAGE( TEXT( "%s" ), *StatusMessage );
|
|
|
|
FString WarningTitle = TEXT( "Houdini Engine Plugin Warning" );
|
|
FText WarningTitleText = FText::FromString( WarningTitle );
|
|
FString WarningMessage = FString::Printf( TEXT( "Houdini License issue - %s." ), *StatusMessage );
|
|
|
|
FMessageDialog::Debugf( FText::FromString( WarningMessage ), &WarningTitleText );
|
|
}
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
{
|
|
NotificationItem->SetText( TaskInfo.StatusText );
|
|
NotificationItem->ExpireAndFadeout();
|
|
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
if ( TaskInfo.bLoadedComponent )
|
|
bFinishedLoadedInstantiation = true;
|
|
|
|
FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID );
|
|
HapiGUID.Invalidate();
|
|
|
|
bStopTicking = true;
|
|
AssetCookCount = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case EHoudiniEngineTaskState::Processing:
|
|
{
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
NotificationItem->SetText( TaskInfo.StatusText );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case EHoudiniEngineTaskState::None:
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Task information does not exist, we can stop ticking.
|
|
HapiGUID.Invalidate();
|
|
bStopTicking = true;
|
|
}
|
|
}
|
|
|
|
if (bFinishedLoadedInstantiation)
|
|
bAssetIsBeingInstantiated = false;
|
|
|
|
if ( !IsInstantiatingOrCooking() )
|
|
{
|
|
if (( HasBeenInstantiatedButNotCooked() || bParametersChanged || bComponentNeedsCook || bManualRecookRequested )
|
|
&& (FHoudiniEngine::Get().GetEnableCookingGlobal() || bManualRecookRequested) )
|
|
{
|
|
// Grab current time for delayed notification.
|
|
HapiNotificationStarted = FPlatformTime::Seconds();
|
|
|
|
// We just submitted a task, we want to continue ticking.
|
|
bStopTicking = false;
|
|
|
|
if ( bWaitingForUpstreamAssetsToInstantiate )
|
|
{
|
|
// We are waiting for upstream assets to instantiate. Update the flag
|
|
UpdateWaitingForUpstreamAssetsToInstantiate();
|
|
|
|
// Try instantiating this asset again.
|
|
if ( !bWaitingForUpstreamAssetsToInstantiate )
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
}
|
|
else if ( bLoadedComponentRequiresInstantiation )
|
|
{
|
|
// This component has been loaded and requires instantiation.
|
|
bLoadedComponentRequiresInstantiation = false;
|
|
StartTaskAssetInstantiation( true );
|
|
}
|
|
else if ( bFinishedLoadedInstantiation )
|
|
{
|
|
// If we are doing first cook after instantiation.
|
|
RefreshEditableNodesAfterLoad();
|
|
|
|
// Update parameter node id for all loaded parameters.
|
|
UpdateLoadedParameters();
|
|
|
|
// Additionally, we need to update and create assets for all input parameters that have geos assigned.
|
|
UpdateLoadedInputs( bManualRecookRequested );
|
|
|
|
// We also need to upload loaded curve points.
|
|
UploadLoadedCurves();
|
|
|
|
// If we finished loading instantiation, we can restore preset data.
|
|
if ( PresetBuffer.Num() > 0 )
|
|
{
|
|
FHoudiniEngineUtils::SetAssetPreset( AssetId, PresetBuffer );
|
|
PresetBuffer.Empty();
|
|
}
|
|
|
|
// Upload changed parameters back to HAPI.
|
|
UploadChangedParameters();
|
|
|
|
// Reset tranform changed flag.
|
|
bComponentNeedsCook = false;
|
|
|
|
// Create asset cooking task object and submit it for processing.
|
|
StartTaskAssetCooking();
|
|
}
|
|
else
|
|
{
|
|
if ( IsCookingEnabled() || bManualRecookRequested )
|
|
{
|
|
// Upload changed parameters back to HAPI.
|
|
UploadChangedParameters();
|
|
|
|
// Create asset cooking task object and submit it for processing.
|
|
StartTaskAssetCooking();
|
|
|
|
// Reset ComponentNeedsCook flag.
|
|
bComponentNeedsCook = false;
|
|
}
|
|
else
|
|
{
|
|
// Cooking is disabled, but we still need to upload the parameters
|
|
// and update the editor properties
|
|
UploadChangedParameters();
|
|
|
|
// Update properties panel.
|
|
UpdateEditorProperties(true);
|
|
|
|
// Remember that we have uncooked changes
|
|
bComponentNeedsCook = true;
|
|
|
|
// Stop ticking
|
|
bStopTicking = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Nothing has changed, we can terminate ticking.
|
|
bStopTicking = true;
|
|
}
|
|
}
|
|
|
|
if ( bNeedToUpdateNavigationSystem )
|
|
{
|
|
#ifdef WITH_EDITOR
|
|
// notify navigation system
|
|
AHoudiniAssetActor* HoudiniActor = GetHoudiniAssetActorOwner();
|
|
if ( HoudiniActor && !HoudiniActor->IsPendingKill() )
|
|
FNavigationSystem::UpdateActorAndComponentData(*HoudiniActor);
|
|
#endif
|
|
bNeedToUpdateNavigationSystem = false;
|
|
}
|
|
|
|
if ( bStopTicking )
|
|
StopHoudiniTicking();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdateEditorProperties( bool bConditionalUpdate )
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = GetHoudiniAssetActorOwner();
|
|
if ( !HoudiniAssetActor )
|
|
return;
|
|
|
|
FPropertyEditorModule & PropertyModule =
|
|
FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >( "PropertyEditor" );
|
|
|
|
// We want to iterate on all the details panel
|
|
static const FName DetailsTabIdentifiers[] = { "LevelEditorSelectionDetails", "LevelEditorSelectionDetails2", "LevelEditorSelectionDetails3", "LevelEditorSelectionDetails4" };
|
|
for (const FName& DetailsPanelName : DetailsTabIdentifiers )
|
|
{
|
|
// Locate the details panel.
|
|
//FName DetailsPanelName = "LevelEditorSelectionDetails";
|
|
TSharedPtr< IDetailsView > DetailsView = PropertyModule.FindDetailView( DetailsPanelName );
|
|
|
|
if ( !DetailsView.IsValid() )
|
|
{
|
|
// We have no details panel, nothing to update.
|
|
continue;
|
|
}
|
|
|
|
if ( DetailsView->IsLocked() )
|
|
{
|
|
// If details panel is locked, locate selected actors and check if this component belongs to one of them.
|
|
const TArray< TWeakObjectPtr< AActor > > & SelectedDetailActors = DetailsView->GetSelectedActors();
|
|
bool bFoundActor = false;
|
|
|
|
for (int32 ActorIdx = 0, ActorNum = SelectedDetailActors.Num(); ActorIdx < ActorNum; ++ActorIdx)
|
|
{
|
|
TWeakObjectPtr< AActor > SelectedActor = SelectedDetailActors[ActorIdx];
|
|
if ( SelectedActor.IsValid() && SelectedActor.Get() == HoudiniAssetActor )
|
|
{
|
|
bFoundActor = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Details panel is locked, but our actor is not selected.
|
|
if ( !bFoundActor )
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// If our actor is not selected (and details panel is not locked) don't do any updates.
|
|
if ( !HoudiniAssetActor->IsSelected() )
|
|
continue;
|
|
}
|
|
|
|
if ( GEditor && HoudiniAssetActor && bIsNativeComponent )
|
|
{
|
|
if ( bConditionalUpdate && FSlateApplication::Get().HasAnyMouseCaptor() )
|
|
{
|
|
// We want to avoid UI update if this is a conditional update and widget has captured the mouse.
|
|
StartHoudiniUIUpdateTicking();
|
|
continue;
|
|
}
|
|
|
|
TArray< UObject * > SelectedActors;
|
|
SelectedActors.Add( HoudiniAssetActor );
|
|
|
|
// bEditorPropertiesNeedFullUpdate is false only when small changes (parameters value) have been made
|
|
// We do not reselect the actor to avoid loosing the current selected parameter
|
|
|
|
// Reset selected actor to itself, force refresh and override the lock.
|
|
DetailsView->SetObjects( SelectedActors, bEditorPropertiesNeedFullUpdate, true );
|
|
|
|
if ( !bEditorPropertiesNeedFullUpdate )
|
|
bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
if ( GUnrealEd )
|
|
{
|
|
GUnrealEd->UpdateFloatingPropertyWindows();
|
|
}
|
|
}
|
|
|
|
StopHoudiniUIUpdateTicking();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetInstantiation( bool bLocalLoadedComponent, bool bStartTicking )
|
|
{
|
|
// We do not want to be instantiated twice
|
|
bAssetIsBeingInstantiated = true;
|
|
|
|
// We first need to make sure all our asset inputs have been instantiated and reconnected.
|
|
UpdateWaitingForUpstreamAssetsToInstantiate( true );
|
|
|
|
if ( !bWaitingForUpstreamAssetsToInstantiate )
|
|
{
|
|
// Check if asset has multiple Houdini assets inside.
|
|
HAPI_AssetLibraryId AssetLibraryId = -1;
|
|
TArray< HAPI_StringHandle > AssetNames;
|
|
|
|
if ( FHoudiniEngineUtils::GetAssetNames( HoudiniAsset, AssetLibraryId, AssetNames ) )
|
|
{
|
|
HAPI_StringHandle PickedAssetName = AssetNames[ 0 ];
|
|
bool bShowMultiAssetDialog = false;
|
|
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if ( HoudiniRuntimeSettings )
|
|
bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog;
|
|
|
|
if ( bShowMultiAssetDialog && AssetNames.Num() > 1 )
|
|
{
|
|
// If we have more than one asset, we need to present user with choice dialog.
|
|
|
|
TSharedPtr< SWindow > ParentWindow;
|
|
|
|
// Check if the main frame is loaded. When using the old main frame it may not be.
|
|
if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) )
|
|
{
|
|
IMainFrameModule & MainFrame = FModuleManager::LoadModuleChecked< IMainFrameModule >( "MainFrame" );
|
|
ParentWindow = MainFrame.GetParentWindow();
|
|
}
|
|
|
|
if ( ParentWindow.IsValid() )
|
|
{
|
|
TSharedPtr< SAssetSelectionWidget > AssetSelectionWidget;
|
|
|
|
TSharedRef< SWindow > Window = SNew( SWindow )
|
|
.Title( LOCTEXT( "WindowTitle", "Select an asset to instantiate" ) )
|
|
.ClientSize( FVector2D( 640, 480 ) )
|
|
.SupportsMinimize( false )
|
|
.SupportsMaximize( false )
|
|
.HasCloseButton( false );
|
|
|
|
Window->SetContent( SAssignNew( AssetSelectionWidget, SAssetSelectionWidget )
|
|
.WidgetWindow( Window )
|
|
.AvailableAssetNames( AssetNames ) );
|
|
|
|
if ( AssetSelectionWidget->IsValidWidget() )
|
|
{
|
|
FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false );
|
|
|
|
int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName();
|
|
if ( DialogPickedAssetName != -1 )
|
|
PickedAssetName = DialogPickedAssetName;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create new GUID to identify this request.
|
|
HapiGUID = FGuid::NewGuid();
|
|
|
|
FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetInstantiation, HapiGUID );
|
|
Task.Asset = HoudiniAsset;
|
|
Task.ActorName = GetOuter()->GetName();
|
|
Task.bLoadedComponent = bLocalLoadedComponent;
|
|
Task.AssetLibraryId = AssetLibraryId;
|
|
Task.AssetHapiName = PickedAssetName;
|
|
FHoudiniEngine::Get().AddTask( Task );
|
|
}
|
|
else
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Cancelling asset instantiation - unable to retrieve asset names." ) );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Start ticking - this will poll the cooking system for completion.
|
|
if ( bStartTicking )
|
|
StartHoudiniTicking();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetCookingManual()
|
|
{
|
|
if ( !IsInstantiatingOrCooking() )
|
|
{
|
|
bManualRecookRequested = true;
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( GetAssetId() ) )
|
|
{
|
|
StartHoudiniTicking();
|
|
}
|
|
else
|
|
{
|
|
if ( bLoadedComponent )
|
|
{
|
|
// This is a loaded component which requires instantiation.
|
|
StartTaskAssetInstantiation( true, true );
|
|
bParametersChanged = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetResetManual()
|
|
{
|
|
if ( !IsInstantiatingOrCooking() )
|
|
{
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( GetAssetId() ) )
|
|
{
|
|
if ( FHoudiniEngineUtils::SetAssetPreset( GetAssetId(), DefaultPresetBuffer ) )
|
|
{
|
|
UnmarkChangedParameters();
|
|
StartTaskAssetCookingManual();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( bLoadedComponent )
|
|
{
|
|
// This is a loaded component which requires instantiation.
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
bParametersChanged = true;
|
|
|
|
// Replace serialized preset buffer with default preset buffer.
|
|
PresetBuffer = DefaultPresetBuffer;
|
|
StartHoudiniTicking();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetRebuildManual()
|
|
{
|
|
if ( !IsInstantiatingOrCooking() )
|
|
{
|
|
if ( FHoudiniEngineUtils::IsHoudiniNodeValid( AssetId ) )
|
|
{
|
|
if ( !FHoudiniEngineUtils::GetAssetPreset( AssetId, PresetBuffer ) )
|
|
{
|
|
HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters."));
|
|
}
|
|
|
|
// We need to delete the asset and request a new one.
|
|
StartTaskAssetDeletion();
|
|
}
|
|
|
|
HapiGUID = FGuid::NewGuid();
|
|
|
|
// If this is a loaded component, then we just need to instantiate.
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
bParametersChanged = true;
|
|
bManualRecookRequested = true;
|
|
|
|
StartHoudiniTicking();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetDeletion()
|
|
{
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) && bIsNativeComponent )
|
|
{
|
|
// Get the Asset's NodeInfo
|
|
HAPI_NodeInfo AssetNodeInfo;
|
|
FHoudiniApi::NodeInfo_Init(&AssetNodeInfo);
|
|
FHoudiniApi::GetNodeInfo(
|
|
FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo );
|
|
|
|
HAPI_NodeId OBJNodeToDelete = AssetId;
|
|
if ( AssetNodeInfo.type == HAPI_NODETYPE_SOP )
|
|
{
|
|
// For SOP Asset, we want to delete their parent's OBJ node
|
|
HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId( AssetId );
|
|
OBJNodeToDelete = ParentId != -1 ? ParentId : AssetId;
|
|
}
|
|
|
|
// Generate GUID for our new task.
|
|
FGuid HapiDeletionGUID = FGuid::NewGuid();
|
|
|
|
// Create asset deletion task object and submit it for processing.
|
|
FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetDeletion, HapiDeletionGUID );
|
|
Task.AssetId = OBJNodeToDelete;
|
|
FHoudiniEngine::Get().AddTask( Task );
|
|
|
|
// Reset asset id
|
|
AssetId = -1;
|
|
|
|
// We do not need to tick as we are not interested in result.
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::StartTaskAssetCooking( bool bStartTicking )
|
|
{
|
|
if ( !IsInstantiatingOrCooking() )
|
|
{
|
|
if (!FHoudiniEngineUtils::IsValidNodeId(AssetId))
|
|
return;
|
|
|
|
// Generate GUID for our new task.
|
|
HapiGUID = FGuid::NewGuid();
|
|
|
|
FHoudiniEngineTask Task( EHoudiniEngineTaskType::AssetCooking, HapiGUID );
|
|
Task.ActorName = GetOuter()->GetName();
|
|
Task.AssetId = GetAssetId();
|
|
FHoudiniEngine::Get().AddTask( Task );
|
|
|
|
if ( bStartTicking )
|
|
StartHoudiniTicking();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ResetHoudiniResources()
|
|
{
|
|
if ( HapiGUID.IsValid() )
|
|
{
|
|
// If we have a valid task GUID.
|
|
FHoudiniEngineTaskInfo TaskInfo;
|
|
|
|
if ( FHoudiniEngine::Get().RetrieveTaskInfo( HapiGUID, TaskInfo ) )
|
|
{
|
|
FHoudiniEngine::Get().RemoveTaskInfo( HapiGUID );
|
|
HapiGUID.Invalidate();
|
|
StopHoudiniTicking();
|
|
|
|
// Get settings.
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
|
|
// Check whether we want to display Slate cooking and instantiation notifications.
|
|
bool bDisplaySlateCookingNotifications = false;
|
|
if ( HoudiniRuntimeSettings )
|
|
bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications;
|
|
|
|
if ( NotificationPtr.IsValid() && bDisplaySlateCookingNotifications )
|
|
{
|
|
TSharedPtr< SNotificationItem > NotificationItem = NotificationPtr.Pin();
|
|
if ( NotificationItem.IsValid() )
|
|
{
|
|
NotificationItem->ExpireAndFadeout();
|
|
NotificationPtr.Reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start asset deletion.
|
|
StartTaskAssetDeletion();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SubscribeEditorDelegates()
|
|
{
|
|
// Add delegate for viewport drag and drop events.
|
|
DelegateHandleApplyObjectToActor =
|
|
FEditorDelegates::OnApplyObjectToActor.AddUObject( this, &UHoudiniAssetComponent::OnApplyObjectToActor );
|
|
|
|
if ( GEditor )
|
|
{
|
|
// Add delegate for asset post import.
|
|
if (UImportSubsystem* ImportSys = GEditor->GetEditorSubsystem<UImportSubsystem>())
|
|
{
|
|
ImportSys->OnAssetPostImport.AddUObject(this, &UHoudiniAssetComponent::OnAssetPostImport);
|
|
}
|
|
|
|
// Add delegate for actor moved.
|
|
GEditor->OnActorMoved().AddUObject( this, &UHoudiniAssetComponent::OnActorMoved );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UnsubscribeEditorDelegates()
|
|
{
|
|
// Remove delegate for viewport drag and drop events.
|
|
FEditorDelegates::OnApplyObjectToActor.Remove( DelegateHandleApplyObjectToActor );
|
|
|
|
if ( GEditor )
|
|
{
|
|
// Remove delegate for asset post import.
|
|
if (UImportSubsystem* ImportSys = GEditor->GetEditorSubsystem<UImportSubsystem>())
|
|
{
|
|
ImportSys->OnAssetPostImport.Remove(DelegateHandleAssetPostImport);
|
|
}
|
|
|
|
// Remove delegate for actor moved.
|
|
GEditor->OnActorMoved().RemoveAll( this );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostEditChangeProperty( FPropertyChangedEvent & PropertyChangedEvent )
|
|
{
|
|
Super::PostEditChangeProperty( PropertyChangedEvent );
|
|
|
|
if (!bIsNativeComponent)
|
|
return;
|
|
|
|
FProperty * Property = PropertyChangedEvent.MemberProperty;
|
|
if ( !Property )
|
|
return;
|
|
|
|
if ( Property->GetName() == TEXT( "Mobility" ) )
|
|
{
|
|
// Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent
|
|
// not propagating property changes to their own child StaticMeshComponents.
|
|
TArray< USceneComponent * > LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
|
|
// Mobility was changed, we need to update it for all attached components as well.
|
|
for ( TArray< USceneComponent * >::TConstIterator Iter( LocalAttachChildren ); Iter; ++Iter)
|
|
{
|
|
USceneComponent * SceneComponent = *Iter;
|
|
SceneComponent->SetMobility( Mobility );
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bVisible" ) )
|
|
{
|
|
// Visibility has changed, propagate it to children.
|
|
SetVisibility( IsVisible(), true );
|
|
return;
|
|
}
|
|
else if ( ( Property->GetName() == TEXT( "RelativeLocation" ) )
|
|
|| (Property->GetName() == TEXT( "RelativeRotation" ) )
|
|
|| (Property->GetName() == TEXT( "RelativeScale3D" ) ) )
|
|
{
|
|
// Location has changed
|
|
CheckedUploadTransform();
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bHiddenInGame" ) )
|
|
{
|
|
// Visibility has changed, propagate it to children.
|
|
SetHiddenInGame( bHiddenInGame, true );
|
|
return;
|
|
}
|
|
|
|
if ( Property->HasMetaData( TEXT( "Category" ) ) )
|
|
{
|
|
const FString & Category = Property->GetMetaData( TEXT( "Category" ) );
|
|
static const FString CategoryHoudiniGeneratedStaticMeshSettings = TEXT( "HoudiniGeneratedStaticMeshSettings" );
|
|
static const FString CategoryLighting = TEXT( "Lighting" );
|
|
static const FString CategoryRendering = TEXT( "Rendering" );
|
|
static const FString CategoryCollision = TEXT( "Collision" );
|
|
static const FString CategoryPhysics = TEXT("Physics");
|
|
static const FString CategoryLOD = TEXT("LOD");
|
|
|
|
if ( CategoryHoudiniGeneratedStaticMeshSettings == Category )
|
|
{
|
|
// We are changing one of the mesh generation properties, we need to update all static meshes.
|
|
// As the StaticMeshComponents map contains only top-level static mesh components only, use the StaticMeshes map instead
|
|
for (TMap< FHoudiniGeoPartObject, UStaticMesh * > ::TIterator Iter(StaticMeshes); Iter; ++Iter)
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
SetStaticMeshGenerationParameters( StaticMesh );
|
|
FHoudiniScopedGlobalSilence ScopedGlobalSilence;
|
|
StaticMesh->Build( true );
|
|
RefreshCollisionChange( *StaticMesh );
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if ( CategoryLighting == Category )
|
|
{
|
|
if ( Property->GetName() == TEXT( "CastShadow" ) )
|
|
{
|
|
// Stop cast-shadow being applied to invisible colliders children
|
|
// This prevent colliders only meshes from casting shadows
|
|
TArray< UActorComponent * > ReregisterComponents;
|
|
{
|
|
TArray< USceneComponent * > LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter)
|
|
{
|
|
UStaticMeshComponent * Component = Cast< UStaticMeshComponent >(*Iter);
|
|
if (!Component || Component->IsPendingKill())
|
|
continue;
|
|
|
|
const FHoudiniGeoPartObject * pGeoPart = StaticMeshes.FindKey(Component->GetStaticMesh());
|
|
if (pGeoPart && pGeoPart->IsCollidable())
|
|
{
|
|
// This is an invisible collision mesh:
|
|
// Do not interfere with lightmap builds - disable shadow casting
|
|
Component->SetCastShadow(false);
|
|
}
|
|
else
|
|
{
|
|
// Set normally
|
|
Component->SetCastShadow(CastShadow);
|
|
}
|
|
|
|
ReregisterComponents.Add(Component);
|
|
}
|
|
}
|
|
|
|
if (ReregisterComponents.Num() > 0)
|
|
{
|
|
FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents);
|
|
}
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastDynamicShadow" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastDynamicShadow );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastStaticShadow" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastStaticShadow );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastVolumetricTranslucentShadow" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastVolumetricTranslucentShadow );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastInsetShadow" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastInsetShadow );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastHiddenShadow" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastHiddenShadow );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bCastShadowAsTwoSided" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCastShadowAsTwoSided );
|
|
}
|
|
/*else if ( Property->GetName() == TEXT( "bLightAsIfStatic" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bLightAsIfStatic );
|
|
}*/
|
|
else if ( Property->GetName() == TEXT( "bLightAttachmentsAsGroup" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bLightAttachmentsAsGroup );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "IndirectLightingCacheQuality" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, IndirectLightingCacheQuality );
|
|
}
|
|
}
|
|
else if ( CategoryRendering == Category )
|
|
{
|
|
if ( Property->GetName() == TEXT( "bVisibleInReflectionCaptures" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bVisibleInReflectionCaptures );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bRenderInMainPass" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bRenderInMainPass );
|
|
}
|
|
/*
|
|
else if ( Property->GetName() == TEXT( "bRenderInMono" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bRenderInMono );
|
|
}
|
|
*/
|
|
else if ( Property->GetName() == TEXT( "bOwnerNoSee" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bOwnerNoSee );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bOnlyOwnerSee" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bOnlyOwnerSee );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bTreatAsBackgroundForOcclusion" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bTreatAsBackgroundForOcclusion );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bUseAsOccluder" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bUseAsOccluder );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bRenderCustomDepth" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bRenderCustomDepth );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "CustomDepthStencilValue" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, CustomDepthStencilValue );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "CustomDepthStencilWriteMask" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, CustomDepthStencilWriteMask );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "TranslucencySortPriority" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, TranslucencySortPriority );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "LpvBiasMultiplier" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, LpvBiasMultiplier );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bReceivesDecals" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bReceivesDecals );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "BoundsScale" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, BoundsScale );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bUseAttachParentBound" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( USceneComponent, bUseAttachParentBound);
|
|
}
|
|
}
|
|
else if ( CategoryCollision == Category )
|
|
{
|
|
if ( Property->GetName() == TEXT( "bAlwaysCreatePhysicsState" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bAlwaysCreatePhysicsState );
|
|
}
|
|
/*else if ( Property->GetName() == TEXT( "bGenerateOverlapEvents" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bGenerateOverlapEvents );
|
|
}*/
|
|
else if ( Property->GetName() == TEXT( "bMultiBodyOverlap" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bMultiBodyOverlap );
|
|
}
|
|
/*
|
|
else if ( Property->GetName() == TEXT( "bCheckAsyncSceneOnMove" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bCheckAsyncSceneOnMove );
|
|
}
|
|
*/
|
|
else if ( Property->GetName() == TEXT( "bTraceComplexOnMove" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bTraceComplexOnMove );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bReturnMaterialOnMove" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bReturnMaterialOnMove );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "BodyInstance" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, BodyInstance );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "CanCharacterStepUpOn" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, CanCharacterStepUpOn );
|
|
}
|
|
/*else if ( Property->GetName() == TEXT( "bCanEverAffectNavigation" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UActorComponent, bCanEverAffectNavigation );
|
|
}*/
|
|
}
|
|
else if ( CategoryPhysics == Category )
|
|
{
|
|
if ( Property->GetName() == TEXT( "bIgnoreRadialImpulse" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bIgnoreRadialImpulse );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bIgnoreRadialForce" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bIgnoreRadialForce );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bApplyImpulseOnDamage" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bApplyImpulseOnDamage );
|
|
}
|
|
/*
|
|
else if ( Property->GetName() == TEXT( "bShouldUpdatePhysicsVolume" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( USceneComponent, bShouldUpdatePhysicsVolume );
|
|
}
|
|
*/
|
|
}
|
|
else if ( CategoryLOD == Category )
|
|
{
|
|
if ( Property->GetName() == TEXT( "MinDrawDistance" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, MinDrawDistance );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "LDMaxDrawDistance" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, LDMaxDrawDistance );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "CachedMaxDrawDistance" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, CachedMaxDrawDistance );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "bAllowCullDistanceVolume" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( UPrimitiveComponent, bAllowCullDistanceVolume );
|
|
}
|
|
else if ( Property->GetName() == TEXT( "DetailMode" ) )
|
|
{
|
|
HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( USceneComponent, DetailMode );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::RemoveAllAttachedComponents()
|
|
{
|
|
while ( true )
|
|
{
|
|
const int32 ChildCount = GetAttachChildren().Num();
|
|
if ( ChildCount <= 0 )
|
|
break;
|
|
|
|
USceneComponent * Component = GetAttachChildren()[ ChildCount - 1 ];
|
|
|
|
Component->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform );
|
|
Component->UnregisterComponent();
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
check( GetAttachChildren().Num() == 0 );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnComponentClipboardCopy( UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
// Store copied component.
|
|
CopiedHoudiniComponent = HoudiniAssetComponent;
|
|
|
|
if ( bIsNativeComponent )
|
|
{
|
|
// This component has been loaded.
|
|
bLoadedComponent = true;
|
|
bFullyLoaded = false;
|
|
}
|
|
|
|
// Mark this component as imported.
|
|
bComponentCopyImported = true;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnAssetPostImport( UFactory * Factory, UObject * Object )
|
|
{
|
|
if (!bComponentCopyImported || !CopiedHoudiniComponent.IsValid() )
|
|
return;
|
|
|
|
// Show busy cursor.
|
|
FScopedBusyCursor ScopedBusyCursor;
|
|
|
|
// Copy the original scale - this gets lost sometimes in the copy/paste procedure
|
|
SetWorldScale3D( CopiedHoudiniComponent->GetComponentScale() );
|
|
|
|
// Copy mobility
|
|
SetMobility( CopiedHoudiniComponent->Mobility );
|
|
|
|
// Get original asset id.
|
|
HAPI_NodeId CopiedHoudiniComponentAssetId = CopiedHoudiniComponent->AssetId;
|
|
|
|
// Set Houdini asset.
|
|
HoudiniAsset = CopiedHoudiniComponent->HoudiniAsset;
|
|
|
|
// Copy preset buffer.
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( CopiedHoudiniComponentAssetId ) )
|
|
FHoudiniEngineUtils::GetAssetPreset( CopiedHoudiniComponentAssetId, PresetBuffer );
|
|
else
|
|
PresetBuffer = CopiedHoudiniComponent->PresetBuffer;
|
|
|
|
// Copy default preset buffer.
|
|
DefaultPresetBuffer = CopiedHoudiniComponent->DefaultPresetBuffer;
|
|
|
|
// Clean up all generated and auto-attached components.
|
|
RemoveAllAttachedComponents();
|
|
|
|
// Release static mesh related resources.
|
|
ReleaseObjectGeoPartResources( StaticMeshes );
|
|
StaticMeshes.Empty();
|
|
StaticMeshComponents.Empty();
|
|
|
|
TMap<UObject*, UObject*> ReplacementMap;
|
|
|
|
// We need to reconstruct geometry from copied actor.
|
|
for( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( CopiedHoudiniComponent->StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
// Prevents the default Houdini logo mesh from being duplicated and saved into sub-levels
|
|
const UStaticMesh * HoudiniLogoMesh = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get();
|
|
if (StaticMesh == HoudiniLogoMesh)
|
|
{
|
|
HOUDINI_LOG_WARNING(TEXT("Ignoring HoudiniAsset duplication of default logo static mesh."));
|
|
continue;
|
|
}
|
|
|
|
// Duplicate static mesh and all related generated Houdini materials and textures.
|
|
UStaticMesh * DuplicatedStaticMesh =
|
|
FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( StaticMesh, this, HoudiniGeoPartObject, FHoudiniCookParams::GetDefaultStaticMeshesCookMode() );
|
|
|
|
if( DuplicatedStaticMesh && !DuplicatedStaticMesh->IsPendingKill() )
|
|
{
|
|
// Store this duplicated mesh.
|
|
StaticMeshes.Add( FHoudiniGeoPartObject( HoudiniGeoPartObject, true ), DuplicatedStaticMesh );
|
|
ReplacementMap.Add( StaticMesh, DuplicatedStaticMesh );
|
|
}
|
|
}
|
|
|
|
// Copy material information.
|
|
HoudiniAssetComponentMaterials = CopiedHoudiniComponent->HoudiniAssetComponentMaterials->Duplicate( this, ReplacementMap );
|
|
|
|
// Copy parameters.
|
|
{
|
|
ClearParameters();
|
|
CopiedHoudiniComponent->DuplicateParameters( this );
|
|
}
|
|
|
|
// Copy inputs.
|
|
{
|
|
ClearInputs();
|
|
CopiedHoudiniComponent->DuplicateInputs( this );
|
|
}
|
|
|
|
// Copy instance inputs.
|
|
{
|
|
ClearInstanceInputs();
|
|
CopiedHoudiniComponent->DuplicateInstanceInputs( this, ReplacementMap );
|
|
}
|
|
|
|
// We need to reconstruct splines.
|
|
for( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator
|
|
Iter( CopiedHoudiniComponent->SplineComponents ); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value();
|
|
if ( !HoudiniSplineComponent || !HoudiniSplineComponent->IsValidLowLevel() )
|
|
continue;
|
|
|
|
// Duplicate spline component.
|
|
UHoudiniSplineComponent * DuplicatedSplineComponent =
|
|
DuplicateObject< UHoudiniSplineComponent >( HoudiniSplineComponent, this );
|
|
|
|
if ( DuplicatedSplineComponent )
|
|
{
|
|
DuplicatedSplineComponent->SetFlags( RF_Transactional | RF_Public );
|
|
SplineComponents.Add( HoudiniGeoPartObject, DuplicatedSplineComponent );
|
|
}
|
|
}
|
|
|
|
/*
|
|
// We need to duplicate landscapes.
|
|
for ( TMap< FHoudiniGeoPartObject, ALandscapeProxy * >::TIterator
|
|
Iter(CopiedHoudiniComponent->LandscapeComponents); Iter; ++Iter)
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value();
|
|
|
|
// Duplicate landscape component.
|
|
ALandscapeProxy * DuplicatedLandscape =
|
|
DuplicateObject< ALandscapeProxy >( HoudiniLandscape, this );
|
|
|
|
if ( DuplicatedLandscape )
|
|
{
|
|
DuplicatedLandscape->SetFlags(RF_Transactional | RF_Public);
|
|
LandscapeComponents.Add(HoudiniGeoPartObject, DuplicatedLandscape);
|
|
}
|
|
}
|
|
*/
|
|
|
|
// We cannot duplicate landscape for now...
|
|
// we will have to recook the asset to recreate them
|
|
bool bNeedsRecook = false;
|
|
if ( CopiedHoudiniComponent->LandscapeComponents.Num() > 0 )
|
|
bNeedsRecook = true;
|
|
|
|
// Perform any necessary post loading.
|
|
PostLoad();
|
|
|
|
DuplicateHandles( CopiedHoudiniComponent.Get() );
|
|
|
|
// Mark this component as no longer copy imported and reset copied component.
|
|
bComponentCopyImported = false;
|
|
CopiedHoudiniComponent = nullptr;
|
|
|
|
if ( bNeedsRecook )
|
|
StartTaskAssetCookingManual();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnApplyObjectToActor( UObject* ObjectToApply, AActor * ActorToApplyTo )
|
|
{
|
|
if ( GetHoudiniAssetActorOwner() != ActorToApplyTo )
|
|
return;
|
|
|
|
// We want to handle material replacements.
|
|
UMaterialInterface * Material = Cast< UMaterialInterface >( ObjectToApply );
|
|
if (!Material)
|
|
return;
|
|
|
|
bool bMaterialReplaced = false;
|
|
|
|
TMap< UStaticMesh*, int32 > MaterialReplacementsMap;
|
|
|
|
// We need to detect which components have material overriden, and replace it on their corresponding
|
|
// generated static meshes.
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Key();
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
|
|
if ( !StaticMeshComponent || !StaticMesh || StaticMeshComponent->IsPendingKill() || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
const TArray< class UMaterialInterface * > & OverrideMaterials = StaticMeshComponent->OverrideMaterials;
|
|
for ( int32 MaterialIdx = 0; MaterialIdx < OverrideMaterials.Num(); ++MaterialIdx )
|
|
{
|
|
UMaterialInterface * OverridenMaterial = OverrideMaterials[ MaterialIdx ];
|
|
if ( OverridenMaterial && OverridenMaterial == Material )
|
|
{
|
|
if ( MaterialIdx < StaticMesh->StaticMaterials.Num() )
|
|
MaterialReplacementsMap.Add( StaticMesh, MaterialIdx );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
InstanceInput->GetMaterialReplacementMeshes( Material, MaterialReplacementsMap );
|
|
}
|
|
|
|
if (MaterialReplacementsMap.Num() <= 0)
|
|
return;
|
|
|
|
FScopedTransaction Transaction(
|
|
TEXT( HOUDINI_MODULE_RUNTIME ),
|
|
LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), this );
|
|
|
|
for ( TMap< UStaticMesh *, int32 >::TIterator Iter( MaterialReplacementsMap ); Iter; ++Iter )
|
|
{
|
|
UStaticMesh * StaticMesh = Iter.Key();
|
|
int32 MaterialIdx = Iter.Value();
|
|
|
|
// Get old material.
|
|
UMaterialInterface * OldMaterial = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface;
|
|
|
|
// Locate geo part object.
|
|
FHoudiniGeoPartObject HoudiniGeoPartObject = LocateGeoPartObject( StaticMesh );
|
|
if ( !HoudiniGeoPartObject.IsValid() )
|
|
continue;
|
|
|
|
if ( ReplaceMaterial( HoudiniGeoPartObject, Material, OldMaterial, MaterialIdx ) )
|
|
{
|
|
StaticMesh->Modify();
|
|
StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = Material;
|
|
|
|
StaticMesh->PreEditChange( nullptr );
|
|
StaticMesh->PostEditChange();
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh );
|
|
if ( StaticMeshComponent )
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
StaticMeshComponent->SetMaterial( MaterialIdx, Material );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
|
|
TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents;
|
|
if ( LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) )
|
|
{
|
|
for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx )
|
|
{
|
|
UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ];
|
|
|
|
if ( InstancedStaticMeshComponent )
|
|
{
|
|
InstancedStaticMeshComponent->Modify();
|
|
InstancedStaticMeshComponent->SetMaterial( MaterialIdx, Material );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bMaterialReplaced )
|
|
UpdateEditorProperties( false );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnActorMoved( AActor* Actor )
|
|
{
|
|
if ( GetOwner() == Actor )
|
|
{
|
|
CheckedUploadTransform();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateDefaultPreset()
|
|
{
|
|
if ( !bLoadedComponent && !FHoudiniEngineUtils::GetAssetPreset( GetAssetId(), DefaultPresetBuffer ) )
|
|
DefaultPresetBuffer.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnUpdateTransform( EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport )
|
|
{
|
|
Super::OnUpdateTransform( UpdateTransformFlags, Teleport );
|
|
|
|
CheckedUploadTransform();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CheckedUploadTransform()
|
|
{
|
|
// Only if the asset is done loading, else this might cause a cook
|
|
// upon loading a map if the asset has TransformChangeTRiggersCook enabled
|
|
if ( !bFullyLoaded )
|
|
return;
|
|
|
|
// If we have to upload transforms.
|
|
if ( bUploadTransformsToHoudiniEngine && AssetCookCount > 0 )
|
|
{
|
|
// Retrieve the current component-to-world transform for this component.
|
|
if ( !FHoudiniEngineUtils::HapiSetAssetTransform( AssetId, GetComponentTransform() ) )
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Failed Uploading Transformation change back to HAPI." ) );
|
|
}
|
|
|
|
// If transforms trigger cooks, we need to schedule a cook.
|
|
if ( bTransformChangeTriggersCooks )
|
|
{
|
|
// If we have landscape inputs in auto select components mode, we need to mark them
|
|
// as changed so to recommit the landscape properly with updated transforms
|
|
bool NeedLandscapeUpdate = false;
|
|
for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx)
|
|
{
|
|
// Retrieve input at this index.
|
|
UHoudiniAssetInput * AssetInput = Inputs[InputIdx];
|
|
if (!AssetInput || AssetInput->IsPendingKill())
|
|
continue;
|
|
|
|
if (!AssetInput->IsLandscapeUpdateNeededOnTransformChange())
|
|
continue;
|
|
|
|
NeedLandscapeUpdate = true;
|
|
AssetInput->MarkChanged();
|
|
}
|
|
|
|
if (bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId(AssetId) && !bAssetIsBeingInstantiated)
|
|
StartTaskAssetCookingManual();
|
|
|
|
bComponentNeedsCook = true;
|
|
StartHoudiniTicking();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject, const FString& BaseName )
|
|
{
|
|
if( const FString* FoundOverride = BakeNameOverrides.Find( GeoPartObject ) )
|
|
{
|
|
// forget the last baked package since we changed the name
|
|
if( *FoundOverride != BaseName )
|
|
{
|
|
BakedStaticMeshPackagesForParts.Remove( GeoPartObject );
|
|
}
|
|
}
|
|
BakeNameOverrides.Add( GeoPartObject, BaseName );
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::RemoveBakingBaseNameOverride( const FHoudiniGeoPartObject& GeoPartObject )
|
|
{
|
|
return BakeNameOverrides.Remove( GeoPartObject ) > 0;
|
|
}
|
|
|
|
#endif // WITH_EDITOR
|
|
|
|
FText
|
|
UHoudiniAssetComponent::GetBakeFolder() const
|
|
{
|
|
// Empty indicates default - which is Content root
|
|
if( BakeFolder.IsEmpty() )
|
|
{
|
|
return LOCTEXT( "Game", "/Game" );
|
|
}
|
|
return BakeFolder;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetBakeFolder( const FString& Folder )
|
|
{
|
|
FText NewBakeFolder = FText::FromString( Folder );
|
|
if( !NewBakeFolder.EqualTo( BakeFolder ) )
|
|
{
|
|
BakeFolder = NewBakeFolder;
|
|
BakedStaticMeshPackagesForParts.Empty();
|
|
BakedMaterialPackagesForIds.Empty();
|
|
}
|
|
}
|
|
|
|
FText
|
|
UHoudiniAssetComponent::GetTempCookFolder() const
|
|
{
|
|
// Empty indicates default
|
|
if( TempCookFolder.IsEmpty() )
|
|
{
|
|
// Get runtime settings.
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
check( HoudiniRuntimeSettings );
|
|
|
|
return HoudiniRuntimeSettings->TemporaryCookFolder;
|
|
}
|
|
|
|
return TempCookFolder;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetTempCookFolder(const FString& Folder)
|
|
{
|
|
FText NewTempCookFolder = FText::FromString(Folder);
|
|
if (!NewTempCookFolder.EqualTo(TempCookFolder))
|
|
{
|
|
TempCookFolder = NewTempCookFolder;
|
|
}
|
|
}
|
|
|
|
FString UHoudiniAssetComponent::GetBakingBaseName( const FHoudiniGeoPartObject& GeoPartObject ) const
|
|
{
|
|
if( const FString* FoundOverride = BakeNameOverrides.Find( GeoPartObject ) )
|
|
{
|
|
return *FoundOverride;
|
|
}
|
|
if( GeoPartObject.HasCustomName() )
|
|
{
|
|
return GeoPartObject.PartName;
|
|
}
|
|
|
|
FString DisplayName;
|
|
if (GetOwner())
|
|
DisplayName = GetOwner()->GetName();
|
|
else
|
|
DisplayName = GetName();
|
|
|
|
return FString::Printf( TEXT( "%s_%d_%d_%d_%d" ),
|
|
*DisplayName,
|
|
GeoPartObject.ObjectId, GeoPartObject.GeoId,
|
|
GeoPartObject.PartId, GeoPartObject.SplitId );
|
|
}
|
|
|
|
FBoxSphereBounds
|
|
UHoudiniAssetComponent::CalcBounds( const FTransform & LocalToWorld ) const
|
|
{
|
|
FBoxSphereBounds LocalBounds;
|
|
FBox BoundingBox = GetAssetBounds();
|
|
if ( BoundingBox.GetExtent() == FVector::ZeroVector )
|
|
BoundingBox.ExpandBy( 1.0f );
|
|
|
|
LocalBounds = FBoxSphereBounds( BoundingBox );
|
|
// fix for offset bounds - maintain local bounds origin
|
|
LocalBounds.TransformBy(LocalToWorld);
|
|
|
|
const auto & LocalAttachedChildren = GetAttachChildren();
|
|
for (int32 Idx = 0; Idx < LocalAttachedChildren.Num(); ++Idx)
|
|
{
|
|
if ( !LocalAttachedChildren[ Idx ] )
|
|
continue;
|
|
|
|
FBoxSphereBounds ChildBounds = LocalAttachedChildren[ Idx ]->CalcBounds( LocalToWorld );
|
|
if ( !ChildBounds.ContainsNaN() )
|
|
LocalBounds = LocalBounds + ChildBounds;
|
|
}
|
|
|
|
return LocalBounds;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdateRenderingInformation()
|
|
{
|
|
// Need to send this to render thread at some point.
|
|
MarkRenderStateDirty();
|
|
|
|
// Update physics representation right away.
|
|
RecreatePhysicsState();
|
|
|
|
// Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent
|
|
// not propagating property changes to their own child StaticMeshComponents.
|
|
TArray< USceneComponent * > LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
for ( TArray< USceneComponent * >::TConstIterator Iter( LocalAttachChildren ); Iter; ++Iter )
|
|
{
|
|
USceneComponent * SceneComponent = *Iter;
|
|
if( SceneComponent )
|
|
SceneComponent->RecreatePhysicsState();
|
|
}
|
|
|
|
// Since we have new asset, we need to update bounds.
|
|
UpdateBounds();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostLoadReattachComponents()
|
|
{
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
|
|
StaticMeshComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform );
|
|
}
|
|
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value();
|
|
if( HoudiniSplineComponent && HoudiniSplineComponent->IsValidLowLevel() )
|
|
HoudiniSplineComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform );
|
|
}
|
|
|
|
for ( TMap< FString, UHoudiniHandleComponent * >::TIterator Iter( HandleComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniHandleComponent * HoudiniHandleComponent = Iter.Value();
|
|
if ( HoudiniHandleComponent && !HoudiniHandleComponent->IsPendingKill() )
|
|
HoudiniHandleComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform );
|
|
}
|
|
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator Iter(LandscapeComponents); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value().Get();
|
|
if ( HoudiniLandscape && HoudiniLandscape->IsValidLowLevel() )
|
|
HoudiniLandscape->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnComponentCreated()
|
|
{
|
|
// This event will only be fired for native Actor and native Component.
|
|
Super::OnComponentCreated();
|
|
|
|
if ( !GetOwner() || !GetOwner()->GetWorld() )
|
|
return;
|
|
|
|
if ( StaticMeshes.Num() == 0 )
|
|
{
|
|
// Create Houdini logo static mesh and component for it.
|
|
CreateStaticMeshHoudiniLogoResource( StaticMeshes );
|
|
}
|
|
|
|
// Create replacement material object.
|
|
if ( !HoudiniAssetComponentMaterials )
|
|
{
|
|
HoudiniAssetComponentMaterials =
|
|
NewObject< UHoudiniAssetComponentMaterials >(
|
|
this, UHoudiniAssetComponentMaterials::StaticClass(), NAME_None, RF_Public | RF_Transactional );
|
|
}
|
|
|
|
// Subscribe to delegates.
|
|
SubscribeEditorDelegates();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnComponentDestroyed( bool bDestroyingHierarchy )
|
|
{
|
|
// Release static mesh related resources.
|
|
ReleaseObjectGeoPartResources( StaticMeshes );
|
|
StaticMeshes.Empty();
|
|
StaticMeshComponents.Empty();
|
|
|
|
// Release all curve related resources.
|
|
ClearCurves();
|
|
|
|
// Destroy all parameters.
|
|
ClearParameters();
|
|
|
|
// Destroy all inputs.
|
|
ClearInputs();
|
|
|
|
// Destroy all instance inputs.
|
|
ClearInstanceInputs();
|
|
|
|
// Destroy all handles.
|
|
ClearHandles();
|
|
|
|
// Destroy all landscapes.
|
|
ClearLandscapes();
|
|
|
|
// Inform downstream assets that we are dieing.
|
|
ClearDownstreamAssets();
|
|
|
|
// Clear cook content temp file
|
|
ClearCookTempFile();
|
|
|
|
// Release all Houdini related resources.
|
|
ResetHoudiniResources();
|
|
|
|
// Unsubscribe from Editor events.
|
|
UnsubscribeEditorDelegates();
|
|
|
|
Super::OnComponentDestroyed( bDestroyingHierarchy );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::OnRegister()
|
|
{
|
|
Super::OnRegister();
|
|
|
|
// We need to recreate render states for loaded components.
|
|
if ( bLoadedComponent )
|
|
{
|
|
// Static meshes.
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
|
|
{
|
|
// Recreate render state.
|
|
StaticMeshComponent->RecreateRenderState_Concurrent();
|
|
|
|
// Need to recreate physics state.
|
|
StaticMeshComponent->RecreatePhysicsState();
|
|
}
|
|
}
|
|
|
|
// Instanced static meshes.
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if (!InstanceInput || InstanceInput->IsPendingKill())
|
|
continue;
|
|
|
|
// Recreate render state.
|
|
InstanceInput->RecreateRenderStates();
|
|
|
|
// Recreate physics state.
|
|
InstanceInput->RecreatePhysicsStates();
|
|
}
|
|
}
|
|
|
|
// We can now consider the asset as fully loaded
|
|
bFullyLoaded = true;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
UHoudiniAssetComponent::ContainsHoudiniLogoGeometry() const
|
|
{
|
|
return bContainsHoudiniLogoGeometry;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateStaticMeshHoudiniLogoResource( TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshMap )
|
|
{
|
|
if ( !bIsNativeComponent )
|
|
return;
|
|
|
|
// Create Houdini logo static mesh and component for it.
|
|
FHoudiniGeoPartObject HoudiniGeoPartObject;
|
|
StaticMeshMap.Add( HoudiniGeoPartObject, FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get() );
|
|
CreateObjectGeoPartResources( StaticMeshMap );
|
|
bContainsHoudiniLogoGeometry = true;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
UHoudiniAssetComponent::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
|
|
// If we are being cooked, we don't want to do anything
|
|
if( GIsCookerLoadingPackage )
|
|
return;
|
|
|
|
// Only do PostLoad stuff if we are in the editor world
|
|
if( UWorld* World = GetWorld() )
|
|
{
|
|
if( World->WorldType != EWorldType::Editor && World->WorldType != EWorldType::Inactive )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we aren't in _any_ world - how unusual
|
|
return;
|
|
}
|
|
|
|
SanitizePostLoad();
|
|
|
|
// We loaded a component which has no asset associated with it.
|
|
if ( !HoudiniAsset && StaticMeshes.Num() <= 0)
|
|
{
|
|
// Set geometry to be Houdini logo geometry, since we have no other geometry.
|
|
CreateStaticMeshHoudiniLogoResource( StaticMeshes );
|
|
return;
|
|
}
|
|
|
|
// Show busy cursor.
|
|
FScopedBusyCursor ScopedBusyCursor;
|
|
|
|
if ( StaticMeshes.Num() > 0 )
|
|
{
|
|
CreateObjectGeoPartResources( StaticMeshes );
|
|
}
|
|
else
|
|
{
|
|
// If the only component our owner has is us, then we should show the logo mesh
|
|
TArray< USceneComponent* > AllSceneComponents;
|
|
if(GetOwner())
|
|
GetOwner()->GetComponents<USceneComponent>(AllSceneComponents);
|
|
|
|
if (AllSceneComponents.Num() == 1 )
|
|
{
|
|
CreateStaticMeshHoudiniLogoResource( StaticMeshes );
|
|
}
|
|
}
|
|
|
|
// Perform post load initialization on parameters.
|
|
PostLoadInitializeParameters();
|
|
|
|
// Perform post load initialization on instance inputs.
|
|
PostLoadInitializeInstanceInputs();
|
|
|
|
// Post attach components to parent asset component.
|
|
PostLoadReattachComponents();
|
|
|
|
// Update mobility.
|
|
// It'll be changed to static if we generated a landscape,
|
|
// and if not, to movable if any of our children is movable
|
|
UpdateMobility();
|
|
|
|
// Need to update rendering information.
|
|
UpdateRenderingInformation();
|
|
|
|
// Force editor to redraw viewports.
|
|
if ( GEditor )
|
|
GEditor->RedrawAllViewports();
|
|
|
|
// Update properties panel after instantiation.
|
|
UpdateEditorProperties( false );
|
|
}
|
|
#endif
|
|
|
|
void
|
|
UHoudiniAssetComponent::Serialize( FArchive & Ar )
|
|
{
|
|
Super::Serialize( Ar );
|
|
|
|
Ar.UsingCustomVersion( FHoudiniCustomSerializationVersion::GUID );
|
|
|
|
if ( !Ar.IsSaving() && !Ar.IsLoading() )
|
|
return;
|
|
|
|
// Serialize component flags.
|
|
Ar << HoudiniAssetComponentFlagsPacked;
|
|
|
|
// State of this component.
|
|
EHoudiniAssetComponentState::Enum ComponentState = EHoudiniAssetComponentState::Invalid;
|
|
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
// Asset has been previously instantiated.
|
|
|
|
if ( HapiGUID.IsValid() )
|
|
{
|
|
// Asset is being re-cooked asynchronously.
|
|
ComponentState = EHoudiniAssetComponentState::BeingCooked;
|
|
}
|
|
else
|
|
{
|
|
// We have no pending asynchronous cook requests.
|
|
ComponentState = EHoudiniAssetComponentState::Instantiated;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( HoudiniAsset )
|
|
{
|
|
// Asset has not been instantiated and therefore must have asynchronous instantiation
|
|
// request in progress.
|
|
ComponentState = EHoudiniAssetComponentState::None;
|
|
}
|
|
else
|
|
{
|
|
// Component is in invalid state (for example is a default class object).
|
|
ComponentState = EHoudiniAssetComponentState::Invalid;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Serialize format version.
|
|
uint32 HoudiniAssetComponentVersion = Ar.CustomVer( FHoudiniCustomSerializationVersion::GUID );
|
|
Ar << HoudiniAssetComponentVersion;
|
|
|
|
// Serialize component state.
|
|
SerializeEnumeration< EHoudiniAssetComponentState::Enum >( Ar, ComponentState );
|
|
|
|
// Serialize scaling information and import axis.
|
|
Ar << GeneratedGeometryScaleFactor;
|
|
Ar << TransformScaleFactor;
|
|
SerializeEnumeration< EHoudiniRuntimeSettingsAxisImport >( Ar, ImportAxis );
|
|
|
|
// Serialize generated component GUID.
|
|
Ar << ComponentGUID;
|
|
|
|
// If component is in invalid state, we can skip the rest of serialization.
|
|
if ( ComponentState == EHoudiniAssetComponentState::Invalid )
|
|
return;
|
|
|
|
// If we have no asset, we can stop.
|
|
if ( !HoudiniAsset && Ar.IsSaving() )
|
|
return;
|
|
|
|
// Serialize Houdini asset.
|
|
UHoudiniAsset * HoudiniSerializedAsset = nullptr;
|
|
|
|
if ( Ar.IsSaving() )
|
|
HoudiniSerializedAsset = HoudiniAsset;
|
|
|
|
Ar << HoudiniSerializedAsset;
|
|
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
if ( Ar.IsTransacting() && HoudiniAsset != HoudiniSerializedAsset )
|
|
{
|
|
bTransactionAssetChange = true;
|
|
PreviousTransactionHoudiniAsset = HoudiniAsset;
|
|
}
|
|
|
|
HoudiniAsset = HoudiniSerializedAsset;
|
|
}
|
|
|
|
// If we are going into playmode, save asset id.
|
|
// NOTE: bIsPlayModeActive_Unused is known to have been mistakenly saved to disk as ON,
|
|
// the following fixes that case - should only happen once when first loading
|
|
if( Ar.IsLoading() && bIsPlayModeActive_Unused )
|
|
{
|
|
HAPI_NodeId TempId;
|
|
Ar << TempId;
|
|
bIsPlayModeActive_Unused = false;
|
|
}
|
|
|
|
// Serialization of default preset.
|
|
Ar << DefaultPresetBuffer;
|
|
|
|
// Serialization of preset.
|
|
{
|
|
bool bPresetSaved = false;
|
|
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
bPresetSaved = true;
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
FHoudiniEngineUtils::GetAssetPreset( AssetId, PresetBuffer );
|
|
}
|
|
}
|
|
|
|
Ar << bPresetSaved;
|
|
|
|
if ( bPresetSaved )
|
|
{
|
|
Ar << PresetBuffer;
|
|
}
|
|
}
|
|
|
|
// Serialize parameters.
|
|
SerializeParameters( Ar );
|
|
|
|
// Serialize parameters name map.
|
|
if ( HoudiniAssetComponentVersion >= VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP )
|
|
{
|
|
Ar << ParameterByName;
|
|
}
|
|
else
|
|
{
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
ParameterByName.Empty();
|
|
|
|
// Otherwise if we are loading an older serialization format, we can reconstruct parameters name map.
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() )
|
|
ParameterByName.Add( HoudiniAssetParameter->GetParameterName(), HoudiniAssetParameter );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Serialize inputs.
|
|
SerializeInputs( Ar );
|
|
|
|
// Serialize material replacements and material assignments.
|
|
Ar << HoudiniAssetComponentMaterials;
|
|
|
|
// Serialize geo parts and generated static meshes.
|
|
Ar << StaticMeshes;
|
|
Ar << StaticMeshComponents;
|
|
|
|
// Serialize instance inputs (we do this after geometry loading as we might need it).
|
|
SerializeInstanceInputs( Ar );
|
|
|
|
// Serialize curves.
|
|
Ar << SplineComponents;
|
|
|
|
// Serialize handles.
|
|
Ar << HandleComponents;
|
|
|
|
// Serialize downstream asset connections.
|
|
Ar << DownstreamAssetConnections;
|
|
|
|
// Serialize Landscape/GeoPart map
|
|
if ( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES )
|
|
{
|
|
Ar << LandscapeComponents;
|
|
}
|
|
|
|
if( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE )
|
|
{
|
|
Ar << BakeNameOverrides;
|
|
}
|
|
|
|
TArray<UPackage *> DirtyPackages;
|
|
if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES)
|
|
{
|
|
TMap<FString, FString> SavedPackages;
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
for ( TMap<FString, TWeakObjectPtr< UPackage > > ::TIterator IterPackage(CookedTemporaryPackages); IterPackage; ++IterPackage )
|
|
{
|
|
if (!IterPackage.Value().IsValid())
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( !Package || UPackage::IsEmptyPackage( Package ) )
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
|
|
FString sValue = Package->GetFName().ToString();
|
|
|
|
FString sKey = IterPackage.Key();
|
|
SavedPackages.Add( sKey, sValue );
|
|
}
|
|
}
|
|
|
|
Ar << SavedPackages;
|
|
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
for ( TMap<FString, FString > ::TIterator IterPackage( SavedPackages ); IterPackage; ++IterPackage )
|
|
{
|
|
FString sKey = IterPackage.Key();
|
|
FString PackageFile = IterPackage.Value();
|
|
if (!FPackageName::DoesPackageExist(PackageFile))
|
|
continue;
|
|
|
|
UPackage * Package = nullptr;
|
|
if ( !PackageFile.IsEmpty() )
|
|
{
|
|
Package = FindPackage(nullptr, *PackageFile);
|
|
}
|
|
|
|
if ( !Package )
|
|
continue;
|
|
|
|
if ( Package->IsDirty() )
|
|
DirtyPackages.Add(Package);
|
|
|
|
CookedTemporaryPackages.Add( sKey, Package );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS )
|
|
{
|
|
// Temporary Mesh Packages
|
|
TMap<FHoudiniGeoPartObject, FString> MeshPackages;
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
for ( TMap<FHoudiniGeoPartObject, TWeakObjectPtr< UPackage > > ::TIterator IterPackage(CookedTemporaryStaticMeshPackages); IterPackage; ++IterPackage )
|
|
{
|
|
if ( !IterPackage.Value().IsValid() )
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( !Package || UPackage::IsEmptyPackage( Package ) )
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
|
|
FString sValue = Package->GetFName().ToString();
|
|
|
|
FHoudiniGeoPartObject Key = IterPackage.Key();
|
|
MeshPackages.Add( Key, sValue );
|
|
}
|
|
}
|
|
|
|
Ar << MeshPackages;
|
|
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
for ( TMap<FHoudiniGeoPartObject, FString > ::TIterator IterPackage( MeshPackages ); IterPackage; ++IterPackage )
|
|
{
|
|
FHoudiniGeoPartObject Key = IterPackage.Key();
|
|
FString PackageFile = IterPackage.Value();
|
|
if (!FPackageName::DoesPackageExist(PackageFile))
|
|
continue;
|
|
|
|
UPackage * Package = nullptr;
|
|
if ( !PackageFile.IsEmpty() )
|
|
{
|
|
Package = FindPackage(nullptr, *PackageFile);
|
|
}
|
|
|
|
if ( !Package )
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
|
|
CookedTemporaryStaticMeshPackages.Add( Key, Package );
|
|
}
|
|
}
|
|
|
|
// Temporary Landscape Layers Packages
|
|
TMap<FString, FHoudiniGeoPartObject> LayerPackages;
|
|
if ( Ar.IsSaving() )
|
|
{
|
|
for ( TMap<TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject > ::TIterator IterPackage( CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage )
|
|
{
|
|
if (!IterPackage.Key().IsValid())
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Key().Get();
|
|
if ( !Package || UPackage::IsEmptyPackage( Package ) )
|
|
continue;
|
|
|
|
if ( Package->IsDirty() )
|
|
DirtyPackages.Add(Package);
|
|
|
|
FString sKey = Package->GetFName().ToString();
|
|
|
|
FHoudiniGeoPartObject Value = IterPackage.Value();
|
|
LayerPackages.Add( sKey, Value );
|
|
}
|
|
}
|
|
|
|
Ar << LayerPackages;
|
|
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
for ( TMap<FString, FHoudiniGeoPartObject > ::TIterator IterPackage( LayerPackages ); IterPackage; ++IterPackage )
|
|
{
|
|
FHoudiniGeoPartObject Value = IterPackage.Value();
|
|
FString PackageFile = IterPackage.Key();
|
|
if ( !FPackageName::DoesPackageExist(PackageFile) )
|
|
continue;
|
|
|
|
UPackage * Package = nullptr;
|
|
if ( !PackageFile.IsEmpty() )
|
|
{
|
|
Package = FindPackage(nullptr, *PackageFile);
|
|
}
|
|
|
|
if ( !Package )
|
|
continue;
|
|
|
|
if ( Package->IsDirty() )
|
|
DirtyPackages.Add(Package);
|
|
|
|
CookedTemporaryLandscapeLayers.Add( Package, Value );
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (DirtyPackages.Num() > 0)
|
|
{
|
|
if (Ar.IsSaving() && !Ar.IsTransacting())
|
|
{
|
|
// Save the dirty packages that we're still using
|
|
const bool bCheckDirty = false;
|
|
const bool bPromptToSave = false;
|
|
FEditorFileUtils::PromptForCheckoutAndSave(DirtyPackages, bCheckDirty, bPromptToSave);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( Ar.IsLoading() && bIsNativeComponent )
|
|
{
|
|
// This component has been loaded.
|
|
bLoadedComponent = true;
|
|
bFullyLoaded = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetStaticMeshGenerationParameters( UStaticMesh * StaticMesh ) const
|
|
{
|
|
#if WITH_EDITOR
|
|
if( !StaticMesh )
|
|
return;
|
|
|
|
// Make sure static mesh has a new lighting guid.
|
|
StaticMesh->LightingGuid = FGuid::NewGuid();
|
|
StaticMesh->LODGroup = NAME_None;
|
|
|
|
// Set resolution of lightmap.
|
|
StaticMesh->LightMapResolution = GeneratedLightMapResolution;
|
|
|
|
// Set Bias multiplier for Light Propagation Volume lighting.
|
|
StaticMesh->LpvBiasMultiplier = GeneratedLpvBiasMultiplier;
|
|
|
|
// Set the global light map coordinate index if it looks valid
|
|
if ( StaticMesh->RenderData.IsValid() && StaticMesh->RenderData->LODResources.Num() > 0)
|
|
{
|
|
int32 NumUVs = StaticMesh->RenderData->LODResources[0].GetNumTexCoords();
|
|
if ( NumUVs > GeneratedLightMapCoordinateIndex )
|
|
{
|
|
StaticMesh->LightMapCoordinateIndex = GeneratedLightMapCoordinateIndex;
|
|
}
|
|
}
|
|
|
|
// Set method for LOD texture factor computation.
|
|
/* TODO_414
|
|
//StaticMesh->bUseMaximumStreamingTexelRatio = bGeneratedUseMaximumStreamingTexelRatio;
|
|
|
|
// Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT
|
|
// StaticMesh->StreamingDistanceMultiplier = GeneratedStreamingDistanceMultiplier;
|
|
*/
|
|
|
|
// Add user data.
|
|
for( int32 AssetUserDataIdx = 0; AssetUserDataIdx < GeneratedAssetUserData.Num(); ++AssetUserDataIdx )
|
|
StaticMesh->AddAssetUserData( GeneratedAssetUserData[ AssetUserDataIdx ] );
|
|
|
|
StaticMesh->CreateBodySetup();
|
|
UBodySetup * BodySetup = StaticMesh->BodySetup;
|
|
check( BodySetup );
|
|
|
|
// Set flag whether physics triangle mesh will use double sided faces when doing scene queries.
|
|
BodySetup->bDoubleSidedGeometry = bGeneratedDoubleSidedGeometry;
|
|
|
|
// Assign physical material for simple collision.
|
|
BodySetup->PhysMaterial = GeneratedPhysMaterial;
|
|
|
|
BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&DefaultBodyInstance);
|
|
|
|
// Assign collision trace behavior.
|
|
BodySetup->CollisionTraceFlag = GeneratedCollisionTraceFlag;
|
|
|
|
// Assign walkable slope behavior.
|
|
BodySetup->WalkableSlopeOverride = GeneratedWalkableSlopeOverride;
|
|
|
|
// We want to use all of geometry for collision detection purposes.
|
|
BodySetup->bMeshCollideAll = true;
|
|
|
|
#endif
|
|
}
|
|
|
|
const TArray< UHoudiniAssetInstanceInputField * >
|
|
UHoudiniAssetComponent::GetAllInstanceInputFields() const
|
|
{
|
|
TArray< UHoudiniAssetInstanceInputField * > AllInstanceInputFields;
|
|
|
|
// Duplicate instanced static mesh components.
|
|
for (auto& InstanceInput : InstanceInputs)
|
|
{
|
|
if (!InstanceInput || InstanceInput->IsPendingKill())
|
|
continue;
|
|
|
|
const TArray< UHoudiniAssetInstanceInputField * > CurrentInstanceInputFields = InstanceInput->GetInstanceInputFields();
|
|
for (auto currentInputField : CurrentInstanceInputFields)
|
|
AllInstanceInputFields.Add(currentInputField);
|
|
}
|
|
|
|
return AllInstanceInputFields;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
AActor *
|
|
UHoudiniAssetComponent::CloneComponentsAndCreateActor()
|
|
{
|
|
// Display busy cursor.
|
|
FScopedBusyCursor ScopedBusyCursor;
|
|
|
|
ULevel * Level = GetHoudiniAssetActorOwner() ? GetHoudiniAssetActorOwner()->GetLevel() : nullptr;
|
|
if ( !Level )
|
|
Level = GWorld->GetCurrentLevel();
|
|
|
|
AActor * Actor = NewObject< AActor >( Level, NAME_None );
|
|
Actor->AddToRoot();
|
|
|
|
USceneComponent * RootComponent =
|
|
NewObject< USceneComponent >( Actor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional );
|
|
|
|
RootComponent->SetMobility(EComponentMobility::Static);
|
|
RootComponent->bVisualizeComponent = true;
|
|
|
|
const FTransform & ComponentWorldTransform = GetComponentTransform();
|
|
RootComponent->SetWorldLocationAndRotation(
|
|
ComponentWorldTransform.GetLocation(),
|
|
ComponentWorldTransform.GetRotation() );
|
|
|
|
Actor->SetRootComponent( RootComponent );
|
|
Actor->AddInstanceComponent( RootComponent );
|
|
|
|
RootComponent->RegisterComponent();
|
|
|
|
// Duplicate static mesh components.
|
|
{
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
// Retrieve referenced static mesh component.
|
|
UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh );
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
// Bake the referenced static mesh.
|
|
UStaticMesh * OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage(
|
|
StaticMesh, this, HoudiniGeoPartObject, EBakeMode::CreateNewAssets );
|
|
|
|
if ( !OutStaticMesh || OutStaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
FAssetRegistryModule::AssetCreated( OutStaticMesh );
|
|
|
|
// Create static mesh component for baked mesh.
|
|
UStaticMeshComponent * DuplicatedComponent =
|
|
NewObject< UStaticMeshComponent >( Actor, UStaticMeshComponent::StaticClass(), NAME_None );//, RF_Transactional );
|
|
|
|
if ( !DuplicatedComponent || DuplicatedComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
Actor->AddInstanceComponent( DuplicatedComponent );
|
|
|
|
DuplicatedComponent->SetStaticMesh( OutStaticMesh );
|
|
DuplicatedComponent->SetVisibility( true );
|
|
DuplicatedComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix );
|
|
|
|
// If this is a collision geo, we need to make it invisible.
|
|
if ( HoudiniGeoPartObject.IsCollidable() )
|
|
{
|
|
DuplicatedComponent->SetVisibility( false );
|
|
DuplicatedComponent->SetHiddenInGame( true );
|
|
DuplicatedComponent->SetCollisionProfileName( FName( TEXT( "InvisibleWall" ) ) );
|
|
}
|
|
|
|
// Reapply the uproperties modified by attributes on the duplicated component
|
|
FHoudiniEngineUtils::UpdateUPropertyAttributesOnObject( DuplicatedComponent, HoudiniGeoPartObject );
|
|
|
|
DuplicatedComponent->AttachToComponent( RootComponent, FAttachmentTransformRules::KeepRelativeTransform );
|
|
DuplicatedComponent->RegisterComponent();
|
|
}
|
|
}
|
|
|
|
// Duplicate instanced static mesh components.
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
InstanceInput->CloneComponentsAndAttachToActor( Actor );
|
|
}
|
|
|
|
return Actor;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsCookingEnabled() const
|
|
{
|
|
return FHoudiniEngine::Get().GetEnableCookingGlobal() && bEnableCooking;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostEditUndo()
|
|
{
|
|
// We need to make sure that all mesh components in the maps are valid ones
|
|
CleanUpAttachedStaticMeshComponents();
|
|
|
|
// Check the cooked materials refer to something..
|
|
bool bCookedContentNeedRecook = false;
|
|
for ( TMap< FString, TWeakObjectPtr< UPackage > > ::TIterator IterPackage( CookedTemporaryPackages ); IterPackage; ++IterPackage )
|
|
{
|
|
if ( bCookedContentNeedRecook )
|
|
break;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( Package )
|
|
{
|
|
FString PackageName = Package->GetName();
|
|
if ( !PackageName.IsEmpty() && ( PackageName != TEXT( "None" ) ) )
|
|
continue;
|
|
}
|
|
|
|
bCookedContentNeedRecook = true;
|
|
}
|
|
|
|
// Check the cooked meshes refer to something..
|
|
for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr< UPackage > > ::TIterator IterPackage( CookedTemporaryStaticMeshPackages ); IterPackage; ++IterPackage )
|
|
{
|
|
if ( bCookedContentNeedRecook )
|
|
break;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( Package )
|
|
{
|
|
FString PackageName = Package->GetName();
|
|
if ( !PackageName.IsEmpty() && ( PackageName != TEXT("None") ) )
|
|
continue;
|
|
}
|
|
|
|
bCookedContentNeedRecook = true;
|
|
}
|
|
|
|
// Check the cooked landscape layers refer to something..
|
|
for ( TMap< TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject >::TIterator IterPackage( CookedTemporaryLandscapeLayers ); IterPackage; ++IterPackage )
|
|
{
|
|
if ( bCookedContentNeedRecook )
|
|
break;
|
|
|
|
UPackage * Package = IterPackage.Key().Get();
|
|
if ( Package )
|
|
{
|
|
FString PackageName = Package->GetName();
|
|
if ( !PackageName.IsEmpty() && ( PackageName != TEXT("None") ) )
|
|
continue;
|
|
}
|
|
|
|
bCookedContentNeedRecook = true;
|
|
}
|
|
|
|
|
|
if ( bCookedContentNeedRecook )
|
|
StartTaskAssetCookingManual();
|
|
|
|
Super::PostEditUndo();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostEditImport()
|
|
{
|
|
Super::PostEditImport();
|
|
|
|
AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor( GetOwner(), TEXT( "" ) );
|
|
if ( CopiedActor )
|
|
OnComponentClipboardCopy( CopiedActor->HoudiniAssetComponent );
|
|
}
|
|
|
|
void UHoudiniAssetComponent::SanitizePostLoad()
|
|
{
|
|
AActor* Owner = GetOwner();
|
|
|
|
for(auto Iter : Parameters)
|
|
{
|
|
UHoudiniAssetParameter* Param = Iter.Value;
|
|
if( !Param || Param->IsPendingKill() )
|
|
{
|
|
// we have at least one bad parameter, clear them all
|
|
FMessageLog("LoadErrors").Error(LOCTEXT("NullParameterFound", "Houdini Engine: Null parameter found, clearing parameters"))
|
|
->AddToken(FUObjectToken::Create(Owner));
|
|
Parameters.Empty();
|
|
ParameterByName.Empty();
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(auto Input : Inputs)
|
|
{
|
|
if( !Input || Input->IsPendingKill() )
|
|
{
|
|
// we have at least one bad input, clear them all
|
|
FMessageLog("LoadErrors").Error(LOCTEXT("NullInputFound", "Houdini Engine: Null input found, clearing inputs"))
|
|
->AddToken(FUObjectToken::Create(Owner));
|
|
Inputs.Empty();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Patch any invalid material references
|
|
for( auto& SMCElem : StaticMeshComponents )
|
|
{
|
|
UStaticMesh* SM = SMCElem.Key;
|
|
UMeshComponent* SMC = SMCElem.Value;
|
|
if ( !SM || SM->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( !SMC || SMC->IsPendingKill() )
|
|
continue;
|
|
|
|
auto& StaticMeshMaterials = SM->StaticMaterials;
|
|
for( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx )
|
|
{
|
|
if( nullptr == StaticMeshMaterials[ MaterialIdx ].MaterialInterface )
|
|
{
|
|
auto DefaultMI = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get();
|
|
StaticMeshMaterials[ MaterialIdx ].MaterialInterface = DefaultMI;
|
|
SMC->SetMaterial( MaterialIdx, DefaultMI );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Fetch the dirty package that need to be reloaded
|
|
TArray<UPackage *> DirtyPackages;
|
|
for (TMap<FString, TWeakObjectPtr< UPackage > > ::TIterator IterPackage(CookedTemporaryPackages); IterPackage; ++IterPackage)
|
|
{
|
|
if (!IterPackage.Value().IsValid())
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if (!Package || UPackage::IsEmptyPackage(Package))
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
}
|
|
|
|
for (TMap<FHoudiniGeoPartObject, TWeakObjectPtr< UPackage > > ::TIterator IterPackage(CookedTemporaryStaticMeshPackages); IterPackage; ++IterPackage)
|
|
{
|
|
if (!IterPackage.Value().IsValid())
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if (!Package || UPackage::IsEmptyPackage(Package))
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
}
|
|
|
|
for (TMap<TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject > ::TIterator IterPackage(CookedTemporaryLandscapeLayers); IterPackage; ++IterPackage)
|
|
{
|
|
if (!IterPackage.Key().IsValid())
|
|
continue;
|
|
|
|
UPackage * Package = IterPackage.Key().Get();
|
|
if (!Package || UPackage::IsEmptyPackage(Package))
|
|
continue;
|
|
|
|
if (Package->IsDirty())
|
|
DirtyPackages.Add(Package);
|
|
}
|
|
|
|
if (DirtyPackages.Num() > 0)
|
|
{
|
|
FlushAsyncLoading();
|
|
|
|
FText ErrorMessage;
|
|
UPackageTools::ReloadPackages(DirtyPackages, ErrorMessage, UPackageTools::EReloadPackagesInteractionMode::AssumePositive);
|
|
}
|
|
*/
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
|
|
if ( HoudiniRuntimeSettings )
|
|
{
|
|
// Copy cooking defaults from settings.
|
|
bEnableCooking = HoudiniRuntimeSettings->bEnableCooking;
|
|
bUploadTransformsToHoudiniEngine = HoudiniRuntimeSettings->bUploadTransformsToHoudiniEngine;
|
|
bTransformChangeTriggersCooks = HoudiniRuntimeSettings->bTransformChangeTriggersCooks;
|
|
|
|
// Copy static mesh generation parameters from settings.
|
|
bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry;
|
|
GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial;
|
|
DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance;
|
|
GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag;
|
|
GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier;
|
|
GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution;
|
|
GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex;
|
|
bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio;
|
|
GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier;
|
|
GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride;
|
|
GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings;
|
|
GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData;
|
|
GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
bool
|
|
UHoudiniAssetComponent::LocateStaticMeshes(
|
|
const FString & ObjectName,
|
|
TMap< FString, TArray< FHoudiniGeoPartObject > > & InOutObjectsToInstance, bool bSubstring ) const
|
|
{
|
|
// See if map has entry for this object name.
|
|
if ( !InOutObjectsToInstance.Contains( ObjectName ) )
|
|
InOutObjectsToInstance.Add( ObjectName, TArray< FHoudiniGeoPartObject >() );
|
|
|
|
{
|
|
// Get array entry for this object name.
|
|
TArray< FHoudiniGeoPartObject > & Objects = InOutObjectsToInstance[ ObjectName ];
|
|
|
|
// Go through all geo part objects and see if we have matches.
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator Iter( StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( ObjectName.Len() > 0 )
|
|
{
|
|
if ( bSubstring && ObjectName.Len() >= HoudiniGeoPartObject.ObjectName.Len() )
|
|
{
|
|
int32 Index = ObjectName.Find(
|
|
*HoudiniGeoPartObject.ObjectName,
|
|
ESearchCase::IgnoreCase,
|
|
ESearchDir::FromEnd, INDEX_NONE );
|
|
|
|
if ( ( Index != -1 ) && ( Index + HoudiniGeoPartObject.ObjectName.Len() == ObjectName.Len() ) )
|
|
Objects.Add( HoudiniGeoPartObject );
|
|
}
|
|
else if ( HoudiniGeoPartObject.ObjectName.Equals( ObjectName ) )
|
|
{
|
|
Objects.Add( HoudiniGeoPartObject );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort arrays.
|
|
for ( TMap< FString, TArray< FHoudiniGeoPartObject > >::TIterator Iter( InOutObjectsToInstance ); Iter; ++Iter )
|
|
{
|
|
TArray< FHoudiniGeoPartObject > & Objects = Iter.Value();
|
|
Objects.Sort( FHoudiniGeoPartObjectSortPredicate() );
|
|
}
|
|
|
|
return InOutObjectsToInstance.Num() > 0;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::LocateStaticMeshes(
|
|
int32 ObjectToInstanceId,
|
|
TArray< FHoudiniGeoPartObject > & InOutObjectsToInstance ) const
|
|
{
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator Iter( StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject& HoudiniGeoPartObject = Iter.Key();
|
|
if ( HoudiniGeoPartObject.ObjectId == ObjectToInstanceId )
|
|
{
|
|
// Check that this part isn't being instanced at the part level
|
|
if ( !HoudiniGeoPartObject.HapiPartIsInstanced() )
|
|
InOutObjectsToInstance.Add( HoudiniGeoPartObject );
|
|
}
|
|
}
|
|
|
|
// Sort array.
|
|
InOutObjectsToInstance.Sort( FHoudiniGeoPartObjectSortPredicate() );
|
|
|
|
return InOutObjectsToInstance.Num() > 0;
|
|
}
|
|
|
|
|
|
FHoudiniGeoPartObject
|
|
UHoudiniAssetComponent::LocateGeoPartObject( UStaticMesh * StaticMesh ) const
|
|
{
|
|
FHoudiniGeoPartObject GeoPartObject;
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
return GeoPartObject;
|
|
|
|
const FHoudiniGeoPartObject * FoundGeoPartObject = StaticMeshes.FindKey( StaticMesh );
|
|
if ( FoundGeoPartObject )
|
|
GeoPartObject = *FoundGeoPartObject;
|
|
|
|
return GeoPartObject;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::IsPIEActive() const
|
|
{
|
|
#if WITH_EDITOR
|
|
for( const FWorldContext& Context : GEngine->GetWorldContexts() )
|
|
{
|
|
if( Context.WorldType == EWorldType::PIE )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateCurves( const TArray< FHoudiniGeoPartObject > & FoundCurves )
|
|
{
|
|
bool bCurveCreated = false;
|
|
|
|
TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* > NewSplineComponents;
|
|
for ( TArray< FHoudiniGeoPartObject >::TConstIterator Iter( FoundCurves ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject = *Iter;
|
|
|
|
// Retrieve node id from geo part.
|
|
HAPI_NodeId NodeId = HoudiniGeoPartObject.HapiGeoGetNodeId( AssetId );
|
|
if ( NodeId == -1 )
|
|
{
|
|
// Invalid node id.
|
|
continue;
|
|
}
|
|
|
|
if ( !HoudiniGeoPartObject.HasParameters( AssetId ) )
|
|
{
|
|
// We have no parameters on this curve.
|
|
continue;
|
|
}
|
|
|
|
// We need to cook the spline node.
|
|
FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), NodeId, nullptr);
|
|
|
|
FString CurvePointsString;
|
|
EHoudiniSplineComponentType::Enum CurveTypeValue = EHoudiniSplineComponentType::Bezier;
|
|
EHoudiniSplineComponentMethod::Enum CurveMethodValue = EHoudiniSplineComponentMethod::CVs;
|
|
int32 CurveClosed = 1;
|
|
|
|
HAPI_AttributeInfo AttributeRefinedCurvePositions;
|
|
FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions);
|
|
|
|
TArray< float > RefinedCurvePositions;
|
|
if ( !FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(
|
|
HoudiniGeoPartObject, HAPI_UNREAL_ATTRIB_POSITION,
|
|
AttributeRefinedCurvePositions, RefinedCurvePositions ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !AttributeRefinedCurvePositions.exists && RefinedCurvePositions.Num() > 0 )
|
|
continue;
|
|
|
|
// Transfer refined positions to position vector and perform necessary axis swap.
|
|
TArray< FVector > CurveDisplayPoints;
|
|
FHoudiniEngineUtils::ConvertScaleAndFlipVectorData( RefinedCurvePositions, CurveDisplayPoints );
|
|
|
|
if ( !FHoudiniEngineUtils::HapiGetParameterDataAsString(
|
|
NodeId, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString) ||
|
|
!FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
|
|
NodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, (int32) EHoudiniSplineComponentType::Bezier, (int32&) CurveTypeValue ) ||
|
|
!FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
|
|
NodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, (int32) EHoudiniSplineComponentMethod::CVs, (int32&) CurveMethodValue ) ||
|
|
!FHoudiniEngineUtils::HapiGetParameterDataAsInteger(
|
|
NodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 1, CurveClosed ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Process coords string and extract positions.
|
|
TArray< FVector > CurvePositions;
|
|
FHoudiniEngineUtils::ExtractStringPositions( CurvePointsString, CurvePositions );
|
|
|
|
// Check if this curve already exists.
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = LocateSplineComponent( HoudiniGeoPartObject );
|
|
|
|
if ( HoudiniSplineComponent )
|
|
{
|
|
// The curve already exists, we can reuse it.
|
|
// Remove it from old map.
|
|
SplineComponents.Remove( HoudiniGeoPartObject );
|
|
}
|
|
else
|
|
{
|
|
// We need to create a new curve.
|
|
HoudiniSplineComponent = NewObject< UHoudiniSplineComponent >(
|
|
this, UHoudiniSplineComponent::StaticClass(),
|
|
NAME_None, RF_Public | RF_Transactional );
|
|
|
|
bCurveCreated = true;
|
|
}
|
|
|
|
// Set the GeoPartObject
|
|
HoudiniSplineComponent->SetHoudiniGeoPartObject( HoudiniGeoPartObject );
|
|
|
|
// If we have no parent, we need to re-attach.
|
|
if ( !HoudiniSplineComponent->GetAttachParent() )
|
|
HoudiniSplineComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform );
|
|
|
|
HoudiniSplineComponent->SetVisibility( true );
|
|
|
|
// If component is not registered, register it.
|
|
if ( !HoudiniSplineComponent->IsRegistered() )
|
|
HoudiniSplineComponent->RegisterComponent();
|
|
|
|
// Add to map of components.
|
|
NewSplineComponents.Add( HoudiniGeoPartObject, HoudiniSplineComponent );
|
|
|
|
// Transform the component by transformation provided by HAPI.
|
|
HoudiniSplineComponent->SetRelativeTransform( HoudiniGeoPartObject.TransformMatrix );
|
|
|
|
// Create Transform for the HoudiniSplineComponents
|
|
TArray< FTransform > CurvePoints;
|
|
CurvePoints.SetNumUninitialized(CurvePositions.Num());
|
|
|
|
FTransform trans = FTransform::Identity;
|
|
for (int32 n = 0; n < CurvePoints.Num(); n++)
|
|
{
|
|
trans.SetLocation(CurvePositions[n]);
|
|
CurvePoints[n] = trans;
|
|
}
|
|
|
|
// Construct curve from available data.
|
|
HoudiniSplineComponent->Construct(
|
|
HoudiniGeoPartObject, CurvePoints, CurveDisplayPoints, CurveTypeValue,
|
|
CurveMethodValue, ( CurveClosed == 1 ) );
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// The editor caches the current selection visualizer, so we need to trick
|
|
// and pretend the selection has changed so that the HSplineVisualizer can be drawn immediately
|
|
if ( bCurveCreated && GUnrealEd )
|
|
GUnrealEd->NoteSelectionChange();
|
|
#endif
|
|
|
|
ClearCurves();
|
|
SplineComponents = NewSplineComponents;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateParameters()
|
|
{
|
|
TMap< HAPI_ParmId, class UHoudiniAssetParameter * > NewParameters;
|
|
if( FHoudiniParamUtils::Build(AssetId, this, Parameters, NewParameters) )
|
|
{
|
|
bEditorPropertiesNeedFullUpdate = true;
|
|
|
|
// Remove all unused parameters.
|
|
ClearParameters();
|
|
|
|
// Update parameters.
|
|
Parameters = NewParameters;
|
|
for ( auto& ParmPair : NewParameters )
|
|
{
|
|
UHoudiniAssetParameter* Param = ParmPair.Value;
|
|
if( Param && !Param->IsPendingKill() )
|
|
ParameterByName.Add( Param->GetParameterName(), Param );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::NotifyParameterChanged( UHoudiniAssetParameter * HoudiniAssetParameter )
|
|
{
|
|
if ( bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId( AssetId ) && !bAssetIsBeingInstantiated )
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
|
|
if ( HoudiniAssetParameter )
|
|
{
|
|
// Some parameter types won't require a full update of the editor panel
|
|
// This will avoid breaking the current selection
|
|
UClass* FoundClass = HoudiniAssetParameter->GetClass();
|
|
/*
|
|
if ( FoundClass->IsChildOf< UHoudiniAssetParameterFloat >()
|
|
|| FoundClass->IsChildOf< UHoudiniAssetParameterInt >()
|
|
|| FoundClass->IsChildOf< UHoudiniAssetParameterString >() )
|
|
*/
|
|
if ( !FoundClass->IsChildOf< UHoudiniAssetInput >() )
|
|
bEditorPropertiesNeedFullUpdate = false;
|
|
}
|
|
|
|
bParametersChanged = true;
|
|
StartHoudiniTicking();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::NotifyHoudiniSplineChanged( UHoudiniSplineComponent * HoudiniSplineComponent )
|
|
{
|
|
if ( bLoadedComponent && !FHoudiniEngineUtils::IsValidNodeId( AssetId ) && !bAssetIsBeingInstantiated )
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
|
|
bParametersChanged = true;
|
|
StartHoudiniTicking();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UnmarkChangedParameters()
|
|
{
|
|
if ( bParametersChanged )
|
|
{
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() )
|
|
continue;
|
|
|
|
// If parameter has changed, unmark it.
|
|
if ( HoudiniAssetParameter->HasChanged() )
|
|
HoudiniAssetParameter->UnmarkChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UploadChangedParameters()
|
|
{
|
|
bool Success = true;
|
|
|
|
if ( bParametersChanged )
|
|
{
|
|
// Upload inputs.
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
// If input has changed, upload it to HAPI.
|
|
if ( HoudiniAssetInput->HasChanged() )
|
|
{
|
|
Success &= HoudiniAssetInput->UploadParameterValue();
|
|
}
|
|
}
|
|
|
|
// Upload parameters.
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill())
|
|
continue;
|
|
|
|
// If parameter has changed, upload it to HAPI.
|
|
if ( HoudiniAssetParameter->HasChanged() )
|
|
{
|
|
Success &= HoudiniAssetParameter->UploadParameterValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !Success )
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("%s UploadChangedParameters failed"), GetOwner() ? *GetOwner()->GetName() : *GetName());
|
|
}
|
|
|
|
// We no longer have changed parameters.
|
|
bParametersChanged = false;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdateLoadedParameters()
|
|
{
|
|
if (!FHoudiniEngineUtils::IsValidNodeId(AssetId))
|
|
return;
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetParameter->SetNodeId( AssetId );
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::CreateHandles()
|
|
{
|
|
if ( !FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
// There's no Houdini asset, we can return.
|
|
return false;
|
|
}
|
|
|
|
HAPI_AssetInfo AssetInfo;
|
|
FHoudiniApi::AssetInfo_Init(&AssetInfo);
|
|
if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) != HAPI_RESULT_SUCCESS )
|
|
return false;
|
|
|
|
FHandleComponentMap NewHandleComponents;
|
|
|
|
// If we have handles.
|
|
if ( AssetInfo.handleCount > 0 )
|
|
{
|
|
TArray< HAPI_HandleInfo > HandleInfos;
|
|
HandleInfos.SetNumZeroed( AssetInfo.handleCount );
|
|
for (int32 Idx = 0; Idx < HandleInfos.Num(); Idx++)
|
|
FHoudiniApi::HandleInfo_Init(&(HandleInfos[Idx]));
|
|
|
|
if ( FHoudiniApi::GetHandleInfo(
|
|
FHoudiniEngine::Get().GetSession(), AssetId,
|
|
&HandleInfos[ 0 ], 0, AssetInfo.handleCount ) != HAPI_RESULT_SUCCESS )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for ( int32 HandleIdx = 0; HandleIdx < AssetInfo.handleCount; ++HandleIdx )
|
|
{
|
|
// Retrieve handle info for this index.
|
|
const HAPI_HandleInfo & HandleInfo = HandleInfos[ HandleIdx ];
|
|
|
|
// If we do not have bindings, we can skip.
|
|
if ( HandleInfo.bindingsCount <= 0 )
|
|
continue;
|
|
|
|
FString TypeName = TEXT( "" );
|
|
EHoudiniHandleType HandleType = EHoudiniHandleType::Unsupported;
|
|
{
|
|
FHoudiniEngineString HoudiniEngineString( HandleInfo.typeNameSH );
|
|
if( !HoudiniEngineString.ToFString( TypeName ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( TypeName.Equals( TEXT( HAPI_UNREAL_HANDLE_TRANSFORM ) ) )
|
|
HandleType = EHoudiniHandleType::Xform;
|
|
else if( TypeName.Equals( TEXT( HAPI_UNREAL_HANDLE_BOUNDER ) ) )
|
|
HandleType = EHoudiniHandleType::Bounder;
|
|
}
|
|
|
|
FString HandleName = TEXT( "" );
|
|
|
|
{
|
|
FHoudiniEngineString HoudiniEngineString( HandleInfo.nameSH );
|
|
if ( !HoudiniEngineString.ToFString( HandleName ) )
|
|
continue;
|
|
}
|
|
|
|
if( HandleType == EHoudiniHandleType::Unsupported )
|
|
{
|
|
HOUDINI_LOG_DISPLAY( TEXT( "%s: Unsupported Handle Type %s for handle %s" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName(), *TypeName, *HandleName );
|
|
continue;
|
|
}
|
|
|
|
UHoudiniHandleComponent * HandleComponent = nullptr;
|
|
UHoudiniHandleComponent ** FoundHandleComponent = HandleComponents.Find( HandleName );
|
|
|
|
if ( FoundHandleComponent )
|
|
{
|
|
HandleComponent = *FoundHandleComponent;
|
|
|
|
// Remove so that it's not destroyed.
|
|
HandleComponents.Remove( HandleName );
|
|
}
|
|
else
|
|
{
|
|
HandleComponent = NewObject< UHoudiniHandleComponent >(
|
|
this, UHoudiniHandleComponent::StaticClass(),
|
|
NAME_None, RF_Public | RF_Transactional );
|
|
}
|
|
|
|
if ( !HandleComponent )
|
|
continue;
|
|
|
|
// If we have no parent, we need to re-attach.
|
|
if ( !HandleComponent->GetAttachParent() )
|
|
HandleComponent->AttachToComponent( this, FAttachmentTransformRules::KeepRelativeTransform );
|
|
|
|
HandleComponent->SetVisibility( true );
|
|
|
|
// If component is not registered, register it.
|
|
if ( !HandleComponent->IsRegistered() )
|
|
HandleComponent->RegisterComponent();
|
|
|
|
if ( HandleComponent->Construct( AssetId, HandleIdx, HandleName, HandleInfo, Parameters, HandleType ) )
|
|
NewHandleComponents.Add( HandleName, HandleComponent );
|
|
}
|
|
}
|
|
|
|
ClearHandles();
|
|
HandleComponents = NewHandleComponents;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateInputs()
|
|
{
|
|
if ( !FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
// There's no Houdini asset, we can return.
|
|
return;
|
|
}
|
|
|
|
HAPI_AssetInfo AssetInfo;
|
|
FHoudiniApi::AssetInfo_Init(&AssetInfo);
|
|
int32 InputCount = 0;
|
|
if ( FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo ) == HAPI_RESULT_SUCCESS
|
|
&& AssetInfo.hasEverCooked )
|
|
{
|
|
InputCount = AssetInfo.geoInputCount;
|
|
}
|
|
|
|
// We've already created the number of required inputs.
|
|
if( Inputs.Num() == InputCount )
|
|
return;
|
|
|
|
// Resize our inputs to match the asset
|
|
if( InputCount == 0 )
|
|
{
|
|
ClearInputs();
|
|
}
|
|
else
|
|
{
|
|
if( InputCount > Inputs.Num() )
|
|
{
|
|
int32 NumNewInputs = InputCount - Inputs.Num();
|
|
for( int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx )
|
|
Inputs.Add( UHoudiniAssetInput::Create( this, InputIdx, AssetId ) );
|
|
}
|
|
else
|
|
{
|
|
// Must be fewer inputs
|
|
for( int32 InputIdx = InputCount; InputIdx < Inputs.Num(); ++InputIdx )
|
|
{
|
|
Inputs[ InputIdx ]->ConditionalBeginDestroy();
|
|
}
|
|
Inputs.SetNum( InputCount );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdateLoadedInputs( const bool& ForceRefresh )
|
|
{
|
|
if ( !FHoudiniEngineUtils::IsValidNodeId(AssetId) )
|
|
return;
|
|
|
|
bool Success = true;
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
HoudiniAssetInput->SetNodeId( AssetId );
|
|
|
|
// If we're updating the input landscape data, we need to restore the original input geometry
|
|
// to submit it as the source mesh
|
|
EHoudiniAssetInputType::Enum InputType = HoudiniAssetInput->GetChoiceIndex();
|
|
if (InputType == EHoudiniAssetInputType::LandscapeInput
|
|
&& HoudiniAssetInput->IsUpdatingInputLandscape() )
|
|
//&& !HoudiniAssetInput->IsLandscapeAssetConnected() )
|
|
FHoudiniLandscapeUtils::RestoreLandscapeFromFile( HoudiniAssetInput->GetLandscapeInput() );
|
|
|
|
Success &= HoudiniAssetInput->ChangeInputType( HoudiniAssetInput->GetChoiceIndex(), ForceRefresh );
|
|
Success &= HoudiniAssetInput->UploadParameterValue();
|
|
}
|
|
|
|
if( !Success )
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("%s UpdateLoadedInputs failed"), GetOwner() ? *(GetOwner()->GetName()) : *GetName());
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::UpdateWaitingForUpstreamAssetsToInstantiate( bool bNotifyUpstreamAsset )
|
|
{
|
|
bWaitingForUpstreamAssetsToInstantiate = false;
|
|
|
|
// We first need to make sure all our asset inputs have been instantiated and reconnected.
|
|
for ( auto LocalInput : Inputs )
|
|
{
|
|
if ( !LocalInput || LocalInput->IsPendingKill() )
|
|
continue;
|
|
|
|
bool bInputAssetNeedsInstantiation = LocalInput->DoesInputAssetNeedInstantiation();
|
|
if ( !bInputAssetNeedsInstantiation )
|
|
continue;
|
|
|
|
bWaitingForUpstreamAssetsToInstantiate = true;
|
|
|
|
if ( bNotifyUpstreamAsset )
|
|
{
|
|
UHoudiniAssetComponent * LocalInputAssetComponent = LocalInput->GetConnectedInputAssetComponent();
|
|
if ( LocalInputAssetComponent && !LocalInputAssetComponent->IsPendingKill() )
|
|
LocalInputAssetComponent->NotifyParameterChanged( nullptr );
|
|
}
|
|
}
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetInput* Input = Cast< UHoudiniAssetInput >( IterParams.Value() );
|
|
if ( !Input || Input->IsPendingKill() )
|
|
continue;
|
|
|
|
bool bInputAssetNeedsInstantiation = Input->DoesInputAssetNeedInstantiation();
|
|
if ( !bInputAssetNeedsInstantiation )
|
|
continue;
|
|
|
|
bWaitingForUpstreamAssetsToInstantiate = true;
|
|
|
|
if ( bNotifyUpstreamAsset )
|
|
{
|
|
UHoudiniAssetComponent * LocalInputAssetComponent = Input->GetConnectedInputAssetComponent();
|
|
if ( LocalInputAssetComponent && !LocalInputAssetComponent->IsPendingKill() )
|
|
LocalInputAssetComponent->NotifyParameterChanged( nullptr );
|
|
}
|
|
}
|
|
|
|
return bWaitingForUpstreamAssetsToInstantiate;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::RefreshEditableNodesAfterLoad()
|
|
{
|
|
// For some reason, we need to go through all the editable nodes once
|
|
// To "Activate" them...
|
|
HAPI_AssetInfo AssetInfo;
|
|
FHoudiniApi::AssetInfo_Init(&AssetInfo);
|
|
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo(
|
|
FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false);
|
|
|
|
// Retrieve information about each object contained within our asset.
|
|
TArray< HAPI_ObjectInfo > ObjectInfos;
|
|
if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos))
|
|
return false;
|
|
|
|
// Iterate through all objects.
|
|
for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ++ObjectIdx)
|
|
{
|
|
// Retrieve object at this index.
|
|
const HAPI_ObjectInfo & ObjectInfo = ObjectInfos[ObjectIdx];
|
|
|
|
// Get all the GeoInfos for the editable nodes
|
|
int32 EditableNodeCount = 0;
|
|
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList(
|
|
FHoudiniEngine::Get().GetSession(), ObjectInfo.nodeId,
|
|
HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE,
|
|
true, &EditableNodeCount), false);
|
|
|
|
if (EditableNodeCount <= 0)
|
|
continue;
|
|
|
|
TArray< HAPI_NodeId > EditableNodeIds;
|
|
EditableNodeIds.SetNumUninitialized(EditableNodeCount);
|
|
HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList(
|
|
FHoudiniEngine::Get().GetSession(), AssetId,
|
|
EditableNodeIds.GetData(), EditableNodeCount), false);
|
|
|
|
for (int nEditable = 0; nEditable < EditableNodeCount; nEditable++)
|
|
{
|
|
HAPI_GeoInfo CurrentEditableGeoInfo;
|
|
FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo);
|
|
HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetGeoInfo(
|
|
FHoudiniEngine::Get().GetSession(),
|
|
EditableNodeIds[nEditable],
|
|
&CurrentEditableGeoInfo), false);
|
|
|
|
if ( CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE )
|
|
continue;
|
|
|
|
FString NodePathTemp;
|
|
if ( !FHoudiniEngineUtils::HapiGetNodePath( CurrentEditableGeoInfo.nodeId, AssetId, NodePathTemp ) )
|
|
continue;
|
|
|
|
// We need to refresh the spline corresponding to that node
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
|
|
// Get NodePath appends the partId to the node path, so we use contains instead of equals
|
|
if ( HoudiniGeoPartObject.GetNodePath().Contains( NodePathTemp ) )
|
|
{
|
|
// Update the Geo/Node Id
|
|
HoudiniGeoPartObject.GeoId = CurrentEditableGeoInfo.nodeId;
|
|
|
|
// Update the attached spline component too
|
|
UHoudiniSplineComponent * SplineComponent = Iter.Value();
|
|
if ( SplineComponent )
|
|
SplineComponent->SetHoudiniGeoPartObject( HoudiniGeoPartObject );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
UHoudiniAssetComponent::UploadLoadedCurves()
|
|
{
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TIterator Iter( SplineComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniSplineComponent * HoudiniSplineComponent = Iter.Value();
|
|
if ( !HoudiniSplineComponent )
|
|
continue;
|
|
|
|
HoudiniSplineComponent->UploadControlPoints();
|
|
}
|
|
}
|
|
|
|
UHoudiniAssetInstanceInput*
|
|
UHoudiniAssetComponent::LocateInstanceInput( const FHoudiniGeoPartObject& GeoPart ) const
|
|
{
|
|
for ( UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs )
|
|
{
|
|
if ( !InstanceInput || InstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
// Verify path to instancer + various configuration flags
|
|
if ( InstanceInput->GetGeoPartObject().GetNodePath() == GeoPart.GetNodePath() &&
|
|
UHoudiniAssetInstanceInput::GetInstancerFlags(GeoPart).HoudiniAssetInstanceInputFlagsPacked ==
|
|
InstanceInput->Flags.HoudiniAssetInstanceInputFlagsPacked )
|
|
{
|
|
return InstanceInput;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CreateInstanceInputs( const TArray< FHoudiniGeoPartObject > & Instancers )
|
|
{
|
|
TArray< UHoudiniAssetInstanceInput * > NewInstanceInputs;
|
|
|
|
for ( const FHoudiniGeoPartObject& GeoPart : Instancers )
|
|
{
|
|
if ( GeoPart.IsVisible() )
|
|
{
|
|
// Check if this instance input already exists.
|
|
UHoudiniAssetInstanceInput * HoudiniAssetInstanceInput = nullptr;
|
|
|
|
UHoudiniAssetInstanceInput * FoundHoudiniAssetInstanceInput = LocateInstanceInput(GeoPart);
|
|
if ( FoundHoudiniAssetInstanceInput && !FoundHoudiniAssetInstanceInput->IsPendingKill() )
|
|
{
|
|
// Input already exists, we can reuse it.
|
|
HoudiniAssetInstanceInput = FoundHoudiniAssetInstanceInput;
|
|
|
|
// Since this is the corresponding part, we will refresh the InstanceInput's GeoPart
|
|
HoudiniAssetInstanceInput->SetGeoPartObject( GeoPart );
|
|
|
|
// Remove it from old map.
|
|
InstanceInputs.Remove( FoundHoudiniAssetInstanceInput );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise we need to create new instance input.
|
|
HoudiniAssetInstanceInput = UHoudiniAssetInstanceInput::Create( this, GeoPart );
|
|
}
|
|
|
|
if ( !HoudiniAssetInstanceInput || HoudiniAssetInstanceInput->IsPendingKill() )
|
|
{
|
|
// Invalid instance input.
|
|
HOUDINI_LOG_WARNING( TEXT( "%s: Failed to create Instancer from part %s" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName(), *GeoPart.GetNodePath() );
|
|
}
|
|
else
|
|
{
|
|
// Add input to new map.
|
|
NewInstanceInputs.Add( HoudiniAssetInstanceInput );
|
|
// Create or re-create this input.
|
|
HoudiniAssetInstanceInput->CreateInstanceInput();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear all the existing instance inputs and replace with the new
|
|
ClearInstanceInputs();
|
|
InstanceInputs = NewInstanceInputs;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::DuplicateParameters( UHoudiniAssetComponent * DuplicatedHoudiniComponent )
|
|
{
|
|
if ( !DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill() )
|
|
return;
|
|
|
|
TMap< HAPI_ParmId, UHoudiniAssetParameter * > & InParameters = DuplicatedHoudiniComponent->Parameters;
|
|
TMap< FString, UHoudiniAssetParameter * > & InParametersByName = DuplicatedHoudiniComponent->ParameterByName;
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
HAPI_ParmId HoudiniAssetParameterKey = IterParams.Key();
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if ( !HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() )
|
|
continue;
|
|
|
|
if (HoudiniAssetParameterKey == -1)
|
|
continue;
|
|
|
|
// Duplicate parameter.
|
|
UHoudiniAssetParameter * DuplicatedHoudiniAssetParameter = HoudiniAssetParameter->Duplicate( DuplicatedHoudiniComponent );
|
|
if (!DuplicatedHoudiniAssetParameter || DuplicatedHoudiniAssetParameter->IsPendingKill())
|
|
continue;
|
|
|
|
// PIE does not like standalone flags.
|
|
DuplicatedHoudiniAssetParameter->ClearFlags( RF_Standalone );
|
|
DuplicatedHoudiniAssetParameter->SetHoudiniAssetComponent( DuplicatedHoudiniComponent );
|
|
|
|
// For Input Parameters
|
|
UHoudiniAssetInput* HoudiniAssetInputParameter = Cast< UHoudiniAssetInput >(HoudiniAssetParameter);
|
|
UHoudiniAssetInput* DuplicatedHoudiniAssetInputParameter = Cast< UHoudiniAssetInput >(DuplicatedHoudiniAssetParameter);
|
|
if(HoudiniAssetInputParameter && DuplicatedHoudiniAssetInputParameter)
|
|
{
|
|
// Invalidate the node ids on the duplicate so that new inputs will be created.
|
|
DuplicatedHoudiniAssetInputParameter->InvalidateNodeIds();
|
|
|
|
// We also need to duplicate the attached curves properly
|
|
DuplicatedHoudiniAssetInputParameter->DuplicateCurves(HoudiniAssetInputParameter);
|
|
}
|
|
|
|
InParameters.Add( HoudiniAssetParameterKey, DuplicatedHoudiniAssetParameter );
|
|
InParametersByName.Add(
|
|
DuplicatedHoudiniAssetParameter->GetParameterName(),
|
|
DuplicatedHoudiniAssetParameter );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::DuplicateHandles( UHoudiniAssetComponent * SrcAssetComponent )
|
|
{
|
|
if (!SrcAssetComponent || SrcAssetComponent->IsPendingKill())
|
|
return;
|
|
|
|
for ( auto const & SrcNameToHandle : SrcAssetComponent->HandleComponents )
|
|
{
|
|
if (!SrcNameToHandle.Value || SrcNameToHandle.Value->IsPendingKill())
|
|
continue;
|
|
|
|
// Duplicate spline component.
|
|
UHoudiniHandleComponent * NewHandleComponent =
|
|
DuplicateObject< UHoudiniHandleComponent >( SrcNameToHandle.Value, this );
|
|
|
|
if ( NewHandleComponent && !NewHandleComponent->IsPendingKill() )
|
|
{
|
|
NewHandleComponent->SetFlags( RF_Transactional | RF_Public );
|
|
NewHandleComponent->ResolveDuplicatedParameters( Parameters );
|
|
HandleComponents.Add( SrcNameToHandle.Key, NewHandleComponent );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::DuplicateInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent )
|
|
{
|
|
if (!DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill())
|
|
return;
|
|
|
|
TArray< UHoudiniAssetInput * > & InInputs = DuplicatedHoudiniComponent->Inputs;
|
|
for ( int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx )
|
|
{
|
|
// Retrieve input at this index.
|
|
UHoudiniAssetInput * AssetInput = Inputs[ InputIdx ];
|
|
if (!AssetInput || AssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
// Duplicate input.
|
|
UHoudiniAssetInput * DuplicateAssetInput = DuplicateObject( AssetInput, DuplicatedHoudiniComponent );
|
|
if (!DuplicateAssetInput || DuplicateAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
DuplicateAssetInput->SetHoudiniAssetComponent( DuplicatedHoudiniComponent );
|
|
|
|
// Invalidate the node ids on the duplicate so that new inputs will be created.
|
|
DuplicateAssetInput->InvalidateNodeIds();
|
|
|
|
// We also need to duplicate the attached curves properly
|
|
DuplicateAssetInput->DuplicateCurves(AssetInput);
|
|
|
|
// PIE does not like standalone flags.
|
|
DuplicateAssetInput->ClearFlags( RF_Standalone );
|
|
|
|
InInputs.Add( DuplicateAssetInput );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::DuplicateInstanceInputs( UHoudiniAssetComponent * DuplicatedHoudiniComponent, const TMap<UObject*, UObject*>& ReplacementMap )
|
|
{
|
|
if ( !DuplicatedHoudiniComponent || DuplicatedHoudiniComponent->IsPendingKill() )
|
|
return;
|
|
|
|
auto& InInstanceInputs = DuplicatedHoudiniComponent->InstanceInputs;
|
|
|
|
for ( auto& HoudiniAssetInstanceInput : InstanceInputs )
|
|
{
|
|
if ( !HoudiniAssetInstanceInput && HoudiniAssetInstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
UHoudiniAssetInstanceInput * DuplicatedHoudiniAssetInstanceInput =
|
|
UHoudiniAssetInstanceInput::Create( DuplicatedHoudiniComponent, HoudiniAssetInstanceInput );
|
|
|
|
if ( !DuplicatedHoudiniAssetInstanceInput || DuplicatedHoudiniAssetInstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
// PIE does not like standalone flags.
|
|
DuplicatedHoudiniAssetInstanceInput->ClearFlags( RF_Standalone );
|
|
|
|
InInstanceInputs.Add( DuplicatedHoudiniAssetInstanceInput );
|
|
|
|
// remap our instanced objects (only necessary for unbaked assets)
|
|
for( UHoudiniAssetInstanceInputField* InputField : DuplicatedHoudiniAssetInstanceInput->GetInstanceInputFields() )
|
|
{
|
|
if ( !InputField || InputField->IsPendingKill() )
|
|
continue;
|
|
|
|
InputField->FixInstancedObjects( ReplacementMap );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::CreateAllLandscapes( const TArray< FHoudiniGeoPartObject > & FoundVolumes )
|
|
{
|
|
if ( FoundVolumes.Num() <= 0 )
|
|
return false;
|
|
|
|
// Try to create a Landscape for each HeightData found
|
|
TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> > NewLandscapes;
|
|
|
|
FHoudiniCookParams HoudiniCookParams( this );
|
|
HoudiniCookParams.StaticMeshBakeMode = FHoudiniCookParams::GetDefaultStaticMeshesCookMode();
|
|
HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode();
|
|
|
|
// Keep a map of the valid landscape, this is to ensure that none of the new/input landscapes will be destroyed when cleaning up the old map
|
|
TArray<ALandscapeProxy *> ValidLandscapes;
|
|
// See if we have any landscape input that is marked as needing an update
|
|
TArray<ALandscapeProxy *> InputLandscapesToUpdate;
|
|
// Get all the inputs, including the object merge inputs
|
|
TArray< UHoudiniAssetInput * > AllInputs;
|
|
GetInputs(AllInputs, true);
|
|
for ( auto CurrentInput : AllInputs)
|
|
{
|
|
if ( !CurrentInput )
|
|
continue;
|
|
|
|
if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::Enum::LandscapeInput )
|
|
continue;
|
|
|
|
ALandscapeProxy* InputLandscape = CurrentInput->GetLandscapeInput();
|
|
if ( !InputLandscape || InputLandscape->IsPendingKill() )
|
|
continue;
|
|
|
|
ValidLandscapes.Add( InputLandscape );
|
|
|
|
if ( CurrentInput->IsUpdatingInputLandscape() )
|
|
InputLandscapesToUpdate.Add( CurrentInput->GetLandscapeInput() );
|
|
}
|
|
|
|
if ( !FHoudiniLandscapeUtils::CreateAllLandscapes( HoudiniCookParams, FoundVolumes, LandscapeComponents, NewLandscapes, InputLandscapesToUpdate ) )
|
|
return false;
|
|
|
|
// The asset needs to be static in order to attach the landscapes to it
|
|
SetMobility( EComponentMobility::Static );
|
|
|
|
for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator IterLandscape( NewLandscapes ); IterLandscape; ++IterLandscape )
|
|
{
|
|
ALandscapeProxy* NewLandscape = IterLandscape.Value().Get();
|
|
if ( !NewLandscape || !NewLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
// Add the new landscape to the valid list to avoid its destruction if we updated it
|
|
ValidLandscapes.Add( NewLandscape );
|
|
|
|
// Attach the new landscapes to ourselves if we dont already own it
|
|
if ( GetAttachChildren().Find( NewLandscape->GetRootComponent() ) == INDEX_NONE )
|
|
NewLandscape->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
|
|
|
|
FHoudiniGeoPartObject Heightfield = IterLandscape.Key();
|
|
|
|
// Update the materials from our assignement/replacement and the materials assigned on the previous version of this landscape
|
|
UpdateLandscapeMaterialsAssignementsAndReplacements( NewLandscape, Heightfield );
|
|
|
|
// Replace any reference we might still have to the old landscape with the new one
|
|
TWeakObjectPtr<ALandscapeProxy>* OldLandscapePtr = LandscapeComponents.Find( Heightfield );
|
|
if ( !OldLandscapePtr)
|
|
continue;
|
|
|
|
ALandscapeProxy* OldLandscape = OldLandscapePtr->Get();
|
|
if ( !OldLandscape || !OldLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
if ( OldLandscape != NewLandscape )
|
|
FHoudiniLandscapeUtils::UpdateOldLandscapeReference( OldLandscape, NewLandscape );
|
|
}
|
|
|
|
// Replace the old landscapes map with the new ones
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator Iter( LandscapeComponents ); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value().Get();
|
|
if ( !HoudiniLandscape || HoudiniLandscape->IsPendingKill() || !HoudiniLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
if ( ValidLandscapes.Contains( HoudiniLandscape ) )
|
|
continue;
|
|
|
|
HoudiniLandscape->UnregisterAllComponents();
|
|
HoudiniLandscape->Destroy();
|
|
}
|
|
|
|
LandscapeComponents.Empty();
|
|
LandscapeComponents = NewLandscapes;
|
|
|
|
return true;
|
|
}
|
|
|
|
void UHoudiniAssetComponent::UpdateLandscapeMaterialsAssignementsAndReplacements(ALandscapeProxy* Landscape, FHoudiniGeoPartObject Heightfield )
|
|
{
|
|
if ( !Landscape )
|
|
return;
|
|
|
|
// Handle the material assignment/replacement here
|
|
UMaterialInterface* LandscapeMaterial = Landscape->GetLandscapeMaterial();
|
|
if ( LandscapeMaterial )
|
|
{
|
|
// Make sure this material is in the assignements before trying to replacing it.
|
|
if ( !GetAssignmentMaterial( LandscapeMaterial->GetName() ) && HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
HoudiniAssetComponentMaterials->Assignments.Add( LandscapeMaterial->GetName(), LandscapeMaterial );
|
|
|
|
// See if we have a replacement material for this.
|
|
UMaterialInterface * ReplacementMaterialInterface = GetReplacementMaterial( Heightfield, LandscapeMaterial->GetName() );
|
|
if ( ReplacementMaterialInterface )
|
|
LandscapeMaterial = ReplacementMaterialInterface;
|
|
}
|
|
|
|
UMaterialInterface* LandscapeHoleMaterial = Landscape->GetLandscapeHoleMaterial();
|
|
if ( LandscapeHoleMaterial )
|
|
{
|
|
// Make sure this material is in the assignemets before trying to replacing it.
|
|
if ( !GetAssignmentMaterial( LandscapeHoleMaterial->GetName() ) && HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
HoudiniAssetComponentMaterials->Assignments.Add( LandscapeHoleMaterial->GetName(), LandscapeHoleMaterial );
|
|
|
|
// See if we have a replacement material for this.
|
|
UMaterialInterface * ReplacementMaterialInterface = GetReplacementMaterial( Heightfield, LandscapeHoleMaterial->GetName() );
|
|
if ( ReplacementMaterialInterface )
|
|
LandscapeHoleMaterial = ReplacementMaterialInterface;
|
|
}
|
|
|
|
// Try to see if we can find materials used by the previous landscape for this Heightfield
|
|
// ( the user might have replaced the landscape materials manually without us knowing it )
|
|
if ( LandscapeComponents.Contains( Heightfield ) )
|
|
{
|
|
ALandscapeProxy* PreviousLandscape = LandscapeComponents[ Heightfield ].Get();
|
|
if ( PreviousLandscape && PreviousLandscape->IsValidLowLevel() )
|
|
{
|
|
// Get the previously used materials, but ignore the default ones
|
|
UMaterialInterface* PreviousLandscapeMaterial = PreviousLandscape->GetLandscapeMaterial();
|
|
if ( PreviousLandscapeMaterial == UMaterial::GetDefaultMaterial( MD_Surface ) )
|
|
PreviousLandscapeMaterial = nullptr;
|
|
|
|
if ( PreviousLandscapeMaterial && PreviousLandscapeMaterial != LandscapeMaterial )
|
|
{
|
|
ReplaceMaterial( Heightfield, PreviousLandscapeMaterial, LandscapeMaterial, 0 );
|
|
LandscapeMaterial = PreviousLandscapeMaterial;
|
|
}
|
|
|
|
// Do the same thing for the hole material
|
|
UMaterialInterface* PreviousLandscapeHoleMaterial = PreviousLandscape->GetLandscapeHoleMaterial();
|
|
if ( PreviousLandscapeHoleMaterial == UMaterial::GetDefaultMaterial( MD_Surface ) )
|
|
PreviousLandscapeHoleMaterial = nullptr;
|
|
|
|
if ( PreviousLandscapeHoleMaterial && PreviousLandscapeHoleMaterial != LandscapeHoleMaterial )
|
|
{
|
|
ReplaceMaterial( Heightfield, PreviousLandscapeHoleMaterial, LandscapeHoleMaterial, 0 );
|
|
LandscapeHoleMaterial = PreviousLandscapeHoleMaterial;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign the new material if they have been updated
|
|
if ( Landscape->LandscapeMaterial != LandscapeMaterial )
|
|
Landscape->LandscapeMaterial = LandscapeMaterial;
|
|
|
|
if ( Landscape->LandscapeHoleMaterial != LandscapeHoleMaterial )
|
|
Landscape->LandscapeHoleMaterial = LandscapeHoleMaterial;
|
|
|
|
Landscape->UpdateAllComponentMaterialInstances();
|
|
|
|
/*
|
|
// As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty
|
|
// to trigger a fake Property change event that will call the Update function...
|
|
UProperty* FoundProperty = FindField< UProperty >(Landscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial"));
|
|
if (FoundProperty)
|
|
{
|
|
FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet);
|
|
Landscape->PostEditChangeProperty(PropChanged);
|
|
}
|
|
*/
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::ReplaceLandscapeInInputs(ALandscapeProxy* Old, ALandscapeProxy* New )
|
|
{
|
|
bool bReturn = false;
|
|
|
|
// First, get all the inputs, including the object path parameters
|
|
TArray< UHoudiniAssetInput * > AllInputs;
|
|
GetInputs( AllInputs, true );
|
|
|
|
// Look for landscape input, selecting the old landscape
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( AllInputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( HoudiniAssetInput->GetChoiceIndex() != EHoudiniAssetInputType::LandscapeInput )
|
|
continue;
|
|
|
|
if ( Old != HoudiniAssetInput->GetLandscapeInput() )
|
|
continue;
|
|
|
|
HoudiniAssetInput->OnLandscapeActorSelected( New );
|
|
HoudiniAssetInput->UploadParameterValue();
|
|
|
|
bReturn = true;
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearInstanceInputs()
|
|
{
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
InstanceInput->ConditionalBeginDestroy();
|
|
}
|
|
|
|
InstanceInputs.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearCurves()
|
|
{
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent*>::TIterator Iter( SplineComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniSplineComponent * SplineComponent = Iter.Value();
|
|
if (SplineComponent)
|
|
{
|
|
SplineComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
|
|
SplineComponent->UnregisterComponent();
|
|
SplineComponent->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
SplineComponents.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearLandscapes()
|
|
{
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator Iter(LandscapeComponents); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value().Get();
|
|
if ( !IsValid( HoudiniLandscape ) || !HoudiniLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
// Make sure we never destroy an input landscape
|
|
bool IsCurrentLandscapeAnInput = false;
|
|
for (auto CurrentInput : Inputs)
|
|
{
|
|
if (!CurrentInput)
|
|
continue;
|
|
|
|
if (CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::Enum::LandscapeInput)
|
|
continue;
|
|
|
|
ALandscapeProxy* InputLandscape = CurrentInput->GetLandscapeInput();
|
|
if (!InputLandscape || InputLandscape->IsPendingKill())
|
|
continue;
|
|
|
|
IsCurrentLandscapeAnInput = true;
|
|
break;
|
|
}
|
|
|
|
if ( IsCurrentLandscapeAnInput )
|
|
continue;
|
|
|
|
HoudiniLandscape->UnregisterAllComponents();
|
|
HoudiniLandscape->Destroy();
|
|
}
|
|
|
|
LandscapeComponents.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearParameters()
|
|
{
|
|
for ( auto Iter : Parameters )
|
|
{
|
|
if ( Iter.Value && !Iter.Value->IsPendingKill() )
|
|
{
|
|
Iter.Value->ConditionalBeginDestroy();
|
|
}
|
|
else if(GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE)
|
|
{
|
|
// Avoid spamming that error when leaving PIE mode
|
|
HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName());
|
|
}
|
|
}
|
|
|
|
Parameters.Empty();
|
|
ParameterByName.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearHandles()
|
|
{
|
|
for ( auto & NameToComponent : HandleComponents )
|
|
{
|
|
UHoudiniHandleComponent * HandleComponent = NameToComponent.Value;
|
|
|
|
HandleComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform );
|
|
HandleComponent->UnregisterComponent();
|
|
HandleComponent->DestroyComponent();
|
|
}
|
|
|
|
HandleComponents.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearInputs()
|
|
{
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( !HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if (HoudiniAssetInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad))
|
|
continue;
|
|
|
|
// Destroy connected Houdini asset.
|
|
HoudiniAssetInput->ConditionalBeginDestroy();
|
|
}
|
|
|
|
Inputs.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearDownstreamAssets()
|
|
{
|
|
for ( TMap< UHoudiniAssetComponent *, TSet< int32 > >::TIterator IterAssets(DownstreamAssetConnections );
|
|
IterAssets; ++IterAssets )
|
|
{
|
|
UHoudiniAssetComponent * DownstreamAsset = IterAssets.Key();
|
|
if (!DownstreamAsset || DownstreamAsset->IsPendingKill())
|
|
continue;
|
|
|
|
TSet< int32 > & LocalInputIndicies = IterAssets.Value();
|
|
for ( auto LocalInputIndex : LocalInputIndicies )
|
|
{
|
|
if( DownstreamAsset->Inputs.IsValidIndex( LocalInputIndex )
|
|
&& DownstreamAsset->Inputs[ LocalInputIndex ] != nullptr
|
|
&& !DownstreamAsset->Inputs[LocalInputIndex]->IsPendingKill() )
|
|
{
|
|
DownstreamAsset->Inputs[ LocalInputIndex ]->ExternalDisconnectInputAssetActor();
|
|
}
|
|
else
|
|
{
|
|
// It could be connected as an operator path parameter
|
|
bool DidADisconnect = false;
|
|
for( auto& OtherParmElem : DownstreamAsset->Parameters )
|
|
{
|
|
UHoudiniAssetInput* OtherParm = Cast<UHoudiniAssetInput>(OtherParmElem.Value);
|
|
if (!OtherParm || OtherParm->IsPendingKill())
|
|
continue;
|
|
|
|
if( OtherParm->GetConnectedInputAssetComponent() == this && OtherParm->IsInputAssetConnected() )
|
|
{
|
|
OtherParm->ExternalDisconnectInputAssetActor();
|
|
DidADisconnect = true;
|
|
}
|
|
}
|
|
|
|
if ( !DidADisconnect )
|
|
HOUDINI_LOG_ERROR( TEXT( "%s: Invalid downstream asset connection" ), GetOwner() ? *(GetOwner()->GetName()) : *GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
DownstreamAssetConnections.Empty();
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::ClearCookTempFile()
|
|
{
|
|
// First, Clean up the assignement/replacement map
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
HoudiniAssetComponentMaterials->ResetMaterialInfo();
|
|
|
|
// Then delete all the materials
|
|
for ( TMap<FString, TWeakObjectPtr< UPackage > > ::TIterator IterPackage(CookedTemporaryPackages );
|
|
IterPackage; ++IterPackage)
|
|
{
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( !Package )
|
|
continue;
|
|
|
|
Package->ClearFlags( RF_Standalone );
|
|
//Package->ConditionalBeginDestroy();
|
|
}
|
|
|
|
CookedTemporaryPackages.Empty();
|
|
|
|
// Delete all cooked Static Meshes
|
|
for ( TMap<FHoudiniGeoPartObject, TWeakObjectPtr< UPackage > > ::TIterator IterPackage( CookedTemporaryStaticMeshPackages );
|
|
IterPackage; ++IterPackage )
|
|
{
|
|
UPackage * Package = IterPackage.Value().Get();
|
|
if ( !Package )
|
|
continue;
|
|
|
|
Package->ClearFlags( RF_Standalone );
|
|
//Package->ConditionalBeginDestroy();
|
|
}
|
|
|
|
CookedTemporaryStaticMeshPackages.Empty();
|
|
|
|
// Delete all cooked Landscape Layers
|
|
for ( TMap<TWeakObjectPtr< UPackage >, FHoudiniGeoPartObject > ::TIterator IterPackage(CookedTemporaryLandscapeLayers);
|
|
IterPackage; ++IterPackage )
|
|
{
|
|
UPackage * Package = IterPackage.Key().Get();
|
|
if ( !Package )
|
|
continue;
|
|
|
|
Package->ClearFlags( RF_Standalone );
|
|
//Package->ConditionalBeginDestroy();
|
|
}
|
|
|
|
CookedTemporaryLandscapeLayers.Empty();
|
|
}
|
|
|
|
UStaticMesh *
|
|
UHoudiniAssetComponent::LocateStaticMesh( const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& ExactSearch ) const
|
|
{
|
|
UStaticMesh * const * FoundStaticMesh = StaticMeshes.Find( HoudiniGeoPartObject );
|
|
UStaticMesh * StaticMesh = nullptr;
|
|
|
|
if ( !ExactSearch && !FoundStaticMesh )
|
|
{
|
|
// We couldnt find the exact SM corresponding to this geo part
|
|
// Try again without caring for the split id
|
|
for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator IterSM( StaticMeshes ); IterSM; ++IterSM )
|
|
{
|
|
const FHoudiniGeoPartObject& HGPO = IterSM.Key();
|
|
if (HGPO.AssetId == HoudiniGeoPartObject.AssetId && HGPO.ObjectId == HoudiniGeoPartObject.ObjectId
|
|
&& HGPO.GeoId == HoudiniGeoPartObject.GeoId && HGPO.PartId == HoudiniGeoPartObject.PartId)
|
|
{
|
|
FoundStaticMesh = &IterSM.Value();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( FoundStaticMesh )
|
|
StaticMesh = *FoundStaticMesh;
|
|
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
return nullptr;
|
|
|
|
return StaticMesh;
|
|
}
|
|
|
|
UStaticMeshComponent *
|
|
UHoudiniAssetComponent::LocateStaticMeshComponent( const UStaticMesh * StaticMesh ) const
|
|
{
|
|
UStaticMeshComponent * const * FoundStaticMeshComponent = StaticMeshComponents.Find( StaticMesh );
|
|
UStaticMeshComponent * StaticMeshComponent = nullptr;
|
|
|
|
if ( FoundStaticMeshComponent )
|
|
StaticMeshComponent = *FoundStaticMeshComponent;
|
|
|
|
if ( StaticMeshComponent && StaticMeshComponent->IsPendingKill() )
|
|
return nullptr;
|
|
|
|
return StaticMeshComponent;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::LocateInstancedStaticMeshComponents(
|
|
const UStaticMesh * StaticMesh, TArray< UInstancedStaticMeshComponent * > & Components ) const
|
|
{
|
|
Components.Empty();
|
|
|
|
bool bResult = false;
|
|
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
bResult |= InstanceInput->CollectAllInstancedStaticMeshComponents( Components, StaticMesh );
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
UHoudiniSplineComponent*
|
|
UHoudiniAssetComponent::LocateSplineComponent(const FHoudiniGeoPartObject & HoudiniGeoPartObject) const
|
|
{
|
|
UHoudiniSplineComponent * const * FoundHoudiniSplineComponent = SplineComponents.Find(HoudiniGeoPartObject);
|
|
UHoudiniSplineComponent * SplineComponent = nullptr;
|
|
|
|
if ( FoundHoudiniSplineComponent )
|
|
SplineComponent = *FoundHoudiniSplineComponent;
|
|
|
|
if ( !SplineComponent || SplineComponent->IsPendingKill() || !SplineComponent->IsValidLowLevel() )
|
|
return nullptr;
|
|
|
|
return SplineComponent;
|
|
}
|
|
|
|
// Allow searching of geopart in array by names and not guid
|
|
UStaticMesh *
|
|
UHoudiniAssetComponent::LocateStaticMeshByNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const TMap< FHoudiniGeoPartObject, UStaticMesh * >& FindInMap) const
|
|
{
|
|
UStaticMesh * StaticMesh = nullptr;
|
|
|
|
// Find via the object and Part names.
|
|
for (TMap< FHoudiniGeoPartObject, UStaticMesh * >::TConstIterator IterSM(FindInMap); IterSM; ++IterSM)
|
|
{
|
|
const FHoudiniGeoPartObject& HGPO = IterSM.Key();
|
|
if (HGPO.CompareNames(HoudiniGeoPartObject))
|
|
{
|
|
StaticMesh = IterSM.Value();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!StaticMesh || StaticMesh->IsPendingKill())
|
|
return nullptr;
|
|
|
|
return StaticMesh;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SerializeInputs( FArchive & Ar )
|
|
{
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
if ( !Ar.IsTransacting() )
|
|
ClearInputs();
|
|
}
|
|
|
|
// We have to make sure that inputs are NOT saved with an empty name, as this will cause UE to crash on load
|
|
for (TArray< UHoudiniAssetInput * >::TIterator IterInputs(Inputs); IterInputs; ++IterInputs)
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if (HoudiniAssetInput->GetFName() != NAME_None)
|
|
continue;
|
|
|
|
// Calling Rename with null parameters will make sure the instanced input has a unique name
|
|
HoudiniAssetInput->Rename();
|
|
}
|
|
|
|
Ar << Inputs;
|
|
|
|
if ( Ar.IsTransacting() )
|
|
{
|
|
for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx)
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = Inputs[InputIdx];
|
|
if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() )
|
|
HoudiniAssetInput->Serialize(Ar);
|
|
}
|
|
}
|
|
else if ( Ar.IsLoading() )
|
|
{
|
|
for ( int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = Inputs[ InputIdx ];
|
|
if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill())
|
|
Inputs[ InputIdx ]->SetHoudiniAssetComponent( this );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SerializeInstanceInputs( FArchive & Ar )
|
|
{
|
|
if ( Ar.IsLoading() )
|
|
{
|
|
// When loading for undo, we want to call Serialize on each InstanceInput
|
|
if ( !Ar.IsTransacting() )
|
|
ClearInstanceInputs();
|
|
|
|
int32 HoudiniAssetComponentVersion = Ar.CustomVer( FHoudiniCustomSerializationVersion::GUID );
|
|
if ( HoudiniAssetComponentVersion > VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP )
|
|
{
|
|
Ar << InstanceInputs;
|
|
}
|
|
else
|
|
{
|
|
int32 InstanceInputCount = 0;
|
|
Ar << InstanceInputCount;
|
|
|
|
InstanceInputs.SetNumUninitialized( InstanceInputCount );
|
|
|
|
for ( int32 InstanceInputIdx = 0; InstanceInputIdx < InstanceInputCount; ++InstanceInputIdx )
|
|
{
|
|
HAPI_NodeId HoudiniInstanceInputKey = -1;
|
|
|
|
Ar << HoudiniInstanceInputKey;
|
|
Ar << InstanceInputs[ InstanceInputIdx ];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We have to make sure that instanced inputs are NOT saved with an empty name, as this will cause UE to crash on load
|
|
for ( TArray< UHoudiniAssetInstanceInput * >::TIterator IterInstance(InstanceInputs ); IterInstance; ++IterInstance )
|
|
{
|
|
UHoudiniAssetInstanceInput * HoudiniInstanceInput = *IterInstance;
|
|
if ( !HoudiniInstanceInput || HoudiniInstanceInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( HoudiniInstanceInput->GetFName() != NAME_None )
|
|
continue;
|
|
|
|
// Calling Rename with null parameters will make sure the instanced input has a unique name
|
|
HoudiniInstanceInput->Rename();
|
|
}
|
|
|
|
Ar << InstanceInputs;
|
|
}
|
|
|
|
if ( Ar.IsTransacting() )
|
|
{
|
|
for ( UHoudiniAssetInstanceInput* InstanceInput : InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
InstanceInput->Serialize( Ar );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SerializeParameters( FArchive & Ar )
|
|
{
|
|
// We have to make sure that parameter are NOT saaved with an empty name, as this will cause UE to crash on load
|
|
for (TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams(Parameters); IterParams; ++IterParams)
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() )
|
|
continue;
|
|
|
|
if (HoudiniAssetParameter->GetFName() != NAME_None)
|
|
continue;
|
|
|
|
// Calling Rename with null parameters will make sure the parameter has a unique name
|
|
HoudiniAssetParameter->Rename();
|
|
}
|
|
|
|
Ar << Parameters;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostLoadInitializeInstanceInputs()
|
|
{
|
|
for ( auto& InstanceInput : InstanceInputs )
|
|
{
|
|
if (InstanceInput && !InstanceInput->IsPendingKill() )
|
|
InstanceInput->SetHoudiniAssetComponent( this );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::PostLoadInitializeParameters()
|
|
{
|
|
// We need to re-patch parent parameter links.
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams( Parameters ); IterParams; ++IterParams )
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetParameter->SetHoudiniAssetComponent( this );
|
|
|
|
HAPI_ParmId ParentParameterId = HoudiniAssetParameter->GetParmParentId();
|
|
if ( ParentParameterId != -1 )
|
|
{
|
|
UHoudiniAssetParameter * const * FoundParentParameter = Parameters.Find( ParentParameterId );
|
|
if (FoundParentParameter)
|
|
{
|
|
if ( *FoundParentParameter && !(*FoundParentParameter)->IsPendingKill() )
|
|
HoudiniAssetParameter->SetParentParameter(*FoundParentParameter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::RemoveStaticMeshComponent( UStaticMesh * StaticMesh )
|
|
{
|
|
UStaticMeshComponent * const * FoundStaticMeshComponent = StaticMeshComponents.Find( StaticMesh );
|
|
if ( FoundStaticMeshComponent )
|
|
{
|
|
StaticMeshComponents.Remove( StaticMesh );
|
|
|
|
UStaticMeshComponent * StaticMeshComponent = *FoundStaticMeshComponent;
|
|
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
|
|
{
|
|
StaticMeshComponent->DetachFromComponent( FDetachmentTransformRules::KeepRelativeTransform );
|
|
StaticMeshComponent->UnregisterComponent();
|
|
StaticMeshComponent->DestroyComponent();
|
|
}
|
|
}
|
|
}
|
|
|
|
const FGuid &
|
|
UHoudiniAssetComponent::GetComponentGuid() const
|
|
{
|
|
return ComponentGUID;
|
|
}
|
|
|
|
UMaterialInterface *
|
|
UHoudiniAssetComponent::GetReplacementMaterial(
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
|
|
const FString & MaterialName )
|
|
{
|
|
UMaterialInterface * ReplacementMaterial = nullptr;
|
|
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements =
|
|
HoudiniAssetComponentMaterials->Replacements;
|
|
|
|
if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) )
|
|
{
|
|
TMap< FString, UMaterialInterface * > & FoundReplacements = MaterialReplacements[ HoudiniGeoPartObject ];
|
|
|
|
UMaterialInterface * const * FoundReplacementMaterial = FoundReplacements.Find( MaterialName );
|
|
if ( FoundReplacementMaterial )
|
|
ReplacementMaterial = *FoundReplacementMaterial;
|
|
}
|
|
}
|
|
|
|
return ReplacementMaterial;
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::GetReplacementMaterialShopName(
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
|
|
UMaterialInterface * MaterialInterface, FString & MaterialName)
|
|
{
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements =
|
|
HoudiniAssetComponentMaterials->Replacements;
|
|
|
|
if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) )
|
|
{
|
|
TMap< FString, UMaterialInterface * > & FoundReplacements = MaterialReplacements[ HoudiniGeoPartObject ];
|
|
|
|
const FString * FoundMaterialShopName = FoundReplacements.FindKey( MaterialInterface );
|
|
if ( FoundMaterialShopName )
|
|
{
|
|
MaterialName = *FoundMaterialShopName;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UMaterialInterface *
|
|
UHoudiniAssetComponent::GetAssignmentMaterial( const FString & MaterialName )
|
|
{
|
|
UMaterialInterface * Material = nullptr;
|
|
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
TMap< FString, UMaterialInterface * > & MaterialAssignments = HoudiniAssetComponentMaterials->Assignments;
|
|
|
|
UMaterialInterface * const * FoundMaterial = MaterialAssignments.Find( MaterialName );
|
|
if ( FoundMaterial )
|
|
Material = *FoundMaterial;
|
|
}
|
|
|
|
return Material;
|
|
}
|
|
|
|
void UHoudiniAssetComponent::ClearAssignmentMaterials()
|
|
{
|
|
if( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
HoudiniAssetComponentMaterials->Assignments.Empty();
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::AddAssignmentMaterial( const FString& MaterialName, UMaterialInterface* MaterialInterface )
|
|
{
|
|
if( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
HoudiniAssetComponentMaterials->Assignments.Add( MaterialName, MaterialInterface );
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::ReplaceMaterial(
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
|
|
UMaterialInterface * NewMaterialInterface,
|
|
UMaterialInterface * OldMaterialInterface,
|
|
int32 MaterialIndex )
|
|
{
|
|
if ( !HoudiniAssetComponentMaterials || HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
return false;
|
|
|
|
// Check that we do own this GeoPartObject, either via StaticMeshes or Landscapes
|
|
UStaticMesh * StaticMesh = LocateStaticMesh( HoudiniGeoPartObject, false );
|
|
if ( StaticMesh )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh );
|
|
if ( !StaticMeshComponent )
|
|
{
|
|
TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents;
|
|
if ( !LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) )
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !LandscapeComponents.Find( HoudiniGeoPartObject ) )
|
|
return false;
|
|
}
|
|
|
|
TMap< FHoudiniGeoPartObject, TMap< FString, UMaterialInterface * > > & MaterialReplacements =
|
|
HoudiniAssetComponentMaterials->Replacements;
|
|
|
|
TMap< FString, UMaterialInterface * > & MaterialAssignments = HoudiniAssetComponentMaterials->Assignments;
|
|
|
|
UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get();
|
|
|
|
if ( !MaterialReplacements.Contains( HoudiniGeoPartObject ) )
|
|
{
|
|
// If there's no replacement map for this geo part object, add one.
|
|
MaterialReplacements.Add( HoudiniGeoPartObject, TMap< FString, UMaterialInterface * >() );
|
|
}
|
|
|
|
// Retrieve replacements for this geo part object.
|
|
TMap< FString, UMaterialInterface * > & MaterialReplacementsValues = MaterialReplacements[ HoudiniGeoPartObject ];
|
|
|
|
const FString * FoundMaterialShopName = MaterialReplacementsValues.FindKey( OldMaterialInterface );
|
|
if ( FoundMaterialShopName )
|
|
{
|
|
// This material has been replaced previously. Replace old material with new material.
|
|
FString MaterialShopName = *FoundMaterialShopName;
|
|
MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface );
|
|
}
|
|
else
|
|
{
|
|
UMaterialInterface * OldMaterial = Cast< UMaterialInterface >( OldMaterialInterface );
|
|
if ( OldMaterial )
|
|
{
|
|
// We have no previous replacement for this material, see if we have it in list of material assignments.
|
|
FoundMaterialShopName = MaterialAssignments.FindKey( OldMaterial );
|
|
if ( FoundMaterialShopName )
|
|
{
|
|
// This material has been assigned previously. Add material replacement entry.
|
|
FString MaterialShopName = *FoundMaterialShopName;
|
|
MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface );
|
|
}
|
|
else if ( OldMaterial == DefaultMaterial )
|
|
{
|
|
// This is replacement for default material. Add material replacement entry.
|
|
FString MaterialShopName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME;
|
|
MaterialReplacementsValues.Add( MaterialShopName, NewMaterialInterface );
|
|
}
|
|
else
|
|
{
|
|
// External Material?
|
|
MaterialReplacementsValues.Add(OldMaterial->GetName(), NewMaterialInterface);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::SetMaterial( int32 ElementIndex, class UMaterialInterface* Material )
|
|
{
|
|
Super::SetMaterial(ElementIndex, Material);
|
|
|
|
#if WITH_EDITOR
|
|
UpdateEditorProperties(false);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::RemoveReplacementMaterial(
|
|
const FHoudiniGeoPartObject & HoudiniGeoPartObject,
|
|
const FString & MaterialName )
|
|
{
|
|
if ( HoudiniAssetComponentMaterials && !HoudiniAssetComponentMaterials->IsPendingKill() )
|
|
{
|
|
TMap< FHoudiniGeoPartObject, TMap<FString, UMaterialInterface * > > & MaterialReplacements =
|
|
HoudiniAssetComponentMaterials->Replacements;
|
|
|
|
if ( MaterialReplacements.Contains( HoudiniGeoPartObject ) )
|
|
{
|
|
TMap< FString, UMaterialInterface * > & MaterialReplacementsValues =
|
|
MaterialReplacements[ HoudiniGeoPartObject ];
|
|
|
|
MaterialReplacementsValues.Remove( MaterialName );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::CreateOrUpdateMaterialInstances()
|
|
{
|
|
#if WITH_EDITOR
|
|
FHoudiniCookParams HoudiniCookParams( this );
|
|
HoudiniCookParams.PackageGUID = ComponentGUID;
|
|
HoudiniCookParams.MaterialAndTextureBakeMode = FHoudiniCookParams::GetDefaultMaterialAndTextureCookMode();
|
|
|
|
bool bMaterialReplaced = false;
|
|
|
|
// 1. FOR STATIC MESHES
|
|
// Handling the creation of material instances from attributes
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( StaticMeshes ); Iter; ++Iter )
|
|
{
|
|
const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
|
|
// Invisible meshes are used for instancers, so we will not skip them as the material instance/parameter attributes
|
|
// need to be set on the "source" mesh
|
|
//if ( !HoudiniGeoPartObject.IsVisible() )
|
|
// continue;
|
|
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
// Replace the source material with the newly created/updated instance
|
|
for( int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++ )
|
|
{
|
|
// The "source" material we want to create an instance of should have already been assigned to the mesh
|
|
UMaterialInstance* NewMaterialInstance = nullptr;
|
|
UMaterialInterface* SourceMaterialInterface = nullptr;
|
|
|
|
// Create a new material instance if needed and update its parameter if needed
|
|
if (!FHoudiniEngineMaterialUtils::CreateMaterialInstances(
|
|
HoudiniGeoPartObject, HoudiniCookParams, NewMaterialInstance, SourceMaterialInterface,
|
|
HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MatIdx ) )
|
|
continue;
|
|
|
|
if (!NewMaterialInstance || !SourceMaterialInterface)
|
|
continue;
|
|
|
|
UMaterialInterface * SMMatInterface = StaticMesh->StaticMaterials[ MatIdx ].MaterialInterface;
|
|
if ( SMMatInterface != SourceMaterialInterface && SMMatInterface->GetBaseMaterial() != SourceMaterialInterface )
|
|
continue;
|
|
|
|
// Replace the material assignment
|
|
if ( !ReplaceMaterial( HoudiniGeoPartObject, NewMaterialInstance, SourceMaterialInterface, MatIdx ) )
|
|
continue;
|
|
|
|
// Update the StaticMesh, StaticMeshComponents and Instanced Static Mesh Components
|
|
StaticMesh->Modify();
|
|
StaticMesh->PreEditChange( nullptr );
|
|
StaticMesh->StaticMaterials[ MatIdx ].MaterialInterface = NewMaterialInstance;
|
|
StaticMesh->PostEditChange();
|
|
StaticMesh->MarkPackageDirty();
|
|
|
|
UStaticMeshComponent * StaticMeshComponent = LocateStaticMeshComponent( StaticMesh );
|
|
if ( StaticMeshComponent )
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
StaticMeshComponent->SetMaterial( MatIdx, NewMaterialInstance );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
|
|
TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents;
|
|
if ( LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) )
|
|
{
|
|
for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx )
|
|
{
|
|
UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ];
|
|
if ( InstancedStaticMeshComponent )
|
|
{
|
|
InstancedStaticMeshComponent->Modify();
|
|
InstancedStaticMeshComponent->SetMaterial( MatIdx, NewMaterialInstance );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. FOR LANDSCAPES
|
|
// Handling the creation of material instances from attributes
|
|
for ( TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator Iter( LandscapeComponents ); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key();
|
|
ALandscapeProxy* Landscape = Iter.Value().Get();
|
|
if ( !Landscape || !Landscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
// The "source" landscape material we want to create an instance of should have already been assigned to the landscape
|
|
UMaterialInstance* NewMaterialInstance = nullptr;
|
|
UMaterialInterface* SourceMaterialInterface = nullptr;
|
|
// Create/update a material instance if needed for the Landscape Material
|
|
bool bLandscapeMaterialReplaced = false;
|
|
if ( FHoudiniEngineMaterialUtils::CreateMaterialInstances(
|
|
HoudiniGeoPartObject, HoudiniCookParams, NewMaterialInstance, SourceMaterialInterface, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE ) )
|
|
{
|
|
if ( NewMaterialInstance && SourceMaterialInterface )
|
|
{
|
|
// Get the old material.
|
|
UMaterialInterface * OldMaterial = Landscape->GetLandscapeMaterial();
|
|
if ( OldMaterial == SourceMaterialInterface || OldMaterial->GetBaseMaterial() == SourceMaterialInterface )
|
|
{
|
|
// Update our replacement table
|
|
bLandscapeMaterialReplaced = ReplaceMaterial( HoudiniGeoPartObject, OldMaterial, NewMaterialInstance, 0 );
|
|
|
|
// Update the landscape's material itself
|
|
Landscape->Modify();
|
|
Landscape->LandscapeMaterial = NewMaterialInstance;
|
|
bLandscapeMaterialReplaced = true;
|
|
bMaterialReplaced = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Repeat the same process for the Landscape HoleMaterial
|
|
// The "source" hole material we want to create an instance of should have already been assigned to the landscape
|
|
UMaterialInstance* NewHoleMaterialInstance = nullptr;
|
|
UMaterialInterface* SourceHoleMaterialInterface = nullptr;
|
|
// Create/update a material instance if needed for the Landscape Hole Material
|
|
bool bLandscapeHoleMaterialReplaced = false;
|
|
if ( FHoudiniEngineMaterialUtils::CreateMaterialInstances(
|
|
HoudiniGeoPartObject, HoudiniCookParams, NewHoleMaterialInstance, SourceHoleMaterialInterface, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE ) )
|
|
{
|
|
if ( NewHoleMaterialInstance && SourceHoleMaterialInterface )
|
|
{
|
|
// Get old material.
|
|
UMaterialInterface * OldHoleMaterial = Landscape->GetLandscapeHoleMaterial();
|
|
if (OldHoleMaterial == SourceHoleMaterialInterface || OldHoleMaterial->GetBaseMaterial() == SourceHoleMaterialInterface)
|
|
{
|
|
// Update our replacement table
|
|
bLandscapeHoleMaterialReplaced = ReplaceMaterial(HoudiniGeoPartObject, OldHoleMaterial, NewHoleMaterialInstance, 0);
|
|
|
|
// Update the landscape's hole material
|
|
Landscape->Modify();
|
|
Landscape->LandscapeHoleMaterial = NewHoleMaterialInstance;
|
|
bLandscapeHoleMaterialReplaced = true;
|
|
bMaterialReplaced = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// For the landscape update:
|
|
// As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty
|
|
// to trigger a fake Property change event that will call the Update function...
|
|
if ( bLandscapeMaterialReplaced )
|
|
{
|
|
FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), TEXT( "LandscapeMaterial" ) );
|
|
if ( FoundProperty )
|
|
{
|
|
FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet );
|
|
Landscape->PostEditChangeProperty( PropChanged );
|
|
}
|
|
}
|
|
|
|
if ( bLandscapeHoleMaterialReplaced )
|
|
{
|
|
FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), TEXT( "LandscapeHoleMaterial" ) );
|
|
if ( FoundProperty )
|
|
{
|
|
FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet );
|
|
Landscape->PostEditChangeProperty( PropChanged );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bMaterialReplaced )
|
|
UpdateEditorProperties( false );
|
|
|
|
return bMaterialReplaced;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
UHoudiniAssetComponent::HasAnySockets() const
|
|
{
|
|
// Return true if any of our StaticMeshComponent HasAnySocket
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( StaticMeshComponent->HasAnySockets() )
|
|
return true;
|
|
}
|
|
|
|
return Super::HasAnySockets();
|
|
}
|
|
|
|
|
|
/** Get a list of sockets this component contains */
|
|
void
|
|
UHoudiniAssetComponent::QuerySupportedSockets( TArray<FComponentSocketDescription>& OutSockets ) const
|
|
{
|
|
// Query all the sockets in our StaticMeshComponents
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter(StaticMeshComponents); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( !StaticMeshComponent->HasAnySockets() )
|
|
continue;
|
|
|
|
TArray< FComponentSocketDescription > ComponentSocket;
|
|
StaticMeshComponent->QuerySupportedSockets( ComponentSocket );
|
|
|
|
OutSockets.Append( ComponentSocket );
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
UHoudiniAssetComponent::DoesSocketExist( FName SocketName ) const
|
|
{
|
|
// Query all the sockets in our StaticMeshComponents
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( StaticMeshComponent->DoesSocketExist( SocketName ) )
|
|
return true;
|
|
}
|
|
|
|
return Super::DoesSocketExist( SocketName );
|
|
}
|
|
|
|
|
|
FTransform
|
|
UHoudiniAssetComponent::GetSocketTransform( FName InSocketName, ERelativeTransformSpace TransformSpace ) const
|
|
{
|
|
// Query all the sockets in our StaticMeshComponents
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( !StaticMeshComponent )
|
|
continue;
|
|
|
|
if ( !StaticMeshComponent->DoesSocketExist( InSocketName ) )
|
|
continue;
|
|
|
|
return StaticMeshComponent->GetSocketTransform( InSocketName, TransformSpace );
|
|
}
|
|
|
|
return Super::GetSocketTransform( InSocketName, TransformSpace );
|
|
}
|
|
|
|
FBox
|
|
UHoudiniAssetComponent::GetAssetBounds( UHoudiniAssetInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape ) const
|
|
{
|
|
FBox BoxBounds( ForceInitToZero );
|
|
|
|
// Query the bounds of all our static mesh components..
|
|
for ( TMap< UStaticMesh *, UStaticMeshComponent * >::TConstIterator Iter( StaticMeshComponents ); Iter; ++Iter )
|
|
{
|
|
UStaticMeshComponent * StaticMeshComponent = Iter.Value();
|
|
if ( !StaticMeshComponent || StaticMeshComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox();
|
|
if ( StaticMeshBounds.IsValid )
|
|
BoxBounds += StaticMeshBounds;
|
|
}
|
|
|
|
// Also scan all our decendants for SMC bounds not just top-level children
|
|
// ( split mesh instances' mesh bounds were not gathered proiperly )
|
|
TArray<USceneComponent*> LocalAttachedChildren;
|
|
LocalAttachedChildren.Reserve(16);
|
|
GetChildrenComponents(true, LocalAttachedChildren);
|
|
for (int32 Idx = 0; Idx < LocalAttachedChildren.Num(); ++Idx)
|
|
{
|
|
if (!LocalAttachedChildren[Idx])
|
|
continue;
|
|
|
|
USceneComponent * pChild = LocalAttachedChildren[Idx];
|
|
if (UStaticMeshComponent * StaticMeshComponent = Cast<UStaticMeshComponent>(pChild))
|
|
{
|
|
if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill())
|
|
continue;
|
|
|
|
FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox();
|
|
if (StaticMeshBounds.IsValid)
|
|
BoxBounds += StaticMeshBounds;
|
|
}
|
|
}
|
|
|
|
//... all our Handles
|
|
for ( TMap< FString, UHoudiniHandleComponent * >::TConstIterator Iter( HandleComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniHandleComponent * HandleComponent = Iter.Value();
|
|
if ( !HandleComponent )
|
|
continue;
|
|
|
|
BoxBounds += HandleComponent->GetComponentLocation();
|
|
}
|
|
|
|
// ... all our curves
|
|
for ( TMap< FHoudiniGeoPartObject, UHoudiniSplineComponent* >::TConstIterator Iter( SplineComponents ); Iter; ++Iter )
|
|
{
|
|
UHoudiniSplineComponent * SplineComponent = Iter.Value();
|
|
if ( !SplineComponent || !SplineComponent->IsValidLowLevel() )
|
|
continue;
|
|
|
|
TArray<FVector> SplinePositions;
|
|
SplineComponent->GetCurvePositions( SplinePositions );
|
|
|
|
for (int32 n = 0; n < SplinePositions.Num(); n++)
|
|
{
|
|
BoxBounds += SplinePositions[ n ];
|
|
}
|
|
}
|
|
|
|
// ... and inputs
|
|
for ( int32 n = 0; n < Inputs.Num(); n++ )
|
|
{
|
|
UHoudiniAssetInput* CurrentInput = Inputs[ n ];
|
|
if ( !CurrentInput || CurrentInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( CurrentInput == IgnoreInput )
|
|
continue;
|
|
|
|
FBox StaticMeshBounds = CurrentInput->GetInputBounds( GetComponentLocation() );
|
|
if ( StaticMeshBounds.IsValid )
|
|
BoxBounds += StaticMeshBounds;
|
|
}
|
|
|
|
// ... all our landscapes
|
|
if ( !bIgnoreGeneratedLandscape )
|
|
{
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TConstIterator Iter( LandscapeComponents ); Iter; ++Iter )
|
|
{
|
|
ALandscapeProxy * Landscape = Iter.Value().Get();
|
|
if ( !Landscape )
|
|
continue;
|
|
|
|
FVector Origin, Extent;
|
|
Landscape->GetActorBounds( false, Origin, Extent );
|
|
|
|
FBox LandscapeBounds = FBox::BuildAABB( Origin, Extent );
|
|
BoxBounds += LandscapeBounds;
|
|
}
|
|
}
|
|
|
|
// If nothing was found, init with the asset's location
|
|
if ( BoxBounds.GetVolume() == 0.0f )
|
|
BoxBounds += GetComponentLocation();
|
|
|
|
return BoxBounds;
|
|
}
|
|
|
|
bool UHoudiniAssetComponent::HasLandscapeActor(ALandscapeProxy* LandscapeActor ) const
|
|
{
|
|
// Check if we created the landscape
|
|
for (TMap<FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy>>::TConstIterator Iter(LandscapeComponents); Iter; ++Iter)
|
|
{
|
|
ALandscapeProxy * HoudiniLandscape = Iter.Value().Get();
|
|
if ( HoudiniLandscape && HoudiniLandscape == LandscapeActor )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> > *
|
|
UHoudiniAssetComponent::GetLandscapeComponents()
|
|
{
|
|
return &LandscapeComponents;
|
|
}
|
|
|
|
|
|
/** Set the preset Input for HoudiniTools **/
|
|
void
|
|
UHoudiniAssetComponent::SetHoudiniToolInputPresets( const TMap<UObject*, int32>& InPresets )
|
|
{
|
|
#if WITH_EDITOR
|
|
HoudiniToolInputPreset = InPresets;
|
|
#endif
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void
|
|
UHoudiniAssetComponent::ApplyHoudiniToolInputPreset()
|
|
{
|
|
if ( HoudiniToolInputPreset.Num() <= 0 )
|
|
return;
|
|
|
|
// We'll ignore inputs that have been preset to a curve type
|
|
TArray< UHoudiniAssetInput*> InputArray;
|
|
for ( auto CurrentInput : Inputs )
|
|
{
|
|
if (!CurrentInput || CurrentInput->IsPendingKill())
|
|
continue;
|
|
|
|
if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::CurveInput )
|
|
InputArray.Add( CurrentInput );
|
|
}
|
|
|
|
// Also look for ObjectPath parameter inputs
|
|
for ( auto CurrentParam : Parameters )
|
|
{
|
|
UHoudiniAssetInput* CurrentInput = Cast< UHoudiniAssetInput > ( CurrentParam.Value );
|
|
if ( !CurrentInput || CurrentInput->IsPendingKill() )
|
|
continue;
|
|
|
|
if ( CurrentInput->GetChoiceIndex() != EHoudiniAssetInputType::CurveInput )
|
|
InputArray.Add( CurrentInput );
|
|
}
|
|
|
|
// Identify some special cases first
|
|
bool OnlyHoudiniAssets = true;
|
|
bool OnlyLandscapes = true;
|
|
bool OnlyOneInput = true;
|
|
for ( TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset )
|
|
{
|
|
UObject * Object = IterToolPreset.Key();
|
|
AHoudiniAssetActor* HAsset = Cast<AHoudiniAssetActor>( Object );
|
|
if ( !HAsset || HAsset->IsPendingKill() )
|
|
OnlyHoudiniAssets = false;
|
|
|
|
ALandscapeProxy* Landscape = Cast<ALandscapeProxy>( Object );
|
|
if ( !Landscape || Landscape->IsPendingKill() )
|
|
OnlyLandscapes = false;
|
|
|
|
if ( IterToolPreset.Value() != 0 )
|
|
OnlyOneInput = false;
|
|
}
|
|
|
|
/*
|
|
if ( OnlyHoudiniAssets )
|
|
{
|
|
// Try to apply the supplied Object to the Input
|
|
for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset)
|
|
{
|
|
AHoudiniAssetActor* HAsset = Cast< AHoudiniAssetActor >( IterToolPreset.Key() );
|
|
if (!HAsset)
|
|
continue;
|
|
|
|
int32 InputNumber = IterToolPreset.Value();
|
|
if ( !InputArray.IsValidIndex( InputNumber ) )
|
|
continue;
|
|
|
|
InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::AssetInput );
|
|
InputArray[ InputNumber ]->OnInputActorSelected( HAsset );
|
|
}
|
|
}
|
|
else if ( OnlyLandscapes )
|
|
{
|
|
// Try to apply the supplied Object to the Input
|
|
for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset)
|
|
{
|
|
ALandscapeProxy* Landscape = Cast< ALandscape >( IterToolPreset.Key() );
|
|
if ( !Landscape )
|
|
continue;
|
|
|
|
int32 InputNumber = IterToolPreset.Value();
|
|
if ( !InputArray.IsValidIndex( InputNumber ) )
|
|
continue;
|
|
|
|
InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::LandscapeInput );
|
|
InputArray[ InputNumber ]->OnLandscapeActorSelected( Landscape );
|
|
}
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
// Try to apply the supplied Object to the Input
|
|
for (TMap< UObject*, int32 >::TIterator IterToolPreset( HoudiniToolInputPreset ); IterToolPreset; ++IterToolPreset)
|
|
{
|
|
UObject * Object = IterToolPreset.Key();
|
|
if (!Object || Object->IsPendingKill())
|
|
continue;
|
|
|
|
int32 InputNumber = IterToolPreset.Value();
|
|
|
|
if ( !InputArray.IsValidIndex( InputNumber ) )
|
|
continue;
|
|
|
|
InputArray[ InputNumber ]->AddInputObject( Object );
|
|
|
|
if ( OnlyLandscapes && ( InputArray[ InputNumber ]->GetChoiceIndex() != EHoudiniAssetInputType::LandscapeInput ) )
|
|
InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::LandscapeInput, true );
|
|
|
|
// Dont auto select asset inputs because they dont have the unreal transform, which can cause issue, prefer WorldInput instead
|
|
//if ( OnlyHoudiniAssets && ( InputArray[ InputNumber ]->GetChoiceIndex() != EHoudiniAssetInputType::AssetInput ) )
|
|
// InputArray[ InputNumber ]->ChangeInputType( EHoudiniAssetInputType::AssetInput );
|
|
}
|
|
}
|
|
|
|
// Discard the tool presets after their first setup
|
|
HoudiniToolInputPreset.Empty();
|
|
}
|
|
#endif
|
|
|
|
FPrimitiveSceneProxy*
|
|
UHoudiniAssetComponent::CreateSceneProxy()
|
|
{
|
|
/** Represents a UHoudiniAssetComponent to the scene manager. */
|
|
class FHoudiniAssetSceneProxy final : public FPrimitiveSceneProxy
|
|
{
|
|
public:
|
|
SIZE_T GetTypeHash() const override
|
|
{
|
|
static size_t UniquePointer;
|
|
return reinterpret_cast<size_t>( &UniquePointer );
|
|
}
|
|
|
|
FHoudiniAssetSceneProxy( const UHoudiniAssetComponent* InComponent )
|
|
: FPrimitiveSceneProxy( InComponent )
|
|
{
|
|
}
|
|
|
|
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
|
|
{
|
|
FPrimitiveViewRelevance Result;
|
|
Result.bDrawRelevance = IsShown( View );
|
|
return Result;
|
|
}
|
|
|
|
virtual uint32 GetMemoryFootprint( void ) const override { return( sizeof( *this ) + GetAllocatedSize() ); }
|
|
uint32 GetAllocatedSize( void ) const { return( FPrimitiveSceneProxy::GetAllocatedSize() ); }
|
|
};
|
|
|
|
return new FHoudiniAssetSceneProxy( this );
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::NotifyAssetNeedsToBeReinstantiated()
|
|
{
|
|
bLoadedComponentRequiresInstantiation = true;
|
|
bLoadedComponent = true;
|
|
bFullyLoaded = false;
|
|
AssetCookCount = 0;
|
|
AssetId = -1;
|
|
|
|
// Mark all input as changed
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator IterInputs( Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if ( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() )
|
|
HoudiniAssetInput->MarkChanged( false );
|
|
}
|
|
|
|
// Upload parameters.
|
|
for (TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator IterParams(Parameters); IterParams; ++IterParams)
|
|
{
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if ( HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill() )
|
|
HoudiniAssetParameter->MarkChanged( false );
|
|
}
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::UpdateMobility()
|
|
{
|
|
// If we generated a landscape, force ourself to Static!
|
|
if ( LandscapeComponents.Num() > 0 )
|
|
{
|
|
SetMobility(EComponentMobility::Static);
|
|
}
|
|
else
|
|
{
|
|
// Changed GetAttachChildren to 'GetAllDescendants' due to HoudiniMeshSplitInstanceComponent
|
|
// not propagating property changes to their own child StaticMeshComponents.
|
|
TArray< USceneComponent * > LocalAttachChildren;
|
|
GetChildrenComponents(true, LocalAttachChildren);
|
|
|
|
// If one of the children we created is movable, we need to set ourselves to movable as well
|
|
for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter)
|
|
{
|
|
USceneComponent * SceneComponent = *Iter;
|
|
if (SceneComponent->Mobility == EComponentMobility::Movable)
|
|
SetMobility(EComponentMobility::Movable);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ELightMapInteractionType UHoudiniAssetComponent::GetStaticLightingType() const
|
|
{
|
|
// Override preventing the automatic reset to default of LightmapType in UPrimitiveComponent::PostEditChangeProperty
|
|
// see UStaticMeshComponent::GetStaticLightingType and UStaticMeshComponent::GetStaticLightingInfo
|
|
switch (LightmapType)
|
|
{
|
|
//default and ForceSurface are Texture mode.
|
|
case ELightmapType::Default:
|
|
case ELightmapType::ForceSurface:
|
|
return LMIT_Texture;
|
|
|
|
case ELightmapType::ForceVolumetric:
|
|
return LMIT_GlobalVolume;
|
|
}
|
|
|
|
return LMIT_None;
|
|
}
|
|
|
|
void
|
|
UHoudiniAssetComponent::CopyComponentPropertiesTo(UPrimitiveComponent * pPrimComp)
|
|
{
|
|
// Fixes UProperties not being propagated to newly created UStaticMeshComponents
|
|
// Copies all of the properties that would normally get propagated.
|
|
if (!pPrimComp || pPrimComp->IsPendingKill())
|
|
return;
|
|
|
|
pPrimComp->CastShadow = this->CastShadow;
|
|
pPrimComp->bCastDynamicShadow = this->bCastDynamicShadow;
|
|
pPrimComp->bCastStaticShadow = this->bCastStaticShadow;
|
|
pPrimComp->bCastVolumetricTranslucentShadow = this->bCastVolumetricTranslucentShadow;
|
|
pPrimComp->bCastInsetShadow = this->bCastInsetShadow;
|
|
pPrimComp->bCastHiddenShadow = this->bCastHiddenShadow;
|
|
pPrimComp->bCastShadowAsTwoSided = this->bCastShadowAsTwoSided;
|
|
pPrimComp->LightmapType = this->LightmapType;
|
|
pPrimComp->bLightAttachmentsAsGroup = this->bLightAttachmentsAsGroup;
|
|
pPrimComp->IndirectLightingCacheQuality = this->IndirectLightingCacheQuality;
|
|
pPrimComp->bVisibleInReflectionCaptures = this->bVisibleInReflectionCaptures;
|
|
pPrimComp->bRenderInMainPass = this->bRenderInMainPass;
|
|
//pPrimComp->bRenderInMono = this->bRenderInMono;
|
|
pPrimComp->bOwnerNoSee = this->bOwnerNoSee;
|
|
pPrimComp->bOnlyOwnerSee = this->bOnlyOwnerSee;
|
|
pPrimComp->bTreatAsBackgroundForOcclusion = this->bTreatAsBackgroundForOcclusion;
|
|
pPrimComp->bUseAsOccluder = this->bUseAsOccluder;
|
|
pPrimComp->bRenderCustomDepth = this->bRenderCustomDepth;
|
|
pPrimComp->CustomDepthStencilValue = this->CustomDepthStencilValue;
|
|
pPrimComp->CustomDepthStencilWriteMask = this->CustomDepthStencilWriteMask;
|
|
pPrimComp->TranslucencySortPriority = this->TranslucencySortPriority;
|
|
pPrimComp->LpvBiasMultiplier = this->LpvBiasMultiplier;
|
|
pPrimComp->bReceivesDecals = this->bReceivesDecals;
|
|
pPrimComp->BoundsScale = this->BoundsScale;
|
|
pPrimComp->bUseAttachParentBound = this->bUseAttachParentBound;
|
|
pPrimComp->bAlwaysCreatePhysicsState = this->bAlwaysCreatePhysicsState;
|
|
pPrimComp->bMultiBodyOverlap = this->bMultiBodyOverlap;
|
|
//pPrimComp->bCheckAsyncSceneOnMove = this->bCheckAsyncSceneOnMove;
|
|
pPrimComp->bTraceComplexOnMove = this->bTraceComplexOnMove;
|
|
pPrimComp->bReturnMaterialOnMove = this->bReturnMaterialOnMove;
|
|
pPrimComp->BodyInstance = this->BodyInstance;
|
|
pPrimComp->CanCharacterStepUpOn = this->CanCharacterStepUpOn;
|
|
pPrimComp->bIgnoreRadialImpulse = this->bIgnoreRadialImpulse;
|
|
pPrimComp->bIgnoreRadialForce = this->bIgnoreRadialForce;
|
|
pPrimComp->bApplyImpulseOnDamage = this->bApplyImpulseOnDamage;
|
|
pPrimComp->MinDrawDistance = this->MinDrawDistance;
|
|
pPrimComp->LDMaxDrawDistance = this->LDMaxDrawDistance;
|
|
pPrimComp->CachedMaxDrawDistance = this->CachedMaxDrawDistance;
|
|
pPrimComp->bAllowCullDistanceVolume = this->bAllowCullDistanceVolume;
|
|
pPrimComp->DetailMode = this->DetailMode;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |